diff --git a/cmd/uplink/cmd_share.go b/cmd/uplink/cmd_share.go index 4b5fddfd7..c38e8ee74 100644 --- a/cmd/uplink/cmd_share.go +++ b/cmd/uplink/cmd_share.go @@ -31,6 +31,7 @@ type cmdShare struct { register bool url bool dns string + tls bool authService string caCert string public bool @@ -52,7 +53,10 @@ func (c *cmdShare) Setup(params clingy.Parameters) { c.url = params.Flag("url", "If true, returns a url for the shared path. implies --register and --public", false, clingy.Transform(strconv.ParseBool), clingy.Boolean, ).(bool) - c.dns = params.Flag("dns", "Specify your custom hostname. if set, returns dns settings for web hosting. implies --register and --public", "").(string) + c.dns = params.Flag("dns", "Specify your custom domain. if set, returns DNS settings for web hosting. implies --register and --public", "").(string) + c.tls = params.Flag("tls", "Return an additional TXT record to secure your domain (Pro Accounts only.) implies --dns and --public", false, + clingy.Transform(strconv.ParseBool), clingy.Boolean, + ).(bool) c.authService = params.Flag("auth-service", "URL for shared auth service", "https://auth.storjshare.io").(string) c.public = params.Flag("public", "If true, the access will be public. --dns and --url override this", false, clingy.Transform(strconv.ParseBool), clingy.Boolean, @@ -77,7 +81,11 @@ func (c *cmdShare) Execute(ctx context.Context) error { return err } - c.public = c.public || c.url || c.dns != "" + if c.tls && c.dns == "" { + return errs.New("You must specify your custom domain with --dns") + } + + c.public = c.public || c.url || c.dns != "" || c.tls if c.public { c.register = true @@ -136,7 +144,7 @@ func (c *cmdShare) Execute(ctx context.Context) error { return errs.New("will only generate DNS entries with readonly restrictions") } - err = createDNS(ctx, credentials.AccessKeyID, c.ap.prefixes, c.baseURL, c.dns) + err = createDNS(ctx, credentials.AccessKeyID, c.ap.prefixes, c.baseURL, c.dns, c.tls) if err != nil { return err } @@ -250,7 +258,7 @@ func createURL(ctx context.Context, accessKeyID string, prefixes []uplink.ShareP } // Creates dns record info for allowed path prefixes. -func createDNS(ctx context.Context, accessKey string, prefixes []uplink.SharePrefix, baseURL, dns string) (err error) { +func createDNS(ctx context.Context, accessKey string, prefixes []uplink.SharePrefix, baseURL, dns string, tls bool) (err error) { if len(prefixes) == 0 { return errs.New("need at least a bucket to create DNS records") } @@ -277,6 +285,9 @@ func createDNS(ctx context.Context, accessKey string, prefixes []uplink.SharePre fmt.Fprintf(clingy.Stdout(ctx), "%s \tIN\tCNAME\t%s.\n", dns, CNAME.Host) fmt.Fprintln(clingy.Stdout(ctx), printStorjRoot) fmt.Fprintf(clingy.Stdout(ctx), "txt-%s\tIN\tTXT \tstorj-access:%s\n", dns, accessKey) + if tls { + fmt.Fprintf(clingy.Stdout(ctx), "txt-%s\tIN\tTXT \tstorj-tls:true\n", dns) + } return nil } diff --git a/cmd/uplink/cmd_share_test.go b/cmd/uplink/cmd_share_test.go index 7ef9a56cf..3e22d9550 100644 --- a/cmd/uplink/cmd_share_test.go +++ b/cmd/uplink/cmd_share_test.go @@ -4,13 +4,24 @@ package main import ( + "context" + "net" "testing" "github.com/stretchr/testify/require" + "storj.io/common/grant" + "storj.io/common/macaroon" + "storj.io/common/pb" + "storj.io/common/storj" + "storj.io/common/testcontext" + "storj.io/drpc/drpcmux" + "storj.io/drpc/drpcserver" "storj.io/storj/cmd/uplink/ultest" ) +const testAPIKey = "13Yqe3oHi5dcnGhMu2ru3cmePC9iEYv6nDrYMbLRh4wre1KtVA9SFwLNAuuvWwc43b9swRsrfsnrbuTHQ6TJKVt4LjGnaARN9PhxJEu" + func TestShare(t *testing.T) { t.Run("share requires prefix", func(t *testing.T) { ultest.Setup(commands).Fail(t, "share") @@ -134,4 +145,84 @@ func TestShare(t *testing.T) { Access : * `) }) + + t.Run("share access with --dns and --tls", func(t *testing.T) { + ctx := testcontext.New(t) + defer ctx.Cleanup() + + state := ultest.Setup(commands) + + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + + serverCtx, serverCancel := context.WithCancel(ctx) + defer serverCancel() + + ctx.Go(func() error { + mux := drpcmux.New() + if err := pb.DRPCRegisterEdgeAuth(mux, &DRPCAuthMockServer{}); err != nil { + return err + } + return drpcserver.New(mux).Serve(serverCtx, listener) + }) + + authAddr := "insecure://" + listener.Addr().String() + + apiKey, err := macaroon.ParseAPIKey(testAPIKey) + require.NoError(t, err) + + encAccess := grant.NewEncryptionAccessWithDefaultKey(&storj.Key{}) + grantAccess := grant.Access{ + SatelliteAddress: "12EayRS2V1kEsWESU9QMRseFhdxYxKicsiFmxrsLZHeLUtdps3S@us1.storj.io:7777", + APIKey: apiKey, + EncAccess: encAccess, + } + + access, err := grantAccess.Serialize() + require.NoError(t, err) + + expected := ` + Sharing access to satellite * + =========== ACCESS RESTRICTIONS ========================================================== + Download : Allowed + Upload : Disallowed + Lists : Allowed + Deletes : Disallowed + NotBefore : No restriction + NotAfter : No restriction + Paths : sj://some/prefix + =========== SERIALIZED ACCESS WITH THE ABOVE RESTRICTIONS TO SHARE WITH OTHERS =========== + Access : * + ========== GATEWAY CREDENTIALS =========================================================== + Access Key ID: accesskeyid + Secret Key : secretkey + Endpoint : endpoint + Public Access: true + =========== DNS INFO ===================================================================== + Remember to update the $ORIGIN with your domain name. You may also change the $TTL. + $ORIGIN example.com. + $TTL 3600 + test.com IN CNAME link.storjshare.io. + txt-test.com IN TXT storj-root:some/prefix + txt-test.com IN TXT storj-access:accesskeyid + ` + + state.Succeed(t, "share", "--access", access, "--not-after=none", "--dns", "test.com", "--auth-service", authAddr, "sj://some/prefix").RequireStdoutGlob(t, expected) + + expected += "\ntxt-test.com IN TXT storj-tls:true\n" + + state.Succeed(t, "share", "--access", access, "--not-after=none", "--dns", "test.com", "--tls", "--auth-service", authAddr, "sj://some/prefix").RequireStdoutGlob(t, expected) + }) +} + +type DRPCAuthMockServer struct { + pb.DRPCEdgeAuthServer +} + +func (g *DRPCAuthMockServer) RegisterAccess(context.Context, *pb.EdgeRegisterAccessRequest) (*pb.EdgeRegisterAccessResponse, error) { + return &pb.EdgeRegisterAccessResponse{ + AccessKeyId: "accesskeyid", + SecretKey: "secretkey", + Endpoint: "endpoint", + }, nil }