diff --git a/main.go b/main.go index 14fd99b1d..0df388c6e 100644 --- a/main.go +++ b/main.go @@ -196,6 +196,7 @@ func main() { BatchChangeInterval: cfg.AWSBatchChangeInterval, EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth, AssumeRole: cfg.AWSAssumeRole, + AssumeRoleExternalId: cfg.AWSAssumeRoleExternalId, APIRetries: cfg.AWSAPIRetries, PreferCNAME: cfg.AWSPreferCNAME, DryRun: cfg.DryRun, @@ -208,7 +209,7 @@ func main() { log.Infof("Registry \"%s\" cannot be used with AWS Cloud Map. Switching to \"aws-sd\".", cfg.Registry) cfg.Registry = "aws-sd" } - p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.AWSAssumeRole, cfg.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID) + p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.AWSAssumeRole, cfg.AWSAssumeRoleExternalId, cfg.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID) case "azure-dns", "azure": p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun) case "azure-private-dns": diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index e9c75c28a..446a57316 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -83,6 +83,7 @@ type Config struct { AWSZoneType string AWSZoneTagFilter []string AWSAssumeRole string + AWSAssumeRoleExternalId string AWSBatchChangeSize int AWSBatchChangeInterval time.Duration AWSEvaluateTargetHealth bool @@ -227,6 +228,7 @@ var defaultConfig = &Config{ AWSZoneType: "", AWSZoneTagFilter: []string{}, AWSAssumeRole: "", + AWSAssumeRoleExternalId: "", AWSBatchChangeSize: 1000, AWSBatchChangeInterval: time.Second, AWSEvaluateTargetHealth: true, @@ -423,6 +425,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("aws-zone-type", "When using the AWS provider, filter for zones of this type (optional, options: public, private)").Default(defaultConfig.AWSZoneType).EnumVar(&cfg.AWSZoneType, "", "public", "private") app.Flag("aws-zone-tags", "When using the AWS provider, filter for zones with these tags").Default("").StringsVar(&cfg.AWSZoneTagFilter) app.Flag("aws-assume-role", "When using the AWS provider, assume this IAM role. Useful for hosted zones in another AWS account. Specify the full ARN, e.g. `arn:aws:iam::123455567:role/external-dns` (optional)").Default(defaultConfig.AWSAssumeRole).StringVar(&cfg.AWSAssumeRole) + app.Flag("aws-assume-role-external-id", "When using the AWS provider and assuming a role then specify this external id` (optional)").Default(defaultConfig.AWSAssumeRoleExternalId).StringVar(&cfg.AWSAssumeRoleExternalId) app.Flag("aws-batch-change-size", "When using the AWS provider, set the maximum number of changes that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.AWSBatchChangeSize)).IntVar(&cfg.AWSBatchChangeSize) app.Flag("aws-batch-change-interval", "When using the AWS provider, set the interval between batch changes.").Default(defaultConfig.AWSBatchChangeInterval.String()).DurationVar(&cfg.AWSBatchChangeInterval) app.Flag("aws-evaluate-target-health", "When using the AWS provider, set whether to evaluate the health of a DNS target (default: enabled, disable with --no-aws-evaluate-target-health)").Default(strconv.FormatBool(defaultConfig.AWSEvaluateTargetHealth)).BoolVar(&cfg.AWSEvaluateTargetHealth) diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index 06d1a9482..01d0ccb0a 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -57,6 +57,7 @@ var ( AWSZoneType: "", AWSZoneTagFilter: []string{""}, AWSAssumeRole: "", + AWSAssumeRoleExternalId: "", AWSBatchChangeSize: 1000, AWSBatchChangeInterval: time.Second, AWSEvaluateTargetHealth: true, @@ -157,6 +158,7 @@ var ( AWSZoneType: "private", AWSZoneTagFilter: []string{"tag=foo"}, AWSAssumeRole: "some-other-role", + AWSAssumeRoleExternalId: "pg2000", AWSBatchChangeSize: 100, AWSBatchChangeInterval: time.Second * 2, AWSEvaluateTargetHealth: false, @@ -326,6 +328,7 @@ func TestParseFlags(t *testing.T) { "--aws-zone-type=private", "--aws-zone-tags=tag=foo", "--aws-assume-role=some-other-role", + "--aws-assume-role-external-id=pg2000", "--aws-batch-change-size=100", "--aws-batch-change-interval=2s", "--aws-api-retries=13", @@ -438,6 +441,7 @@ func TestParseFlags(t *testing.T) { "EXTERNAL_DNS_AWS_ZONE_TYPE": "private", "EXTERNAL_DNS_AWS_ZONE_TAGS": "tag=foo", "EXTERNAL_DNS_AWS_ASSUME_ROLE": "some-other-role", + "EXTERNAL_DNS_AWS_ASSUME_ROLE_EXTERNAL_ID": "pg2000", "EXTERNAL_DNS_AWS_BATCH_CHANGE_SIZE": "100", "EXTERNAL_DNS_AWS_BATCH_CHANGE_INTERVAL": "2s", "EXTERNAL_DNS_AWS_EVALUATE_TARGET_HEALTH": "0", diff --git a/provider/aws/aws.go b/provider/aws/aws.go index 8868de97d..f514bd3e6 100644 --- a/provider/aws/aws.go +++ b/provider/aws/aws.go @@ -170,6 +170,7 @@ type AWSConfig struct { BatchChangeInterval time.Duration EvaluateTargetHealth bool AssumeRole string + AssumeRoleExternalId string APIRetries int PreferCNAME bool DryRun bool @@ -199,7 +200,13 @@ func NewAWSProvider(awsConfig AWSConfig) (*AWSProvider, error) { if awsConfig.AssumeRole != "" { log.Infof("Assuming role: %s", awsConfig.AssumeRole) - session.Config.WithCredentials(stscreds.NewCredentials(session, awsConfig.AssumeRole)) + if awsConfig.AssumeRole != "" { + session.Config.WithCredentials(stscreds.NewCredentials(session, awsConfig.AssumeRole, func(p *stscreds.AssumeRoleProvider) { + p.ExternalID = &awsConfig.AssumeRoleExternalId + })) + } else { + session.Config.WithCredentials(stscreds.NewCredentials(session, awsConfig.AssumeRole)) + } } provider := &AWSProvider{ diff --git a/provider/awssd/aws_sd.go b/provider/awssd/aws_sd.go index 7e2bb737b..ac846d2ed 100644 --- a/provider/awssd/aws_sd.go +++ b/provider/awssd/aws_sd.go @@ -88,7 +88,7 @@ type AWSSDProvider struct { } // NewAWSSDProvider initializes a new AWS Cloud Map based Provider. -func NewAWSSDProvider(domainFilter endpoint.DomainFilter, namespaceType string, assumeRole string, dryRun, cleanEmptyService bool, ownerID string) (*AWSSDProvider, error) { +func NewAWSSDProvider(domainFilter endpoint.DomainFilter, namespaceType string, assumeRole string, assumeRoleExternalId string, dryRun, cleanEmptyService bool, ownerID string) (*AWSSDProvider, error) { config := aws.NewConfig() config = config.WithHTTPClient( @@ -109,8 +109,15 @@ func NewAWSSDProvider(domainFilter endpoint.DomainFilter, namespaceType string, } if assumeRole != "" { - log.Infof("Assuming role: %s", assumeRole) - sess.Config.WithCredentials(stscreds.NewCredentials(sess, assumeRole)) + if assumeRoleExternalId != "" { + log.Infof("Assuming role: %s with external id", assumeRole) + sess.Config.WithCredentials(stscreds.NewCredentials(sess, assumeRole, func(p *stscreds.AssumeRoleProvider) { + p.ExternalID = &assumeRoleExternalId + })) + } else { + log.Infof("Assuming role: %s", assumeRole) + sess.Config.WithCredentials(stscreds.NewCredentials(sess, assumeRole)) + } } sess.Handlers.Build.PushBack(request.MakeAddToUserAgentHandler("ExternalDNS", externaldns.Version))