diff --git a/main.go b/main.go index 87a739813..8ac832f27 100644 --- a/main.go +++ b/main.go @@ -200,6 +200,7 @@ func main() { BatchChangeInterval: cfg.AWSBatchChangeInterval, EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth, AssumeRole: cfg.AWSAssumeRole, + AssumeRoleExternalID: cfg.AWSAssumeRoleExternalID, APIRetries: cfg.AWSAPIRetries, PreferCNAME: cfg.AWSPreferCNAME, DryRun: cfg.DryRun, @@ -212,7 +213,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 92dfe436c..e62720227 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -85,6 +85,7 @@ type Config struct { AWSZoneType string AWSZoneTagFilter []string AWSAssumeRole string + AWSAssumeRoleExternalID string AWSBatchChangeSize int AWSBatchChangeInterval time.Duration AWSEvaluateTargetHealth bool @@ -231,6 +232,7 @@ var defaultConfig = &Config{ AWSZoneType: "", AWSZoneTagFilter: []string{}, AWSAssumeRole: "", + AWSAssumeRoleExternalID: "", AWSBatchChangeSize: 1000, AWSBatchChangeInterval: time.Second, AWSEvaluateTargetHealth: true, @@ -429,6 +431,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 4d9c6f315..1d6f202e6 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, @@ -159,6 +160,7 @@ var ( AWSZoneType: "private", AWSZoneTagFilter: []string{"tag=foo"}, AWSAssumeRole: "some-other-role", + AWSAssumeRoleExternalID: "pg2000", AWSBatchChangeSize: 100, AWSBatchChangeInterval: time.Second * 2, AWSEvaluateTargetHealth: false, @@ -332,6 +334,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", @@ -446,6 +449,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..fbbe55cf4 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 @@ -198,8 +199,15 @@ 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.AssumeRoleExternalID != "" { + log.Infof("Assuming role: %s with external id %s", awsConfig.AssumeRole, awsConfig.AssumeRoleExternalID) + session.Config.WithCredentials(stscreds.NewCredentials(session, awsConfig.AssumeRole, func(p *stscreds.AssumeRoleProvider) { + p.ExternalID = &awsConfig.AssumeRoleExternalID + })) + } else { + log.Infof("Assuming role: %s", awsConfig.AssumeRole) + 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..a87a8411d 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 %q with external ID %q", assumeRole, assumeRoleExternalID) + 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))