github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/datastore/credentials.go (about) 1 package datastore 2 3 import ( 4 "context" 5 "fmt" 6 "sort" 7 "strings" 8 9 "github.com/aws/aws-sdk-go-v2/aws" 10 awsconfig "github.com/aws/aws-sdk-go-v2/config" 11 rdsauth "github.com/aws/aws-sdk-go-v2/feature/rds/auth" 12 "golang.org/x/exp/maps" 13 14 log "github.com/authzed/spicedb/internal/logging" 15 ) 16 17 // CredentialsProvider allows datastore credentials to be retrieved dynamically 18 type CredentialsProvider interface { 19 // Name returns the name of the provider 20 Name() string 21 // IsCleartextToken returns true if the token returned represents a token (rather than a password) that must be sent in cleartext to the datastore, or false otherwise. 22 // This may be used to configure the datastore options to avoid sending a hash of the token instead of its value. 23 // Note that it is always recommended that communication channel be encrypted. 24 IsCleartextToken() bool 25 // Get returns the username and password to use when connecting to the underlying datastore 26 Get(ctx context.Context, dbEndpoint string, dbUser string) (string, string, error) 27 } 28 29 var NoCredentialsProvider CredentialsProvider = nil 30 31 type credentialsProviderBuilderFunc func(ctx context.Context) (CredentialsProvider, error) 32 33 const ( 34 // AWSIAMCredentialProvider generates AWS IAM tokens for authenticating with the datastore (i.e. RDS) 35 AWSIAMCredentialProvider = "aws-iam" 36 ) 37 38 var BuilderForCredentialProvider = map[string]credentialsProviderBuilderFunc{ 39 AWSIAMCredentialProvider: newAWSIAMCredentialsProvider, 40 } 41 42 // CredentialsProviderOptions returns the full set of credential provider names, sorted and quoted into a string. 43 func CredentialsProviderOptions() string { 44 ids := maps.Keys(BuilderForCredentialProvider) 45 sort.Strings(ids) 46 quoted := make([]string, 0, len(ids)) 47 for _, id := range ids { 48 quoted = append(quoted, `"`+id+`"`) 49 } 50 return strings.Join(quoted, ", ") 51 } 52 53 // NewCredentialsProvider create a new CredentialsProvider for the given name 54 // returns an error if no match is found, of if there is a problem creating the given CredentialsProvider 55 func NewCredentialsProvider(ctx context.Context, name string) (CredentialsProvider, error) { 56 builder, ok := BuilderForCredentialProvider[name] 57 if !ok { 58 return nil, fmt.Errorf("unknown credentials provider: %s", name) 59 } 60 return builder(ctx) 61 } 62 63 // AWS IAM provider 64 65 func newAWSIAMCredentialsProvider(ctx context.Context) (CredentialsProvider, error) { 66 awsSdkConfig, err := awsconfig.LoadDefaultConfig(ctx) 67 if err != nil { 68 return nil, err 69 } 70 return &awsIamCredentialsProvider{awsSdkConfig: awsSdkConfig}, nil 71 } 72 73 type awsIamCredentialsProvider struct { 74 awsSdkConfig aws.Config 75 } 76 77 func (d awsIamCredentialsProvider) Name() string { 78 return AWSIAMCredentialProvider 79 } 80 81 func (d awsIamCredentialsProvider) IsCleartextToken() bool { 82 // The AWS IAM token can be of an arbitrary length and must not be hashed or truncated by the datastore driver 83 // See https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html 84 return true 85 } 86 87 func (d awsIamCredentialsProvider) Get(ctx context.Context, dbEndpoint string, dbUser string) (string, string, error) { 88 authToken, err := rdsauth.BuildAuthToken(ctx, dbEndpoint, d.awsSdkConfig.Region, dbUser, d.awsSdkConfig.Credentials) 89 if err != nil { 90 log.Ctx(ctx).Trace().Str("region", d.awsSdkConfig.Region).Str("endpoint", dbEndpoint).Str("user", dbUser).Msg("successfully retrieved IAM auth token for DB") 91 } 92 return dbUser, authToken, err 93 }