30f790a040
* add path implementation This commit adds a pkg/paths package which contains two types, Encrypted and Unencrypted, to statically enforce what is contained in a path. It's part of a refactoring of the code base to be more clear about what is contained in a storj.Path at all the layers. Change-Id: Ifc4d4932da26a97ea99749b8356b4543496a8864 * add encryption store This change adds an encryption.Store type to keep a collection of root keys for arbitrary locations in some buckets. It allows one to look up all of the necessary information to encrypt paths, decrypt paths and decrypt list operations. It adds some exported functions to perform encryption on paths using a Store. Change-Id: I1a3d230c521d65f0ede727f93e1cb389f8be9497 * add shim around streams store This commit changes no functionality, but just reorganizes the code so that changes can be made directly to the streams store implementation without affecting callers. It also adds a Path type that will be used at the interface boundary for the streams store so that it can be sure that it's getting well formed paths that it expects. Change-Id: I50bd682995b185beb653b00562fab62ef11f1ab5 * refactor streams to use encryption store This commit changes the streams store to use the path type as well as the encryption store to handle all of it's encryption and decryption. Some changes were made to how the default key is returned in the encryption store to have it include the case when the bucket exists but no paths matched. The path iterator could also be simplified to not report if a consume was valid: that information is no longer necessary. The kvmetainfo tests were changed to appropriately pass the subtests *testing.T rather than having the closure it executes use the parent one. The test framework now correctly reports which test did the failing. There are still some latent issues with listing in that listing for "a/" and listing for "a" are not the same operation, but we treat them as such. I suspect that there are also issues with paths like "/" or "//foo", but that's for another time. Change-Id: I81cad4ba2850c3d14ba7e632777c4cac93db9472 * use an encryption store at the upper layers Change-Id: Id9b4dd5f27b3ecac863de586e9ae076f4f927f6f * fix linting failures Change-Id: Ifb8378879ad308d4d047a0483850156371a41280 * fix linting in encryption test Change-Id: Ia35647dfe18b0f20fe13763b28e53294f75c38fa * get rid of kvmetainfo rootKey Change-Id: Id795ca03d9417e3fe9634365a121430eb678d6d5 * Fix linting failure for return with else Change-Id: I0b9ffd92be42ffcd8fef7ea735c5fc114a55d3b5 * fix some bugs adding enc store to kvmetainfo Change-Id: I8e765970ba817289c65ec62971ae3bfa2c53a1ba * respond to review feedback Change-Id: I43e2ce29ce2fb6677b1cd6b9469838d80ec92c86
195 lines
7.2 KiB
Go
195 lines
7.2 KiB
Go
// Copyright (C) 2019 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package uplink
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io/ioutil"
|
|
"time"
|
|
|
|
"github.com/vivint/infectious"
|
|
"github.com/zeebo/errs"
|
|
monkit "gopkg.in/spacemonkeygo/monkit.v2"
|
|
|
|
"storj.io/storj/internal/memory"
|
|
"storj.io/storj/pkg/eestream"
|
|
"storj.io/storj/pkg/encryption"
|
|
"storj.io/storj/pkg/identity"
|
|
"storj.io/storj/pkg/metainfo/kvmetainfo"
|
|
"storj.io/storj/pkg/peertls/tlsopts"
|
|
ecclient "storj.io/storj/pkg/storage/ec"
|
|
"storj.io/storj/pkg/storage/segments"
|
|
"storj.io/storj/pkg/storage/streams"
|
|
"storj.io/storj/pkg/storj"
|
|
"storj.io/storj/pkg/transport"
|
|
"storj.io/storj/uplink/metainfo"
|
|
)
|
|
|
|
// RSConfig is a configuration struct that keeps details about default
|
|
// redundancy strategy information
|
|
type RSConfig struct {
|
|
MaxBufferMem memory.Size `help:"maximum buffer memory (in bytes) to be allocated for read buffers" default:"4MiB" hidden:"true"`
|
|
ErasureShareSize memory.Size `help:"the size of each new erasure share in bytes" default:"256B" hidden:"true"`
|
|
MinThreshold int `help:"the minimum pieces required to recover a segment. k." releaseDefault:"29" devDefault:"4" hidden:"true"`
|
|
RepairThreshold int `help:"the minimum safe pieces before a repair is triggered. m." releaseDefault:"35" devDefault:"6" hidden:"true"`
|
|
SuccessThreshold int `help:"the desired total pieces for a segment. o." releaseDefault:"80" devDefault:"8" hidden:"true"`
|
|
MaxThreshold int `help:"the largest amount of pieces to encode to. n." releaseDefault:"130" devDefault:"10" hidden:"true"`
|
|
}
|
|
|
|
// EncryptionConfig is a configuration struct that keeps details about
|
|
// encrypting segments
|
|
type EncryptionConfig struct {
|
|
EncryptionKey string `help:"the root key for encrypting the data which will be stored in KeyFilePath" setup:"true"`
|
|
KeyFilepath string `help:"the path to the file which contains the root key for encrypting the data"`
|
|
DataType int `help:"Type of encryption to use for content and metadata (1=AES-GCM, 2=SecretBox)" default:"1"`
|
|
PathType int `help:"Type of encryption to use for paths (0=Unencrypted, 1=AES-GCM, 2=SecretBox)" default:"1"`
|
|
}
|
|
|
|
// ClientConfig is a configuration struct for the uplink that controls how
|
|
// to talk to the rest of the network.
|
|
type ClientConfig struct {
|
|
APIKey string `default:"" help:"the api key to use for the satellite" noprefix:"true"`
|
|
SatelliteAddr string `releaseDefault:"127.0.0.1:7777" devDefault:"127.0.0.1:10000" help:"the address to use for the satellite" noprefix:"true"`
|
|
MaxInlineSize memory.Size `help:"max inline segment size in bytes" default:"4KiB"`
|
|
SegmentSize memory.Size `help:"the size of a segment in bytes" default:"64MiB"`
|
|
RequestTimeout time.Duration `help:"timeout for request" default:"0h0m20s"`
|
|
DialTimeout time.Duration `help:"timeout for dials" default:"0h0m20s"`
|
|
}
|
|
|
|
// Config uplink configuration
|
|
type Config struct {
|
|
Client ClientConfig
|
|
RS RSConfig
|
|
Enc EncryptionConfig
|
|
TLS tlsopts.Config
|
|
}
|
|
|
|
var (
|
|
mon = monkit.Package()
|
|
|
|
// Error is the errs class of standard End User Client errors
|
|
Error = errs.Class("Uplink configuration error")
|
|
)
|
|
|
|
// GetMetainfo returns an implementation of storj.Metainfo
|
|
func (c Config) GetMetainfo(ctx context.Context, identity *identity.FullIdentity) (db storj.Metainfo, ss streams.Store, err error) {
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
tlsOpts, err := tlsopts.NewOptions(identity, c.TLS)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// ToDo: Handle Versioning for Uplinks here
|
|
|
|
tc := transport.NewClientWithTimeouts(tlsOpts, transport.Timeouts{
|
|
Request: c.Client.RequestTimeout,
|
|
Dial: c.Client.DialTimeout,
|
|
})
|
|
|
|
if c.Client.SatelliteAddr == "" {
|
|
return nil, nil, errors.New("satellite address not specified")
|
|
}
|
|
|
|
m, err := metainfo.NewClient(ctx, tc, c.Client.SatelliteAddr, c.Client.APIKey)
|
|
if err != nil {
|
|
return nil, nil, Error.New("failed to connect to metainfo service: %v", err)
|
|
}
|
|
|
|
project, err := kvmetainfo.SetupProject(m)
|
|
if err != nil {
|
|
return nil, nil, Error.New("failed to create project: %v", err)
|
|
}
|
|
|
|
ec := ecclient.NewClient(tc, c.RS.MaxBufferMem.Int())
|
|
fc, err := infectious.NewFEC(c.RS.MinThreshold, c.RS.MaxThreshold)
|
|
if err != nil {
|
|
return nil, nil, Error.New("failed to create erasure coding client: %v", err)
|
|
}
|
|
rs, err := eestream.NewRedundancyStrategy(eestream.NewRSScheme(fc, c.RS.ErasureShareSize.Int()), c.RS.RepairThreshold, c.RS.SuccessThreshold)
|
|
if err != nil {
|
|
return nil, nil, Error.New("failed to create redundancy strategy: %v", err)
|
|
}
|
|
|
|
maxEncryptedSegmentSize, err := encryption.CalcEncryptedSize(c.Client.SegmentSize.Int64(), c.GetEncryptionScheme())
|
|
if err != nil {
|
|
return nil, nil, Error.New("failed to calculate max encrypted segment size: %v", err)
|
|
}
|
|
segment := segments.NewSegmentStore(m, ec, rs, c.Client.MaxInlineSize.Int(), maxEncryptedSegmentSize)
|
|
|
|
blockSize := c.GetEncryptionScheme().BlockSize
|
|
if int(blockSize)%c.RS.ErasureShareSize.Int()*c.RS.MinThreshold != 0 {
|
|
err = Error.New("EncryptionBlockSize must be a multiple of ErasureShareSize * RS MinThreshold")
|
|
return nil, nil, err
|
|
}
|
|
|
|
key, err := LoadEncryptionKey(c.Enc.KeyFilepath)
|
|
if err != nil {
|
|
return nil, nil, Error.Wrap(err)
|
|
}
|
|
|
|
encStore := encryption.NewStore()
|
|
encStore.SetDefaultKey(key)
|
|
strms, err := streams.NewStreamStore(segment, c.Client.SegmentSize.Int64(), encStore,
|
|
int(blockSize), storj.Cipher(c.Enc.DataType), c.Client.MaxInlineSize.Int(),
|
|
)
|
|
if err != nil {
|
|
return nil, nil, Error.New("failed to create stream store: %v", err)
|
|
}
|
|
|
|
return kvmetainfo.New(project, m, strms, segment, encStore), strms, nil
|
|
}
|
|
|
|
// GetRedundancyScheme returns the configured redundancy scheme for new uploads
|
|
func (c Config) GetRedundancyScheme() storj.RedundancyScheme {
|
|
return storj.RedundancyScheme{
|
|
Algorithm: storj.ReedSolomon,
|
|
ShareSize: c.RS.ErasureShareSize.Int32(),
|
|
RequiredShares: int16(c.RS.MinThreshold),
|
|
RepairShares: int16(c.RS.RepairThreshold),
|
|
OptimalShares: int16(c.RS.SuccessThreshold),
|
|
TotalShares: int16(c.RS.MaxThreshold),
|
|
}
|
|
}
|
|
|
|
// GetPathCipherSuite returns the cipher suite used for path encryption for bucket objects
|
|
func (c Config) GetPathCipherSuite() storj.CipherSuite {
|
|
return storj.Cipher(c.Enc.PathType).ToCipherSuite()
|
|
}
|
|
|
|
// GetEncryptionScheme returns the configured encryption scheme for new uploads
|
|
// Blocksize should align with the stripe size therefore multiples of stripes
|
|
// should fit in every encryption block. Instead of lettings users configure this
|
|
// multiple value, we hardcode stripesPerBlock as 2 for simplicity.
|
|
func (c Config) GetEncryptionScheme() storj.EncryptionScheme {
|
|
const stripesPerBlock = 2
|
|
return storj.EncryptionScheme{
|
|
Cipher: storj.Cipher(c.Enc.DataType),
|
|
BlockSize: c.GetRedundancyScheme().StripeSize() * stripesPerBlock,
|
|
}
|
|
}
|
|
|
|
// GetSegmentSize returns the segment size set in uplink config
|
|
func (c Config) GetSegmentSize() memory.Size {
|
|
return c.Client.SegmentSize
|
|
}
|
|
|
|
// LoadEncryptionKey loads the encryption key stored in the file pointed by
|
|
// filepath.
|
|
//
|
|
// An error is file is not found or there is an I/O error.
|
|
func LoadEncryptionKey(filepath string) (key *storj.Key, error error) {
|
|
if filepath == "" {
|
|
return &storj.Key{}, nil
|
|
}
|
|
|
|
rawKey, err := ioutil.ReadFile(filepath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return storj.NewKey(rawKey)
|
|
}
|