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