storj/cmd/gateway/main.go
JT Olio d70f6e3760
libuplink: remove encryption key from project opening (#1761)
What: This change moves project-level bucket metadata encryption information to the volatile section, because it is unlikely to remain in future releases

Why: Ultimately, the web user interface will allow bucket management (creation, removal, etc), but not object management as that requires an encryption key for sure and we don't want to have users give the satellite their encryption keys.

At a high level, a (*Project) type should map to all of the things you can do inside the web user interface within a project, which by necessity cannot have an encryption key. So, we really don't want an encryption key in the non-volatile section of this library.
2019-04-16 11:29:33 -04:00

267 lines
7.0 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"context"
"crypto/rand"
"fmt"
"net"
"os"
"path/filepath"
base58 "github.com/jbenet/go-base58"
"github.com/minio/cli"
minio "github.com/minio/minio/cmd"
"github.com/spf13/cobra"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/storj/internal/fpath"
libuplink "storj.io/storj/lib/uplink"
"storj.io/storj/pkg/cfgstruct"
"storj.io/storj/pkg/identity"
"storj.io/storj/pkg/miniogw"
"storj.io/storj/pkg/process"
"storj.io/storj/pkg/storj"
"storj.io/storj/uplink"
)
// GatewayFlags configuration flags
type GatewayFlags struct {
Identity identity.Config
GenerateTestCerts bool `default:"false" help:"generate sample TLS certs for Minio GW" setup:"true"`
Server miniogw.ServerConfig
Minio miniogw.MinioConfig
uplink.Config
}
var (
// rootCmd represents the base gateway command when called without any subcommands
rootCmd = &cobra.Command{
Use: "gateway",
Short: "The Storj client-side S3 gateway",
Args: cobra.OnlyValidArgs,
}
setupCmd = &cobra.Command{
Use: "setup",
Short: "Create a gateway config file",
RunE: cmdSetup,
Annotations: map[string]string{"type": "setup"},
}
runCmd = &cobra.Command{
Use: "run",
Short: "Run the S3 gateway",
RunE: cmdRun,
}
setupCfg GatewayFlags
runCfg GatewayFlags
confDir string
identityDir string
isDev bool
)
func init() {
defaultConfDir := fpath.ApplicationDir("storj", "gateway")
defaultIdentityDir := fpath.ApplicationDir("storj", "identity", "gateway")
cfgstruct.SetupFlag(zap.L(), rootCmd, &confDir, "config-dir", defaultConfDir, "main directory for gateway configuration")
cfgstruct.SetupFlag(zap.L(), rootCmd, &identityDir, "identity-dir", defaultIdentityDir, "main directory for gateway identity credentials")
cfgstruct.DevFlag(rootCmd, &isDev, false, "use development and test configuration settings")
rootCmd.AddCommand(runCmd)
rootCmd.AddCommand(setupCmd)
cfgstruct.Bind(runCmd.Flags(), &runCfg, isDev, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.BindSetup(setupCmd.Flags(), &setupCfg, isDev, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
}
func cmdSetup(cmd *cobra.Command, args []string) (err error) {
setupDir, err := filepath.Abs(confDir)
if err != nil {
return err
}
valid, _ := fpath.IsValidSetupDir(setupDir)
if !valid {
return fmt.Errorf("gateway configuration already exists (%v)", setupDir)
}
err = os.MkdirAll(setupDir, 0700)
if err != nil {
return err
}
if setupCfg.GenerateTestCerts {
minioCerts := filepath.Join(setupDir, "minio", "certs")
if err := os.MkdirAll(minioCerts, 0744); err != nil {
return err
}
if err := os.Link(setupCfg.Identity.CertPath, filepath.Join(minioCerts, "public.crt")); err != nil {
return err
}
if err := os.Link(setupCfg.Identity.KeyPath, filepath.Join(minioCerts, "private.key")); err != nil {
return err
}
}
overrides := map[string]interface{}{}
accessKeyFlag := cmd.Flag("minio.access-key")
if !accessKeyFlag.Changed {
accessKey, err := generateKey()
if err != nil {
return err
}
overrides[accessKeyFlag.Name] = accessKey
}
secretKeyFlag := cmd.Flag("minio.secret-key")
if !secretKeyFlag.Changed {
secretKey, err := generateKey()
if err != nil {
return err
}
overrides[secretKeyFlag.Name] = secretKey
}
return process.SaveConfigWithAllDefaults(cmd.Flags(), filepath.Join(setupDir, "config.yaml"), overrides)
}
func cmdRun(cmd *cobra.Command, args []string) (err error) {
identity, err := runCfg.Identity.Load()
if err != nil {
zap.S().Fatal(err)
}
address := runCfg.Server.Address
host, port, err := net.SplitHostPort(address)
if err != nil {
return err
}
if host == "" {
address = net.JoinHostPort("127.0.0.1", port)
}
fmt.Printf("Starting Storj S3-compatible gateway!\n\n")
fmt.Printf("Endpoint: %s\n", address)
fmt.Printf("Access key: %s\n", runCfg.Minio.AccessKey)
fmt.Printf("Secret key: %s\n", runCfg.Minio.SecretKey)
ctx := process.Ctx(cmd)
metainfo, _, err := runCfg.GetMetainfo(ctx, identity)
if err != nil {
return err
}
if err := process.InitMetricsWithCertPath(ctx, nil, runCfg.Identity.CertPath); err != nil {
zap.S().Error("Failed to initialize telemetry batcher: ", err)
}
_, err = metainfo.ListBuckets(ctx, storj.BucketListOptions{Direction: storj.After})
if err != nil {
return fmt.Errorf("Failed to contact Satellite.\n"+
"Perhaps your configuration is invalid?\n%s", err)
}
return runCfg.Run(ctx, identity)
}
func generateKey() (key string, err error) {
var buf [20]byte
_, err = rand.Read(buf[:])
if err != nil {
return "", err
}
return base58.Encode(buf[:]), nil
}
// Run starts a Minio Gateway given proper config
func (flags GatewayFlags) Run(ctx context.Context, identity *identity.FullIdentity) (err error) {
err = minio.RegisterGatewayCommand(cli.Command{
Name: "storj",
Usage: "Storj",
Action: func(cliCtx *cli.Context) error {
return flags.action(ctx, cliCtx, identity)
},
HideHelpCommand: true,
})
if err != nil {
return err
}
// TODO(jt): Surely there is a better way. This is so upsetting
err = os.Setenv("MINIO_ACCESS_KEY", flags.Minio.AccessKey)
if err != nil {
return err
}
err = os.Setenv("MINIO_SECRET_KEY", flags.Minio.SecretKey)
if err != nil {
return err
}
minio.Main([]string{"storj", "gateway", "storj",
"--address", flags.Server.Address, "--config-dir", flags.Minio.Dir, "--quiet"})
return errs.New("unexpected minio exit")
}
func (flags GatewayFlags) action(ctx context.Context, cliCtx *cli.Context, identity *identity.FullIdentity) (err error) {
gw, err := flags.NewGateway(ctx, identity)
if err != nil {
return err
}
minio.StartGateway(cliCtx, miniogw.Logging(gw, zap.L()))
return errs.New("unexpected minio exit")
}
// NewGateway creates a new minio Gateway
func (flags GatewayFlags) NewGateway(ctx context.Context, ident *identity.FullIdentity) (gw minio.Gateway, err error) {
cfg := libuplink.Config{}
cfg.Volatile.TLS = struct {
SkipPeerCAWhitelist bool
PeerCAWhitelistPath string
}{
SkipPeerCAWhitelist: !flags.TLS.UsePeerCAWhitelist,
PeerCAWhitelistPath: flags.TLS.PeerCAWhitelistPath,
}
cfg.Volatile.UseIdentity = ident
cfg.Volatile.MaxInlineSize = flags.Client.MaxInlineSize
cfg.Volatile.MaxMemory = flags.RS.MaxBufferMem
uplink, err := libuplink.NewUplink(ctx, &cfg)
if err != nil {
return nil, err
}
apiKey, err := libuplink.ParseAPIKey(flags.Client.APIKey)
if err != nil {
return nil, err
}
encKey := new(storj.Key)
copy(encKey[:], flags.Enc.Key)
var opts libuplink.ProjectOptions
opts.Volatile.EncryptionKey = encKey
project, err := uplink.OpenProject(ctx, flags.Client.SatelliteAddr, apiKey, &opts)
if err != nil {
return nil, err
}
return miniogw.NewStorjGateway(
project,
encKey,
storj.Cipher(flags.Enc.PathType).ToCipherSuite(),
flags.GetEncryptionScheme().ToEncryptionParameters(),
flags.GetRedundancyScheme(),
flags.Client.SegmentSize,
), nil
}
func main() {
process.Exec(rootCmd)
}