From 587b4128cb9c2118e664b8638e5eb0125b0f86c6 Mon Sep 17 00:00:00 2001 From: Martin Linkhorst Date: Thu, 12 Apr 2018 14:45:20 +0200 Subject: [PATCH 1/3] feat: allow to assume another role for AWS --- main.go | 2 +- pkg/apis/externaldns/types.go | 2 ++ provider/aws.go | 27 ++++++++++++++++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index b551cf32a5..dd1ab4b52a 100644 --- a/main.go +++ b/main.go @@ -94,7 +94,7 @@ func main() { var p provider.Provider switch cfg.Provider { case "aws": - p, err = provider.NewAWSProvider(domainFilter, zoneIDFilter, zoneTypeFilter, cfg.DryRun) + p, err = provider.NewAWSProvider(domainFilter, zoneIDFilter, zoneTypeFilter, cfg.AWSAssumeRole, cfg.DryRun) case "azure": p, err = provider.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.DryRun) case "cloudflare": diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 78f639e308..30ef8c4a19 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -50,6 +50,7 @@ type Config struct { DomainFilter []string ZoneIDFilter []string AWSZoneType string + AWSAssumeRole string AzureConfigFile string AzureResourceGroup string CloudflareProxied bool @@ -164,6 +165,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("zone-id-filter", "Filter target zones by hosted zone id; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneIDFilter) app.Flag("google-project", "When using the Google provider, current project is auto-detected, when running on GCP. Specify other project with this. Must be specified when running outside GCP.").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject) 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-assume-role", "TODO").StringVar(&cfg.AWSAssumeRole) app.Flag("azure-config-file", "When using the Azure provider, specify the Azure configuration file (required when --provider=azure").Default(defaultConfig.AzureConfigFile).StringVar(&cfg.AzureConfigFile) app.Flag("azure-resource-group", "When using the Azure provider, override the Azure resource group to use (optional)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup) app.Flag("cloudflare-proxied", "When using the Cloudflare provider, specify if the proxy mode must be enabled (default: disabled)").BoolVar(&cfg.CloudflareProxied) diff --git a/provider/aws.go b/provider/aws.go index 6204cf8d7b..f56ff8e1d6 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -21,8 +21,10 @@ import ( "strings" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/route53" + "github.com/aws/aws-sdk-go/service/sts" "github.com/kubernetes-incubator/external-dns/endpoint" "github.com/kubernetes-incubator/external-dns/plan" "github.com/linki/instrumented_http" @@ -79,7 +81,7 @@ type AWSProvider struct { } // NewAWSProvider initializes a new AWS Route53 based Provider. -func NewAWSProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, dryRun bool) (*AWSProvider, error) { +func NewAWSProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, assumeRole string, dryRun bool) (*AWSProvider, error) { config := aws.NewConfig() config = config.WithHTTPClient( @@ -99,6 +101,29 @@ func NewAWSProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTy return nil, err } + if assumeRole != "" { + svc := sts.New(session) + + params := &sts.AssumeRoleInput{ + RoleArn: aws.String(assumeRole), + RoleSessionName: aws.String("external-dns"), + } + + log.Infof("Assuming role %s..", aws.StringValue(params.RoleArn)) + + resp, err := svc.AssumeRole(params) + if err != nil { + return nil, err + } + + session.Config.WithCredentials(credentials.NewStaticCredentialsFromCreds(credentials.Value{ + AccessKeyID: aws.StringValue(resp.Credentials.AccessKeyId), + SecretAccessKey: aws.StringValue(resp.Credentials.SecretAccessKey), + SessionToken: aws.StringValue(resp.Credentials.SessionToken), + ProviderName: "assumeRoleProvider", + })) + } + provider := &AWSProvider{ client: route53.New(session), domainFilter: domainFilter, From 4dd4decf7d9524d87de24c506213e46c08010a13 Mon Sep 17 00:00:00 2001 From: Martin Linkhorst Date: Tue, 17 Apr 2018 17:33:41 +0200 Subject: [PATCH 2/3] ref: use AWS's AssumeRoleProvider to refresh credentials --- pkg/apis/externaldns/types.go | 3 ++- pkg/apis/externaldns/types_test.go | 4 ++++ provider/aws.go | 27 ++++----------------------- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 30ef8c4a19..3bc23ad15b 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -91,6 +91,7 @@ var defaultConfig = &Config{ GoogleProject: "", DomainFilter: []string{}, AWSZoneType: "", + AWSAssumeRole: "", AzureConfigFile: "/etc/kubernetes/azure.json", AzureResourceGroup: "", CloudflareProxied: false, @@ -165,7 +166,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("zone-id-filter", "Filter target zones by hosted zone id; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneIDFilter) app.Flag("google-project", "When using the Google provider, current project is auto-detected, when running on GCP. Specify other project with this. Must be specified when running outside GCP.").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject) 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-assume-role", "TODO").StringVar(&cfg.AWSAssumeRole) + 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("azure-config-file", "When using the Azure provider, specify the Azure configuration file (required when --provider=azure").Default(defaultConfig.AzureConfigFile).StringVar(&cfg.AzureConfigFile) app.Flag("azure-resource-group", "When using the Azure provider, override the Azure resource group to use (optional)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup) app.Flag("cloudflare-proxied", "When using the Cloudflare provider, specify if the proxy mode must be enabled (default: disabled)").BoolVar(&cfg.CloudflareProxied) diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index 1cfb0d97ca..a256a4ab3d 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -40,6 +40,7 @@ var ( DomainFilter: []string{""}, ZoneIDFilter: []string{""}, AWSZoneType: "", + AWSAssumeRole: "", AzureConfigFile: "/etc/kubernetes/azure.json", AzureResourceGroup: "", CloudflareProxied: false, @@ -74,6 +75,7 @@ var ( DomainFilter: []string{"example.org", "company.com"}, ZoneIDFilter: []string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"}, AWSZoneType: "private", + AWSAssumeRole: "some-other-role", AzureConfigFile: "azure.json", AzureResourceGroup: "arg", CloudflareProxied: true, @@ -141,6 +143,7 @@ func TestParseFlags(t *testing.T) { "--zone-id-filter=/hostedzone/ZTST1", "--zone-id-filter=/hostedzone/ZTST2", "--aws-zone-type=private", + "--aws-assume-role=some-other-role", "--policy=upsert-only", "--registry=noop", "--txt-owner-id=owner-1", @@ -180,6 +183,7 @@ func TestParseFlags(t *testing.T) { "EXTERNAL_DNS_DOMAIN_FILTER": "example.org\ncompany.com", "EXTERNAL_DNS_ZONE_ID_FILTER": "/hostedzone/ZTST1\n/hostedzone/ZTST2", "EXTERNAL_DNS_AWS_ZONE_TYPE": "private", + "EXTERNAL_DNS_AWS_ASSUME_ROLE": "some-other-role", "EXTERNAL_DNS_POLICY": "upsert-only", "EXTERNAL_DNS_REGISTRY": "noop", "EXTERNAL_DNS_TXT_OWNER_ID": "owner-1", diff --git a/provider/aws.go b/provider/aws.go index f56ff8e1d6..e10f5b3bf3 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -21,10 +21,9 @@ import ( "strings" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/credentials/stscreds" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/route53" - "github.com/aws/aws-sdk-go/service/sts" "github.com/kubernetes-incubator/external-dns/endpoint" "github.com/kubernetes-incubator/external-dns/plan" "github.com/linki/instrumented_http" @@ -84,7 +83,7 @@ type AWSProvider struct { func NewAWSProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, assumeRole string, dryRun bool) (*AWSProvider, error) { config := aws.NewConfig() - config = config.WithHTTPClient( + config.WithHTTPClient( instrumented_http.NewClient(config.HTTPClient, &instrumented_http.Callbacks{ PathProcessor: func(path string) string { parts := strings.Split(path, "/") @@ -102,26 +101,8 @@ func NewAWSProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTy } if assumeRole != "" { - svc := sts.New(session) - - params := &sts.AssumeRoleInput{ - RoleArn: aws.String(assumeRole), - RoleSessionName: aws.String("external-dns"), - } - - log.Infof("Assuming role %s..", aws.StringValue(params.RoleArn)) - - resp, err := svc.AssumeRole(params) - if err != nil { - return nil, err - } - - session.Config.WithCredentials(credentials.NewStaticCredentialsFromCreds(credentials.Value{ - AccessKeyID: aws.StringValue(resp.Credentials.AccessKeyId), - SecretAccessKey: aws.StringValue(resp.Credentials.SecretAccessKey), - SessionToken: aws.StringValue(resp.Credentials.SessionToken), - ProviderName: "assumeRoleProvider", - })) + log.Infof("Assuming role: %s", assumeRole) + config.WithCredentials(stscreds.NewCredentials(session, assumeRole)) } provider := &AWSProvider{ From 790a78292a993e5f5da9f0ff6d923da762a55594 Mon Sep 17 00:00:00 2001 From: Martin Linkhorst Date: Wed, 18 Apr 2018 13:51:28 +0200 Subject: [PATCH 3/3] fix: actually change the correct config object --- provider/aws.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/aws.go b/provider/aws.go index e10f5b3bf3..eb284de46e 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -102,7 +102,7 @@ func NewAWSProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTy if assumeRole != "" { log.Infof("Assuming role: %s", assumeRole) - config.WithCredentials(stscreds.NewCredentials(session, assumeRole)) + session.Config.WithCredentials(stscreds.NewCredentials(session, assumeRole)) } provider := &AWSProvider{