// 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/spf13/cobra" "go.uber.org/zap" "storj.io/storj/internal/fpath" "storj.io/storj/pkg/cfgstruct" "storj.io/storj/pkg/miniogw" "storj.io/storj/pkg/process" "storj.io/storj/pkg/storage/streams" "storj.io/storj/pkg/storj" ) // GatewayFlags configuration flags type GatewayFlags struct { APIKey string `default:"" help:"the api key to use for the satellite" setup:"true"` GenerateTestCerts bool `default:"false" help:"generate sample TLS certs for Minio GW" setup:"true"` SatelliteAddr string `default:"localhost:7778" help:"the address to use for the satellite" setup:"true"` miniogw.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, } defaultConfDir = fpath.ApplicationDir("storj", "gateway") defaultIdentityDir = fpath.ApplicationDir("storj", "identity", "gateway") setupCfg GatewayFlags runCfg GatewayFlags confDir string identityDir string ) func init() { dirParam := cfgstruct.FindConfigDirParam() if dirParam != "" { defaultConfDir = dirParam } identityDirParam := cfgstruct.FindIdentityDirParam() if identityDirParam != "" { defaultIdentityDir = identityDirParam } rootCmd.PersistentFlags().StringVar(&confDir, "config-dir", defaultConfDir, "main directory for setup configuration") err := rootCmd.PersistentFlags().SetAnnotation("config-dir", "setup", []string{"true"}) if err != nil { zap.S().Error("Failed to set 'setup' annotation for 'config-dir'") } rootCmd.PersistentFlags().StringVar(&identityDir, "identity-dir", defaultIdentityDir, "main directory for gateway identity credentials") err = rootCmd.PersistentFlags().SetAnnotation("identity-dir", "setup", []string{"true"}) if err != nil { zap.S().Error("Failed to set 'setup' annotation for 'config-dir'") } rootCmd.AddCommand(runCmd) rootCmd.AddCommand(setupCmd) cfgstruct.Bind(runCmd.Flags(), &runCfg, cfgstruct.ConfDir(defaultConfDir), cfgstruct.IdentityDir(defaultIdentityDir)) cfgstruct.BindSetup(setupCmd.Flags(), &setupCfg, cfgstruct.ConfDir(defaultConfDir), cfgstruct.IdentityDir(defaultIdentityDir)) } 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{}{ "client.api-key": setupCfg.APIKey, "client.pointer-db-addr": setupCfg.SatelliteAddr, "client.overlay-addr": setupCfg.SatelliteAddr, } 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) { if _, err := runCfg.Identity.Load(); 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("localhost", 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.Metainfo(ctx) 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) } func generateKey() (key string, err error) { var buf [20]byte _, err = rand.Read(buf[:]) if err != nil { return "", err } return base58.Encode(buf[:]), nil } // Metainfo loads the storj.Metainfo // // Temporarily it also returns an instance of streams.Store until we improve // the metainfo and streas implementations. func (flags *GatewayFlags) Metainfo(ctx context.Context) (storj.Metainfo, streams.Store, error) { identity, err := flags.Identity.Load() if err != nil { return nil, nil, err } return flags.GetMetainfo(ctx, identity) } func main() { process.Exec(rootCmd) }