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  }