feat(postgres): add iam roles anywhere auth profile (#3604)
Signed-off-by: Samantha Coyle <sam@diagrid.io> Co-authored-by: Yaron Schneider <schneider.yaron@live.com>
This commit is contained in:
parent
1e095ed25a
commit
72c92fb1fe
|
@ -38,7 +38,7 @@ func ParseBuiltinAuthenticationProfile(bi BuiltinAuthenticationProfile, componen
|
|||
metadataPtr[j] = &profile.Metadata[j]
|
||||
}
|
||||
|
||||
if componentTitle == "Apache Kafka" {
|
||||
if componentTitle == "Apache Kafka" || strings.ToLower(componentTitle) == "postgresql" {
|
||||
removeRequiredOnSomeAWSFields(&metadataPtr)
|
||||
}
|
||||
|
||||
|
@ -55,17 +55,17 @@ func ParseBuiltinAuthenticationProfile(bi BuiltinAuthenticationProfile, componen
|
|||
// Note: We must apply the removal of deprecated fields after the merge!!
|
||||
|
||||
// Here, we remove some deprecated fields as we support the transition to a new auth profile
|
||||
if profile.Title == "AWS: Assume specific IAM Role" && componentTitle == "Apache Kafka" {
|
||||
if profile.Title == "AWS: Assume IAM Role" && componentTitle == "Apache Kafka" || profile.Title == "AWS: Assume IAM Role" && strings.ToLower(componentTitle) == "postgresql" {
|
||||
merged = removeSomeDeprecatedFieldsOnUnrelatedAuthProfiles(merged)
|
||||
}
|
||||
|
||||
// Here, there are no metadata fields that need deprecating
|
||||
if profile.Title == "AWS: Credentials from Environment Variables" && componentTitle == "Apache Kafka" {
|
||||
if profile.Title == "AWS: Credentials from Environment Variables" && componentTitle == "Apache Kafka" || profile.Title == "AWS: Credentials from Environment Variables" && strings.ToLower(componentTitle) == "postgresql" {
|
||||
merged = removeAllDeprecatedFieldsOnUnrelatedAuthProfiles(merged)
|
||||
}
|
||||
|
||||
// Here, this is a new auth profile, so rm all deprecating fields as unrelated.
|
||||
if profile.Title == "AWS: IAM Roles Anywhere" && componentTitle == "Apache Kafka" {
|
||||
if profile.Title == "AWS: IAM Roles Anywhere" && componentTitle == "Apache Kafka" || profile.Title == "AWS: IAM Roles Anywhere" && strings.ToLower(componentTitle) == "postgresql" {
|
||||
merged = removeAllDeprecatedFieldsOnUnrelatedAuthProfiles(merged)
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ func removeSomeDeprecatedFieldsOnUnrelatedAuthProfiles(metadata []Metadata) []Me
|
|||
filteredMetadata := []Metadata{}
|
||||
|
||||
for _, field := range metadata {
|
||||
if field.Name == "awsAccessKey" || field.Name == "awsSecretKey" || field.Name == "awsSessionToken" {
|
||||
if field.Name == "awsAccessKey" || field.Name == "awsSecretKey" || field.Name == "awsSessionToken" || field.Name == "awsRegion" {
|
||||
continue
|
||||
} else {
|
||||
filteredMetadata = append(filteredMetadata, field)
|
||||
|
|
|
@ -35,7 +35,7 @@ builtinAuthenticationProfiles:
|
|||
description: |
|
||||
This maintains backwards compatibility with existing fields.
|
||||
It will be deprecated as of Dapr 1.17. Use 'region' instead.
|
||||
The AWS Region where the AWS Relational Database Service is deployed to.
|
||||
The AWS Region where the AWS service is deployed to.
|
||||
example: '"us-east-1"'
|
||||
- name: awsAccessKey
|
||||
type: string
|
||||
|
@ -82,7 +82,7 @@ builtinAuthenticationProfiles:
|
|||
If both fields are set, then 'sessionName' value will be used.
|
||||
Represents the session name for assuming a role.
|
||||
example: '"MyAppSession"'
|
||||
default: '"MSKSASLDefaultSession"'
|
||||
default: '"DaprDefaultSession"'
|
||||
authenticationProfiles:
|
||||
- title: "OIDC Authentication"
|
||||
description: |
|
||||
|
|
|
@ -28,7 +28,7 @@ const (
|
|||
|
||||
type psqlMetadata struct {
|
||||
pgauth.PostgresAuthMetadata `mapstructure:",squash"`
|
||||
aws.AWSIAM `mapstructure:",squash"`
|
||||
aws.DeprecatedPostgresIAM `mapstructure:",squash"`
|
||||
Timeout time.Duration `mapstructure:"timeout" mapstructurealiases:"timeoutInSeconds"`
|
||||
}
|
||||
|
||||
|
|
|
@ -56,25 +56,31 @@ builtinAuthenticationProfiles:
|
|||
example: |
|
||||
"host=mydb.postgres.database.aws.com user=myapplication port=5432 dbname=dapr_test sslmode=require"
|
||||
type: string
|
||||
- name: awsRegion
|
||||
type: string
|
||||
required: true
|
||||
description: |
|
||||
The AWS Region where the AWS Relational Database Service is deployed to.
|
||||
example: '"us-east-1"'
|
||||
- name: awsAccessKey
|
||||
type: string
|
||||
required: true
|
||||
required: false
|
||||
description: |
|
||||
Deprecated as of Dapr 1.17. Use 'accessKey' instead if using AWS IAM.
|
||||
If both fields are set, then 'accessKey' value will be used.
|
||||
AWS access key associated with an IAM account.
|
||||
example: '"AKIAIOSFODNN7EXAMPLE"'
|
||||
- name: awsSecretKey
|
||||
type: string
|
||||
required: true
|
||||
required: false
|
||||
sensitive: true
|
||||
description: |
|
||||
Deprecated as of Dapr 1.17. Use 'secretKey' instead if using AWS IAM.
|
||||
If both fields are set, then 'secretKey' value will be used.
|
||||
The secret key associated with the access key.
|
||||
example: '"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"'
|
||||
- name: awsRegion
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
This maintains backwards compatibility with existing fields.
|
||||
It will be deprecated as of Dapr 1.17. Use 'region' instead.
|
||||
The AWS Region where the AWS service is deployed to.
|
||||
example: '"us-east-1"'
|
||||
authenticationProfiles:
|
||||
- title: "Connection string"
|
||||
description: "Authenticate using a Connection String"
|
||||
|
|
|
@ -26,6 +26,8 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"github.com/dapr/components-contrib/bindings"
|
||||
awsAuth "github.com/dapr/components-contrib/common/authentication/aws"
|
||||
pgauth "github.com/dapr/components-contrib/common/authentication/postgresql"
|
||||
"github.com/dapr/components-contrib/metadata"
|
||||
"github.com/dapr/kit/logger"
|
||||
)
|
||||
|
@ -45,6 +47,11 @@ type Postgres struct {
|
|||
logger logger.Logger
|
||||
db *pgxpool.Pool
|
||||
closed atomic.Bool
|
||||
|
||||
enableAzureAD bool
|
||||
enableAWSIAM bool
|
||||
|
||||
awsAuthProvider awsAuth.Provider
|
||||
}
|
||||
|
||||
// NewPostgres returns a new PostgreSQL output binding.
|
||||
|
@ -59,16 +66,34 @@ func (p *Postgres) Init(ctx context.Context, meta bindings.Metadata) error {
|
|||
if p.closed.Load() {
|
||||
return errors.New("cannot initialize a previously-closed component")
|
||||
}
|
||||
|
||||
opts := pgauth.InitWithMetadataOpts{
|
||||
AzureADEnabled: p.enableAzureAD,
|
||||
AWSIAMEnabled: p.enableAWSIAM,
|
||||
}
|
||||
m := psqlMetadata{}
|
||||
err := m.InitWithMetadata(meta.Properties)
|
||||
if err := m.InitWithMetadata(meta.Properties); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
poolConfig, err := m.GetPgxPoolConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
poolConfig, err := m.GetPgxPoolConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
if opts.AWSIAMEnabled && m.UseAWSIAM {
|
||||
opts, validateErr := m.BuildAwsIamOptions(p.logger, meta.Properties)
|
||||
if validateErr != nil {
|
||||
return fmt.Errorf("failed to validate AWS IAM authentication fields: %w", validateErr)
|
||||
}
|
||||
|
||||
var provider awsAuth.Provider
|
||||
provider, err = awsAuth.NewProvider(ctx, *opts, awsAuth.GetConfig(*opts))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.awsAuthProvider = provider
|
||||
p.awsAuthProvider.UpdatePostgres(ctx, poolConfig)
|
||||
}
|
||||
|
||||
// This context doesn't control the lifetime of the connection pool, and is
|
||||
|
@ -186,7 +211,11 @@ func (p *Postgres) Close() error {
|
|||
}
|
||||
p.db = nil
|
||||
|
||||
return nil
|
||||
errs := make([]error, 1)
|
||||
if p.awsAuthProvider != nil {
|
||||
errs[0] = p.awsAuthProvider.Close()
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (p *Postgres) query(ctx context.Context, sql string, args ...any) (result []byte, err error) {
|
||||
|
|
|
@ -15,16 +15,8 @@ package aws
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
v2creds "github.com/aws/aws-sdk-go-v2/credentials"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/rds/auth"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"github.com/dapr/kit/logger"
|
||||
|
@ -34,16 +26,6 @@ type EnvironmentSettings struct {
|
|||
Metadata map[string]string
|
||||
}
|
||||
|
||||
type AWSIAM struct {
|
||||
// Ignored by metadata parser because included in built-in authentication profile
|
||||
// Access key to use for accessing PostgreSQL.
|
||||
AWSAccessKey string `json:"awsAccessKey" mapstructure:"awsAccessKey"`
|
||||
// Secret key to use for accessing PostgreSQL.
|
||||
AWSSecretKey string `json:"awsSecretKey" mapstructure:"awsSecretKey"`
|
||||
// AWS region in which PostgreSQL is deployed.
|
||||
AWSRegion string `json:"awsRegion" mapstructure:"awsRegion"`
|
||||
}
|
||||
|
||||
// TODO: Delete in Dapr 1.17 so we can move all IAM fields to use the defaults of:
|
||||
// accessKey and secretKey and region as noted in the docs, and Options struct above.
|
||||
type DeprecatedKafkaIAM struct {
|
||||
|
@ -55,14 +37,6 @@ type DeprecatedKafkaIAM struct {
|
|||
StsSessionName string `json:"awsStsSessionName" mapstructure:"awsStsSessionName"`
|
||||
}
|
||||
|
||||
type AWSIAMAuthOptions struct {
|
||||
PoolConfig *pgxpool.Config `json:"poolConfig" mapstructure:"poolConfig"`
|
||||
ConnectionString string `json:"connectionString" mapstructure:"connectionString"`
|
||||
Region string `json:"region" mapstructure:"region"`
|
||||
AccessKey string `json:"accessKey" mapstructure:"accessKey"`
|
||||
SecretKey string `json:"secretKey" mapstructure:"secretKey"`
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Logger logger.Logger
|
||||
Properties map[string]string
|
||||
|
@ -75,11 +49,20 @@ type Options struct {
|
|||
Region string `json:"region" mapstructure:"region" mapstructurealiases:"awsRegion"`
|
||||
AccessKey string `json:"accessKey" mapstructure:"accessKey"`
|
||||
SecretKey string `json:"secretKey" mapstructure:"secretKey"`
|
||||
SessionName string `mapstructure:"sessionName"`
|
||||
AssumeRoleARN string `mapstructure:"assumeRoleArn"`
|
||||
SessionName string `json:"sessionName" mapstructure:"sessionName"`
|
||||
AssumeRoleARN string `json:"assumeRoleArn" mapstructure:"assumeRoleArn"`
|
||||
SessionToken string `json:"sessionToken" mapstructure:"sessionToken"`
|
||||
|
||||
Endpoint string
|
||||
SessionToken string
|
||||
Endpoint string
|
||||
}
|
||||
|
||||
// TODO: Delete in Dapr 1.17 so we can move all IAM fields to use the defaults of:
|
||||
// accessKey and secretKey and region as noted in the docs, and Options struct above.
|
||||
type DeprecatedPostgresIAM struct {
|
||||
// Access key to use for accessing PostgreSQL.
|
||||
AccessKey string `json:"awsAccessKey" mapstructure:"awsAccessKey"`
|
||||
// Secret key to use for accessing PostgreSQL.
|
||||
SecretKey string `json:"awsSecretKey" mapstructure:"awsSecretKey"`
|
||||
}
|
||||
|
||||
func GetConfig(opts Options) *aws.Config {
|
||||
|
@ -106,9 +89,14 @@ type Provider interface {
|
|||
ParameterStore() *ParameterStoreClients
|
||||
Kinesis() *KinesisClients
|
||||
Ses() *SesClients
|
||||
|
||||
Kafka(KafkaOptions) (*KafkaClients, error)
|
||||
|
||||
// Postgres is an outlier to the others in the sense that we can update only it's config,
|
||||
// as we use a max connection time of 8 minutes.
|
||||
// This means that we can just update the config session credentials,
|
||||
// and then in 8 minutes it will update to a new session automatically for us.
|
||||
UpdatePostgres(context.Context, *pgxpool.Config)
|
||||
|
||||
Close() error
|
||||
}
|
||||
|
||||
|
@ -128,69 +116,6 @@ func NewEnvironmentSettings(md map[string]string) (EnvironmentSettings, error) {
|
|||
return es, nil
|
||||
}
|
||||
|
||||
func (opts *Options) GetAccessToken(ctx context.Context) (string, error) {
|
||||
dbEndpoint := opts.PoolConfig.ConnConfig.Host + ":" + strconv.Itoa(int(opts.PoolConfig.ConnConfig.Port))
|
||||
var authenticationToken string
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.Connecting.Go.html
|
||||
// Default to load default config through aws credentials file (~/.aws/credentials)
|
||||
awsCfg, err := config.LoadDefaultConfig(ctx)
|
||||
// Note: in the event of an error with invalid config or failed to load config,
|
||||
// then we fall back to using the access key and secret key.
|
||||
switch {
|
||||
case errors.Is(err, config.SharedConfigAssumeRoleError{}.Err),
|
||||
errors.Is(err, config.SharedConfigLoadError{}.Err),
|
||||
errors.Is(err, config.SharedConfigProfileNotExistError{}.Err):
|
||||
// Validate if access key and secret access key are provided
|
||||
if opts.AccessKey == "" || opts.SecretKey == "" {
|
||||
return "", fmt.Errorf("failed to load default configuration for AWS using accessKey and secretKey: %w", err)
|
||||
}
|
||||
|
||||
// Set credentials explicitly
|
||||
awsCfg := v2creds.NewStaticCredentialsProvider(opts.AccessKey, opts.SecretKey, "")
|
||||
authenticationToken, err = auth.BuildAuthToken(
|
||||
ctx, dbEndpoint, opts.Region, opts.PoolConfig.ConnConfig.User, awsCfg)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create AWS authentication token: %w", err)
|
||||
}
|
||||
|
||||
return authenticationToken, nil
|
||||
case err != nil:
|
||||
return "", errors.New("failed to load default AWS authentication configuration")
|
||||
}
|
||||
|
||||
authenticationToken, err = auth.BuildAuthToken(
|
||||
ctx, dbEndpoint, opts.Region, opts.PoolConfig.ConnConfig.User, awsCfg.Credentials)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create AWS authentication token: %w", err)
|
||||
}
|
||||
|
||||
return authenticationToken, nil
|
||||
}
|
||||
|
||||
func (opts *Options) InitiateAWSIAMAuth() error {
|
||||
// Set max connection lifetime to 8 minutes in postgres connection pool configuration.
|
||||
// Note: this will refresh connections before the 15 min expiration on the IAM AWS auth token,
|
||||
// while leveraging the BeforeConnect hook to recreate the token in time dynamically.
|
||||
opts.PoolConfig.MaxConnLifetime = time.Minute * 8
|
||||
|
||||
// Setup connection pool config needed for AWS IAM authentication
|
||||
opts.PoolConfig.BeforeConnect = func(ctx context.Context, pgConfig *pgx.ConnConfig) error {
|
||||
// Manually reset auth token with aws and reset the config password using the new iam token
|
||||
pwd, errGetAccessToken := opts.GetAccessToken(ctx)
|
||||
if errGetAccessToken != nil {
|
||||
return fmt.Errorf("failed to refresh access token for iam authentication with PostgreSQL: %w", errGetAccessToken)
|
||||
}
|
||||
|
||||
pgConfig.Password = pwd
|
||||
opts.PoolConfig.ConnConfig.Password = pwd
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Coalesce is a helper function to return the first non-empty string from the inputs
|
||||
// This helps us to migrate away from the deprecated duplicate aws auth profile metadata fields in Dapr 1.17.
|
||||
func Coalesce(values ...string) string {
|
||||
|
|
|
@ -17,15 +17,22 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
awsv2 "github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
v2creds "github.com/aws/aws-sdk-go-v2/credentials"
|
||||
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/rds/auth"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"github.com/dapr/kit/logger"
|
||||
)
|
||||
|
@ -227,6 +234,104 @@ func (a *StaticAuth) Ses() *SesClients {
|
|||
return a.clients.ses
|
||||
}
|
||||
|
||||
func (a *StaticAuth) UpdatePostgres(ctx context.Context, poolConfig *pgxpool.Config) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
// Set max connection lifetime to 8 minutes in postgres connection pool configuration.
|
||||
// Note: this will refresh connections before the 15 min expiration on the IAM AWS auth token,
|
||||
// while leveraging the BeforeConnect hook to recreate the token in time dynamically.
|
||||
poolConfig.MaxConnLifetime = time.Minute * 8
|
||||
|
||||
// Setup connection pool config needed for AWS IAM authentication
|
||||
poolConfig.BeforeConnect = func(ctx context.Context, pgConfig *pgx.ConnConfig) error {
|
||||
// Manually reset auth token with aws and reset the config password using the new iam token
|
||||
pwd, err := a.getDatabaseToken(ctx, poolConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database token: %w", err)
|
||||
}
|
||||
pgConfig.Password = pwd
|
||||
poolConfig.ConnConfig.Password = pwd
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.Connecting.Go.html
|
||||
func (a *StaticAuth) getDatabaseToken(ctx context.Context, poolConfig *pgxpool.Config) (string, error) {
|
||||
dbEndpoint := poolConfig.ConnConfig.Host + ":" + strconv.Itoa(int(poolConfig.ConnConfig.Port))
|
||||
|
||||
// First, check if there are credentials set explicitly with accesskey and secretkey
|
||||
var creds credentials.Value
|
||||
if a.session != nil {
|
||||
var err error
|
||||
creds, err = a.session.Config.Credentials.Get()
|
||||
if err != nil {
|
||||
a.logger.Infof("failed to get access key and secret key, will fallback to reading the default AWS credentials file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if creds.AccessKeyID != "" && creds.SecretAccessKey != "" {
|
||||
creds, err := a.session.Config.Credentials.Get()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to retrieve session credentials: %w", err)
|
||||
}
|
||||
awsCfg := v2creds.NewStaticCredentialsProvider(creds.AccessKeyID, creds.SecretAccessKey, creds.SessionToken)
|
||||
authenticationToken, err := auth.BuildAuthToken(
|
||||
ctx, dbEndpoint, *a.region, poolConfig.ConnConfig.User, awsCfg)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create AWS authentication token: %w", err)
|
||||
}
|
||||
|
||||
return authenticationToken, nil
|
||||
}
|
||||
|
||||
// Second, check if we are assuming a role instead
|
||||
if a.assumeRoleARN != nil {
|
||||
awsCfg, err := config.LoadDefaultConfig(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to load default AWS authentication configuration %w", err)
|
||||
}
|
||||
stsClient := sts.NewFromConfig(awsCfg)
|
||||
|
||||
assumeRoleCfg, err := config.LoadDefaultConfig(ctx,
|
||||
config.WithRegion(*a.region),
|
||||
config.WithCredentialsProvider(
|
||||
awsv2.NewCredentialsCache(
|
||||
stscreds.NewAssumeRoleProvider(stsClient, *a.assumeRoleARN, func(aro *stscreds.AssumeRoleOptions) {
|
||||
if a.sessionName != "" {
|
||||
aro.RoleSessionName = a.sessionName
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to assume aws role %w", err)
|
||||
}
|
||||
|
||||
authenticationToken, err := auth.BuildAuthToken(
|
||||
ctx, dbEndpoint, *a.region, poolConfig.ConnConfig.User, assumeRoleCfg.Credentials)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create AWS authentication token: %w", err)
|
||||
}
|
||||
return authenticationToken, nil
|
||||
}
|
||||
|
||||
// Lastly, and by default, just use the default aws configuration
|
||||
awsCfg, err := config.LoadDefaultConfig(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to load default AWS authentication configuration %w", err)
|
||||
}
|
||||
|
||||
authenticationToken, err := auth.BuildAuthToken(ctx, dbEndpoint, *a.region, poolConfig.ConnConfig.User, awsCfg.Credentials)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create AWS authentication token: %w", err)
|
||||
}
|
||||
|
||||
return authenticationToken, nil
|
||||
}
|
||||
|
||||
func (a *StaticAuth) Kafka(opts KafkaOptions) (*KafkaClients, error) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
|
|
@ -22,9 +22,14 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
awsv2 "github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
v2creds "github.com/aws/aws-sdk-go-v2/credentials"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/rds/auth"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/arn"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
|
@ -34,6 +39,12 @@ import (
|
|||
"github.com/aws/rolesanywhere-credential-helper/rolesanywhere"
|
||||
"github.com/aws/rolesanywhere-credential-helper/rolesanywhere/rolesanywhereiface"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
|
||||
cryptopem "github.com/dapr/kit/crypto/pem"
|
||||
spiffecontext "github.com/dapr/kit/crypto/spiffe/context"
|
||||
"github.com/dapr/kit/logger"
|
||||
|
@ -72,6 +83,7 @@ type x509 struct {
|
|||
trustProfileArn *string
|
||||
trustAnchorArn *string
|
||||
assumeRoleArn *string
|
||||
sessionName string
|
||||
}
|
||||
|
||||
func newX509(ctx context.Context, opts Options, cfg *aws.Config) (*x509, error) {
|
||||
|
@ -296,6 +308,105 @@ func (a *x509) Ses() *SesClients {
|
|||
return a.clients.ses
|
||||
}
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.Connecting.Go.html
|
||||
func (a *x509) getDatabaseToken(ctx context.Context, poolConfig *pgxpool.Config) (string, error) {
|
||||
dbEndpoint := poolConfig.ConnConfig.Host + ":" + strconv.Itoa(int(poolConfig.ConnConfig.Port))
|
||||
|
||||
// First, check if there are credentials set explicitly with accesskey and secretkey
|
||||
var creds credentials.Value
|
||||
if a.session != nil {
|
||||
var err error
|
||||
creds, err = a.session.Config.Credentials.Get()
|
||||
if err != nil {
|
||||
a.logger.Infof("failed to get access key and secret key, will fallback to reading the default AWS credentials file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if creds.AccessKeyID != "" && creds.SecretAccessKey != "" {
|
||||
creds, err := a.session.Config.Credentials.Get()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to retrieve session credentials: %w", err)
|
||||
}
|
||||
awsCfg := v2creds.NewStaticCredentialsProvider(creds.AccessKeyID, creds.SecretAccessKey, creds.SessionToken)
|
||||
authenticationToken, err := auth.BuildAuthToken(
|
||||
ctx, dbEndpoint, *a.region, poolConfig.ConnConfig.User, awsCfg)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create AWS authentication token: %w", err)
|
||||
}
|
||||
|
||||
return authenticationToken, nil
|
||||
}
|
||||
|
||||
// Second, check if we are assuming a role instead
|
||||
if a.assumeRoleArn != nil {
|
||||
awsCfg, err := config.LoadDefaultConfig(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to load default AWS authentication configuration %w", err)
|
||||
}
|
||||
stsClient := sts.NewFromConfig(awsCfg)
|
||||
|
||||
assumeRoleCfg, err := config.LoadDefaultConfig(ctx,
|
||||
config.WithRegion(*a.region),
|
||||
config.WithCredentialsProvider(
|
||||
awsv2.NewCredentialsCache(
|
||||
stscreds.NewAssumeRoleProvider(stsClient, *a.assumeRoleArn, func(aro *stscreds.AssumeRoleOptions) {
|
||||
if a.sessionName != "" {
|
||||
aro.RoleSessionName = a.sessionName
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to assume aws role %w", err)
|
||||
}
|
||||
|
||||
authenticationToken, err := auth.BuildAuthToken(
|
||||
ctx, dbEndpoint, *a.region, poolConfig.ConnConfig.User, assumeRoleCfg.Credentials)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create AWS authentication token: %w", err)
|
||||
}
|
||||
return authenticationToken, nil
|
||||
}
|
||||
|
||||
// Lastly, and by default, just use the default aws configuration
|
||||
awsCfg, err := config.LoadDefaultConfig(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to load default AWS authentication configuration %w", err)
|
||||
}
|
||||
|
||||
authenticationToken, err := auth.BuildAuthToken(
|
||||
ctx, dbEndpoint, *a.region, poolConfig.ConnConfig.User, awsCfg.Credentials)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create AWS authentication token: %w", err)
|
||||
}
|
||||
|
||||
return authenticationToken, nil
|
||||
}
|
||||
|
||||
func (a *x509) UpdatePostgres(ctx context.Context, poolConfig *pgxpool.Config) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
// Set max connection lifetime to 8 minutes in postgres connection pool configuration.
|
||||
// Note: this will refresh connections before the 15 min expiration on the IAM AWS auth token,
|
||||
// while leveraging the BeforeConnect hook to recreate the token in time dynamically.
|
||||
poolConfig.MaxConnLifetime = time.Minute * 8
|
||||
|
||||
// Setup connection pool config needed for AWS IAM authentication
|
||||
poolConfig.BeforeConnect = func(ctx context.Context, pgConfig *pgx.ConnConfig) error {
|
||||
// Manually reset auth token with aws and reset the config password using the new iam token
|
||||
pwd, err := a.getDatabaseToken(ctx, poolConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get database token: %w", err)
|
||||
}
|
||||
pgConfig.Password = pwd
|
||||
poolConfig.ConnConfig.Password = pwd
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a *x509) Kafka(opts KafkaOptions) (*KafkaClients, error) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/dapr/components-contrib/common/authentication/aws"
|
||||
"github.com/dapr/components-contrib/common/authentication/azure"
|
||||
"github.com/dapr/components-contrib/metadata"
|
||||
"github.com/dapr/kit/logger"
|
||||
)
|
||||
|
||||
// PostgresAuthMetadata contains authentication metadata for PostgreSQL components.
|
||||
|
@ -86,16 +87,43 @@ func (m *PostgresAuthMetadata) InitWithMetadata(meta map[string]string, opts Ini
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *PostgresAuthMetadata) ValidateAwsIamFields() (string, string, string, error) {
|
||||
func (m *PostgresAuthMetadata) BuildAwsIamOptions(logger logger.Logger, properties map[string]string) (*aws.Options, error) {
|
||||
awsRegion, _ := metadata.GetMetadataProperty(m.awsEnv.Metadata, "AWSRegion")
|
||||
if awsRegion == "" {
|
||||
return "", "", "", errors.New("metadata property AWSRegion is missing")
|
||||
return nil, errors.New("metadata property AWSRegion is missing")
|
||||
}
|
||||
|
||||
// Note: access key and secret keys can be optional
|
||||
// in the event users are leveraging the credential files for an access token.
|
||||
awsAccessKey, _ := metadata.GetMetadataProperty(m.awsEnv.Metadata, "AWSAccessKey")
|
||||
// This is needed as we remove the awsAccessKey field to use the builtin AWS profile 'accessKey' field instead.
|
||||
accessKey, _ := metadata.GetMetadataProperty(m.awsEnv.Metadata, "AccessKey")
|
||||
if awsAccessKey == "" || accessKey != "" {
|
||||
awsAccessKey = accessKey
|
||||
}
|
||||
awsSecretKey, _ := metadata.GetMetadataProperty(m.awsEnv.Metadata, "AWSSecretKey")
|
||||
return awsRegion, awsAccessKey, awsSecretKey, nil
|
||||
// This is needed as we remove the awsSecretKey field to use the builtin AWS profile 'secretKey' field instead.
|
||||
secretKey, _ := metadata.GetMetadataProperty(m.awsEnv.Metadata, "SecretKey")
|
||||
if awsSecretKey == "" || secretKey != "" {
|
||||
awsSecretKey = secretKey
|
||||
}
|
||||
sessionToken, _ := metadata.GetMetadataProperty(m.awsEnv.Metadata, "sessionToken")
|
||||
assumeRoleArn, _ := metadata.GetMetadataProperty(m.awsEnv.Metadata, "assumeRoleArn")
|
||||
sessionName, _ := metadata.GetMetadataProperty(m.awsEnv.Metadata, "sessionName")
|
||||
if sessionName == "" {
|
||||
sessionName = "DaprDefaultSession"
|
||||
}
|
||||
return &aws.Options{
|
||||
Region: awsRegion,
|
||||
AccessKey: awsAccessKey,
|
||||
SecretKey: awsSecretKey,
|
||||
SessionToken: sessionToken,
|
||||
AssumeRoleARN: assumeRoleArn,
|
||||
SessionName: sessionName,
|
||||
|
||||
Logger: logger,
|
||||
Properties: properties,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPgxPoolConfig returns the pgxpool.Config object that contains the credentials for connecting to PostgreSQL.
|
||||
|
@ -154,27 +182,6 @@ func (m *PostgresAuthMetadata) GetPgxPoolConfig() (*pgxpool.Config, error) {
|
|||
cc.Password = at.Token
|
||||
return nil
|
||||
}
|
||||
case m.UseAWSIAM:
|
||||
// We should use AWS IAM
|
||||
awsRegion, awsAccessKey, awsSecretKey, err := m.ValidateAwsIamFields()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to validate AWS IAM authentication fields: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
awsOpts := aws.Options{
|
||||
PoolConfig: config,
|
||||
ConnectionString: m.ConnectionString,
|
||||
Region: awsRegion,
|
||||
AccessKey: awsAccessKey,
|
||||
SecretKey: awsSecretKey,
|
||||
}
|
||||
|
||||
err = awsOpts.InitiateAWSIAMAuth()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to initiate AWS IAM authentication rotation: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
|
|
|
@ -39,7 +39,7 @@ type pgMetadata struct {
|
|||
Timeout time.Duration `mapstructure:"timeout" mapstructurealiases:"timeoutInSeconds"`
|
||||
CleanupInterval *time.Duration `mapstructure:"cleanupInterval" mapstructurealiases:"cleanupIntervalInSeconds"`
|
||||
|
||||
aws.AWSIAM `mapstructure:",squash"`
|
||||
aws.DeprecatedPostgresIAM `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func (m *pgMetadata) InitWithMetadata(meta state.Metadata, opts pgauth.InitWithMetadataOpts) error {
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
awsAuth "github.com/dapr/components-contrib/common/authentication/aws"
|
||||
pgauth "github.com/dapr/components-contrib/common/authentication/postgresql"
|
||||
pginterfaces "github.com/dapr/components-contrib/common/component/postgresql/interfaces"
|
||||
pgtransactions "github.com/dapr/components-contrib/common/component/postgresql/transactions"
|
||||
|
@ -54,6 +55,8 @@ type PostgreSQL struct {
|
|||
etagColumn string
|
||||
enableAzureAD bool
|
||||
enableAWSIAM bool
|
||||
|
||||
awsAuthProvider awsAuth.Provider
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
|
@ -96,16 +99,31 @@ func (p *PostgreSQL) Init(ctx context.Context, meta state.Metadata) error {
|
|||
AWSIAMEnabled: p.enableAWSIAM,
|
||||
}
|
||||
|
||||
err := p.metadata.InitWithMetadata(meta, opts)
|
||||
if err != nil {
|
||||
if err := p.metadata.InitWithMetadata(meta, opts); err != nil {
|
||||
return fmt.Errorf("failed to parse metadata: %w", err)
|
||||
}
|
||||
|
||||
var err error
|
||||
config, err := p.metadata.GetPgxPoolConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.AWSIAMEnabled && p.metadata.UseAWSIAM {
|
||||
opts, validateErr := p.metadata.BuildAwsIamOptions(p.logger, meta.Properties)
|
||||
if validateErr != nil {
|
||||
return fmt.Errorf("failed to validate AWS IAM authentication fields: %w", validateErr)
|
||||
}
|
||||
|
||||
var provider awsAuth.Provider
|
||||
provider, err = awsAuth.NewProvider(ctx, *opts, awsAuth.GetConfig(*opts))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.awsAuthProvider = provider
|
||||
p.awsAuthProvider.UpdatePostgres(ctx, config)
|
||||
}
|
||||
|
||||
connCtx, connCancel := context.WithTimeout(ctx, p.metadata.Timeout)
|
||||
p.db, err = pgxpool.NewWithConfig(connCtx, config)
|
||||
connCancel()
|
||||
|
@ -491,11 +509,15 @@ func (p *PostgreSQL) Close() error {
|
|||
p.db = nil
|
||||
}
|
||||
|
||||
errs := make([]error, 2)
|
||||
if p.gc != nil {
|
||||
return p.gc.Close()
|
||||
errs[0] = p.gc.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
if p.awsAuthProvider != nil {
|
||||
errs[1] = p.awsAuthProvider.Close()
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// GetCleanupInterval returns the cleanupInterval property.
|
||||
|
|
|
@ -32,7 +32,7 @@ type metadata struct {
|
|||
Timeout time.Duration `mapstructure:"timeout" mapstructurealiases:"timeoutInSeconds"`
|
||||
ConfigTable string `mapstructure:"table"`
|
||||
MaxIdleTimeoutOld time.Duration `mapstructure:"connMaxIdleTime"` // Deprecated alias for "connectionMaxIdleTime"
|
||||
aws.AWSIAM `mapstructure:",squash"`
|
||||
aws.DeprecatedPostgresIAM `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func (m *metadata) InitWithMetadata(meta map[string]string) error {
|
||||
|
|
|
@ -46,25 +46,31 @@ builtinAuthenticationProfiles:
|
|||
example: |
|
||||
"host=mydb.postgres.database.aws.com user=myapplication port=5432 dbname=dapr_test sslmode=require"
|
||||
type: string
|
||||
- name: awsRegion
|
||||
type: string
|
||||
required: true
|
||||
description: |
|
||||
The AWS Region where the AWS Relational Database Service is deployed to.
|
||||
example: '"us-east-1"'
|
||||
- name: awsAccessKey
|
||||
type: string
|
||||
required: true
|
||||
required: false
|
||||
description: |
|
||||
Deprecated as of Dapr 1.17. Use 'accessKey' instead if using AWS IAM.
|
||||
If both fields are set, then 'accessKey' value will be used.
|
||||
AWS access key associated with an IAM account.
|
||||
example: '"AKIAIOSFODNN7EXAMPLE"'
|
||||
- name: awsSecretKey
|
||||
type: string
|
||||
required: true
|
||||
required: false
|
||||
sensitive: true
|
||||
description: |
|
||||
Deprecated as of Dapr 1.17. Use 'secretKey' instead if using AWS IAM.
|
||||
If both fields are set, then 'secretKey' value will be used.
|
||||
The secret key associated with the access key.
|
||||
example: '"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"'
|
||||
- name: awsRegion
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
This maintains backwards compatibility with existing fields.
|
||||
It will be deprecated as of Dapr 1.17. Use 'region' instead.
|
||||
The AWS Region where the AWS service is deployed to.
|
||||
example: '"us-east-1"'
|
||||
authenticationProfiles:
|
||||
- title: "Connection string"
|
||||
description: "Authenticate using a Connection String."
|
||||
|
|
|
@ -31,6 +31,8 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
awsAuth "github.com/dapr/components-contrib/common/authentication/aws"
|
||||
pgauth "github.com/dapr/components-contrib/common/authentication/postgresql"
|
||||
"github.com/dapr/components-contrib/configuration"
|
||||
contribMetadata "github.com/dapr/components-contrib/metadata"
|
||||
"github.com/dapr/kit/logger"
|
||||
|
@ -47,6 +49,10 @@ type ConfigurationStore struct {
|
|||
wg sync.WaitGroup
|
||||
closed atomic.Bool
|
||||
lock sync.RWMutex
|
||||
|
||||
enableAzureAD bool
|
||||
enableAWSIAM bool
|
||||
awsAuthProvider awsAuth.Provider
|
||||
}
|
||||
|
||||
type subscription struct {
|
||||
|
@ -77,6 +83,10 @@ func NewPostgresConfigurationStore(logger logger.Logger) configuration.Store {
|
|||
}
|
||||
|
||||
func (p *ConfigurationStore) Init(ctx context.Context, metadata configuration.Metadata) error {
|
||||
opts := pgauth.InitWithMetadataOpts{
|
||||
AzureADEnabled: p.enableAzureAD,
|
||||
AWSIAMEnabled: p.enableAWSIAM,
|
||||
}
|
||||
err := p.metadata.InitWithMetadata(metadata.Properties)
|
||||
if err != nil {
|
||||
p.logger.Error(err)
|
||||
|
@ -84,11 +94,37 @@ func (p *ConfigurationStore) Init(ctx context.Context, metadata configuration.Me
|
|||
}
|
||||
|
||||
p.ActiveSubscriptions = make(map[string]*subscription)
|
||||
p.client, err = p.connectDB(ctx)
|
||||
config, err := p.metadata.GetPgxPoolConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error connecting to configuration store: '%w'", err)
|
||||
return fmt.Errorf("PostgreSQL configuration store connection error: %s", err)
|
||||
}
|
||||
|
||||
if opts.AWSIAMEnabled && p.metadata.UseAWSIAM {
|
||||
opts, validateErr := p.metadata.BuildAwsIamOptions(p.logger, metadata.Properties)
|
||||
if validateErr != nil {
|
||||
return fmt.Errorf("failed to validate AWS IAM authentication fields: %w", validateErr)
|
||||
}
|
||||
|
||||
var provider awsAuth.Provider
|
||||
provider, err = awsAuth.NewProvider(ctx, *opts, awsAuth.GetConfig(*opts))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.awsAuthProvider = provider
|
||||
p.awsAuthProvider.UpdatePostgres(ctx, config)
|
||||
}
|
||||
|
||||
pool, err := pgxpool.NewWithConfig(ctx, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("PostgreSQL configuration store connection error: %w", err)
|
||||
}
|
||||
|
||||
err = pool.Ping(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("PostgreSQL configuration store ping error: %w", err)
|
||||
}
|
||||
p.client = pool
|
||||
|
||||
err = p.client.Ping(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to connect to configuration store: '%w'", err)
|
||||
|
@ -304,25 +340,6 @@ func (p *ConfigurationStore) handleSubscribedChange(ctx context.Context, handler
|
|||
}
|
||||
}
|
||||
|
||||
func (p *ConfigurationStore) connectDB(ctx context.Context) (*pgxpool.Pool, error) {
|
||||
config, err := p.metadata.GetPgxPoolConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("PostgreSQL configuration store connection error: %s", err)
|
||||
}
|
||||
|
||||
pool, err := pgxpool.NewWithConfig(ctx, config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("PostgreSQL configuration store connection error: %w", err)
|
||||
}
|
||||
|
||||
err = pool.Ping(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("PostgreSQL configuration store ping error: %w", err)
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func buildQuery(req *configuration.GetRequest, configTable string) (string, []interface{}, error) {
|
||||
var query string
|
||||
var params []interface{}
|
||||
|
@ -436,5 +453,9 @@ func (p *ConfigurationStore) Close() error {
|
|||
p.client.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
errs := make([]error, 1)
|
||||
if p.awsAuthProvider != nil {
|
||||
errs[0] = p.awsAuthProvider.Close()
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -46,6 +46,7 @@ require (
|
|||
github.com/aws/aws-sdk-go-v2/credentials v1.17.43
|
||||
github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.3.10
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.17.3
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.4
|
||||
github.com/aws/rolesanywhere-credential-helper v1.0.4
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874
|
||||
github.com/camunda/zeebe/clients/go/v8 v8.2.12
|
||||
|
@ -188,7 +189,6 @@ require (
|
|||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.4 // indirect
|
||||
github.com/aws/smithy-go v1.22.0 // indirect
|
||||
github.com/awslabs/kinesis-aggregation/go v0.0.0-20210630091500-54e17340d32f // indirect
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
|
|
|
@ -29,7 +29,7 @@ builtinAuthenticationProfiles:
|
|||
description: |
|
||||
This maintains backwards compatibility with existing fields.
|
||||
It will be deprecated as of Dapr 1.17. Use 'region' instead.
|
||||
The AWS Region where the AWS Relational Database Service is deployed to.
|
||||
The AWS Region where the AWS service is deployed to.
|
||||
example: '"us-east-1"'
|
||||
- name: awsAccessKey
|
||||
type: string
|
||||
|
@ -76,7 +76,7 @@ builtinAuthenticationProfiles:
|
|||
If both fields are set, then 'sessionName' value will be used.
|
||||
Represents the session name for assuming a role.
|
||||
example: '"MyAppSession"'
|
||||
default: '"MSKSASLDefaultSession"'
|
||||
default: '"DaprDefaultSession"'
|
||||
authenticationProfiles:
|
||||
- title: "OIDC Authentication"
|
||||
description: |
|
||||
|
|
|
@ -53,16 +53,12 @@ builtinAuthenticationProfiles:
|
|||
example: |
|
||||
"host=mydb.postgres.database.aws.com user=myapplication port=5432 dbname=dapr_test sslmode=require"
|
||||
type: string
|
||||
- name: awsRegion
|
||||
type: string
|
||||
required: true
|
||||
description: |
|
||||
The AWS Region where the AWS Relational Database Service is deployed to.
|
||||
example: '"us-east-1"'
|
||||
- name: awsAccessKey
|
||||
type: string
|
||||
required: true
|
||||
required: false
|
||||
description: |
|
||||
Deprecated as of Dapr 1.17. Use 'accessKey' instead if using AWS IAM.
|
||||
If both fields are set, then 'accessKey' value will be used.
|
||||
AWS access key associated with an IAM account.
|
||||
example: '"AKIAIOSFODNN7EXAMPLE"'
|
||||
- name: awsSecretKey
|
||||
|
@ -70,8 +66,18 @@ builtinAuthenticationProfiles:
|
|||
required: false
|
||||
sensitive: true
|
||||
description: |
|
||||
Deprecated as of Dapr 1.17. Use 'secretKey' instead if using AWS IAM.
|
||||
If both fields are set, then 'secretKey' value will be used.
|
||||
The secret key associated with the access key.
|
||||
example: '"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"'
|
||||
- name: awsRegion
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
This maintains backwards compatibility with existing fields.
|
||||
It will be deprecated as of Dapr 1.17. Use 'region' instead.
|
||||
The AWS Region where the AWS service is deployed to.
|
||||
example: '"us-east-1"'
|
||||
authenticationProfiles:
|
||||
- title: "Connection string"
|
||||
description: "Authenticate using a Connection String"
|
||||
|
|
|
@ -43,7 +43,7 @@ type pgMetadata struct {
|
|||
Timeout time.Duration `mapstructure:"timeout" mapstructurealiases:"timeoutInSeconds"`
|
||||
CleanupInterval *time.Duration `mapstructure:"cleanupInterval" mapstructurealiases:"cleanupIntervalInSeconds"`
|
||||
|
||||
aws.AWSIAM `mapstructure:",squash"`
|
||||
aws.DeprecatedPostgresIAM `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func (m *pgMetadata) InitWithMetadata(meta state.Metadata, opts pgauth.InitWithMetadataOpts) error {
|
||||
|
@ -60,7 +60,7 @@ func (m *pgMetadata) InitWithMetadata(meta state.Metadata, opts pgauth.InitWithM
|
|||
return err
|
||||
}
|
||||
|
||||
// Validate and sanitize input
|
||||
// Validate and sanitize inputq
|
||||
err = m.PostgresAuthMetadata.InitWithMetadata(meta.Properties, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -52,16 +52,12 @@ builtinAuthenticationProfiles:
|
|||
example: |
|
||||
"host=mydb.postgres.database.aws.com user=myapplication port=5432 dbname=dapr_test sslmode=require"
|
||||
type: string
|
||||
- name: awsRegion
|
||||
type: string
|
||||
required: true
|
||||
description: |
|
||||
The AWS Region where the AWS Relational Database Service is deployed to.
|
||||
example: '"us-east-1"'
|
||||
- name: awsAccessKey
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
Deprecated as of Dapr 1.17. Use 'accessKey' instead if using AWS IAM.
|
||||
If both fields are set, then 'accessKey' value will be used.
|
||||
AWS access key associated with an IAM account.
|
||||
example: '"AKIAIOSFODNN7EXAMPLE"'
|
||||
- name: awsSecretKey
|
||||
|
@ -69,8 +65,18 @@ builtinAuthenticationProfiles:
|
|||
required: false
|
||||
sensitive: true
|
||||
description: |
|
||||
Deprecated as of Dapr 1.17. Use 'secretKey' instead if using AWS IAM.
|
||||
If both fields are set, then 'secretKey' value will be used.
|
||||
The secret key associated with the access key.
|
||||
example: '"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"'
|
||||
- name: awsRegion
|
||||
type: string
|
||||
required: false
|
||||
description: |
|
||||
This maintains backwards compatibility with existing fields.
|
||||
It will be deprecated as of Dapr 1.17. Use 'region' instead.
|
||||
The AWS Region where the AWS service is deployed to.
|
||||
example: '"us-east-1"'
|
||||
authenticationProfiles:
|
||||
- title: "Connection string"
|
||||
description: "Authenticate using a Connection String"
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
awsAuth "github.com/dapr/components-contrib/common/authentication/aws"
|
||||
pgauth "github.com/dapr/components-contrib/common/authentication/postgresql"
|
||||
pginterfaces "github.com/dapr/components-contrib/common/component/postgresql/interfaces"
|
||||
pgtransactions "github.com/dapr/components-contrib/common/component/postgresql/transactions"
|
||||
|
@ -51,6 +52,8 @@ type PostgreSQL struct {
|
|||
|
||||
enableAzureAD bool
|
||||
enableAWSIAM bool
|
||||
|
||||
awsAuthProvider awsAuth.Provider
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
|
@ -98,6 +101,21 @@ func (p *PostgreSQL) Init(ctx context.Context, meta state.Metadata) (err error)
|
|||
return err
|
||||
}
|
||||
|
||||
if opts.AWSIAMEnabled && p.metadata.UseAWSIAM {
|
||||
opts, validateErr := p.metadata.BuildAwsIamOptions(p.logger, meta.Properties)
|
||||
if validateErr != nil {
|
||||
return fmt.Errorf("failed to validate AWS IAM authentication fields: %w", validateErr)
|
||||
}
|
||||
|
||||
var provider awsAuth.Provider
|
||||
provider, err = awsAuth.NewProvider(ctx, *opts, awsAuth.GetConfig(*opts))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.awsAuthProvider = provider
|
||||
p.awsAuthProvider.UpdatePostgres(ctx, config)
|
||||
}
|
||||
|
||||
connCtx, connCancel := context.WithTimeout(ctx, p.metadata.Timeout)
|
||||
defer connCancel()
|
||||
p.db, err = pgxpool.NewWithConfig(connCtx, config)
|
||||
|
@ -534,11 +552,15 @@ func (p *PostgreSQL) Close() error {
|
|||
p.db = nil
|
||||
}
|
||||
|
||||
errs := make([]error, 2)
|
||||
if p.gc != nil {
|
||||
return p.gc.Close()
|
||||
errs[0] = p.gc.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
if p.awsAuthProvider != nil {
|
||||
errs[1] = p.awsAuthProvider.Close()
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// GetCleanupInterval returns the cleanupInterval property.
|
||||
|
|
Loading…
Reference in New Issue