Skip to content

Commit 6625152

Browse files
committed
fix assume role tgb
1 parent db09cb9 commit 6625152

File tree

10 files changed

+342
-93
lines changed

10 files changed

+342
-93
lines changed

docs/guide/targetgroupbinding/targetgroupbinding.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,59 @@ The way to do that is assuming a role from such account. The following spec fiel
118118
* `assumeRoleExternalId`: the external ID for the assume role operation. Optional, but recommended. It helps you to prevent the confused deputy problem ( https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html )
119119

120120

121+
```yaml
122+
apiVersion: elbv2.k8s.aws/v1beta1
123+
kind: TargetGroupBinding
124+
metadata:
125+
name: peered-tg
126+
namespace: nlb-game-2048-1
127+
spec:
128+
assumeRoleExternalId: very-secret-string-2
129+
iamRoleArnToAssume: arn:aws:iam::155642222660:role/tg-management-role
130+
networking:
131+
ingress:
132+
- from:
133+
- securityGroup:
134+
groupID: sg-0b6a41a2fd959623f
135+
ports:
136+
- port: 80
137+
protocol: TCP
138+
serviceRef:
139+
name: service-2048
140+
port: 80
141+
targetGroupARN: arn:aws:elasticloadbalancing:us-west-2:155642222660:targetgroup/peered-tg/6a4ecf7bfae473c1
142+
```
143+
144+
In order to use this feature, the source account (The cluster owner) must allow the controller role to assume role. By default, the installed permissions
145+
_do not_ allow this. Augment the load balancer controller role by adding:
146+
147+
```json
148+
{
149+
"Effect": "Allow",
150+
"Action": [
151+
"sts:AssumeRole"
152+
],
153+
"Resource": "*"
154+
}
155+
```
156+
157+
In the target account, which owns the Target Group the assumed role must allow for Target Group Management, at the minimum:
158+
159+
```json
160+
{
161+
"Effect": "Allow",
162+
"Action": [
163+
"elasticloadbalancing:RegisterTargets",
164+
"elasticloadbalancing:DeregisterTargets",
165+
"elasticloadbalancing:DescribeTargetHealth",
166+
"elasticloadbalancing:DescribeTargetGroups"
167+
],
168+
"Resource": "*"
169+
}
170+
```
171+
172+
173+
121174
## Sample YAML
122175

123176
```yaml

helm/aws-load-balancer-controller/crds/crds.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,15 @@ spec:
317317
spec:
318318
description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding
319319
properties:
320+
assumeRoleExternalId:
321+
description: IAM Role ARN to assume when calling AWS APIs. Needed
322+
to assume a role in another account and prevent the confused deputy
323+
problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html
324+
type: string
325+
iamRoleArnToAssume:
326+
description: IAM Role ARN to assume when calling AWS APIs. Useful
327+
if the target group is in a different AWS account
328+
type: string
320329
multiClusterTargetGroup:
321330
description: MultiClusterTargetGroup Denotes if the TargetGroup is
322331
shared among multiple clusters
@@ -494,6 +503,15 @@ spec:
494503
spec:
495504
description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding
496505
properties:
506+
assumeRoleExternalId:
507+
description: IAM Role ARN to assume when calling AWS APIs. Needed
508+
to assume a role in another account and prevent the confused deputy
509+
problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html
510+
type: string
511+
iamRoleArnToAssume:
512+
description: IAM Role ARN to assume when calling AWS APIs. Useful
513+
if the target group is in a different AWS account
514+
type: string
497515
ipAddressType:
498516
description: ipAddressType specifies whether the target group is of
499517
type IPv4 or IPv6. If unspecified, it will be automatically inferred.

pkg/aws/aws_config.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package aws
2+
3+
import (
4+
"context"
5+
"github.com/aws/aws-sdk-go-v2/aws"
6+
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
7+
"github.com/aws/aws-sdk-go-v2/aws/ratelimit"
8+
"github.com/aws/aws-sdk-go-v2/aws/retry"
9+
"github.com/aws/aws-sdk-go-v2/config"
10+
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
11+
smithymiddleware "github.com/aws/smithy-go/middleware"
12+
"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/throttle"
13+
awsmetrics "sigs.k8s.io/aws-load-balancer-controller/pkg/metrics/aws"
14+
"sigs.k8s.io/aws-load-balancer-controller/pkg/version"
15+
)
16+
17+
const (
18+
userAgent = "elbv2.k8s.aws"
19+
)
20+
21+
func NewAWSConfigGenerator(cfg CloudConfig, ec2IMDSEndpointMode imds.EndpointModeState, metricsCollector *awsmetrics.Collector) AWSConfigGenerator {
22+
return &awsConfigGeneratorImpl{
23+
cfg: cfg,
24+
ec2IMDSEndpointMode: ec2IMDSEndpointMode,
25+
metricsCollector: metricsCollector,
26+
}
27+
28+
}
29+
30+
// AWSConfigGenerator is responsible for generating an aws config based on the running environment
31+
type AWSConfigGenerator interface {
32+
GenerateAWSConfig(optFns ...func(*config.LoadOptions) error) (aws.Config, error)
33+
}
34+
35+
type awsConfigGeneratorImpl struct {
36+
cfg CloudConfig
37+
ec2IMDSEndpointMode imds.EndpointModeState
38+
metricsCollector *awsmetrics.Collector
39+
}
40+
41+
func (gen *awsConfigGeneratorImpl) GenerateAWSConfig(optFns ...func(*config.LoadOptions) error) (aws.Config, error) {
42+
43+
defaultOpts := []func(*config.LoadOptions) error{
44+
config.WithRegion(gen.cfg.Region),
45+
config.WithRetryer(func() aws.Retryer {
46+
return retry.NewStandard(func(o *retry.StandardOptions) {
47+
o.RateLimiter = ratelimit.None
48+
o.MaxAttempts = gen.cfg.MaxRetries
49+
})
50+
}),
51+
config.WithEC2IMDSEndpointMode(gen.ec2IMDSEndpointMode),
52+
config.WithAPIOptions([]func(stack *smithymiddleware.Stack) error{
53+
awsmiddleware.AddUserAgentKeyValue(userAgent, version.GitVersion),
54+
}),
55+
}
56+
57+
defaultOpts = append(defaultOpts, optFns...)
58+
59+
awsConfig, err := config.LoadDefaultConfig(context.TODO(),
60+
defaultOpts...,
61+
)
62+
63+
if err != nil {
64+
return aws.Config{}, err
65+
}
66+
67+
if gen.cfg.ThrottleConfig != nil {
68+
throttler := throttle.NewThrottler(gen.cfg.ThrottleConfig)
69+
awsConfig.APIOptions = append(awsConfig.APIOptions, func(stack *smithymiddleware.Stack) error {
70+
return throttle.WithSDKRequestThrottleMiddleware(throttler)(stack)
71+
})
72+
}
73+
74+
if gen.metricsCollector != nil {
75+
awsConfig.APIOptions = awsmetrics.WithSDKMetricCollector(gen.metricsCollector, awsConfig.APIOptions)
76+
}
77+
78+
return awsConfig, nil
79+
}
80+
81+
var _ AWSConfigGenerator = &awsConfigGeneratorImpl{}

pkg/aws/cloud.go

Lines changed: 19 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,11 @@ import (
1010
"sync"
1111
"time"
1212

13-
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
14-
"github.com/aws/aws-sdk-go-v2/aws/ratelimit"
15-
"github.com/aws/aws-sdk-go-v2/aws/retry"
1613
"github.com/aws/aws-sdk-go-v2/config"
1714
"github.com/aws/aws-sdk-go-v2/credentials"
1815
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
1916
"github.com/aws/aws-sdk-go-v2/service/sts"
2017

21-
smithymiddleware "github.com/aws/smithy-go/middleware"
22-
"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/throttle"
23-
"sigs.k8s.io/aws-load-balancer-controller/pkg/version"
24-
2518
"github.com/aws/aws-sdk-go-v2/aws"
2619
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
2720
"github.com/aws/aws-sdk-go-v2/service/ec2"
@@ -35,7 +28,6 @@ import (
3528
)
3629

3730
const (
38-
userAgent = "elbv2.k8s.aws"
3931
cacheTTLBufferTime = 30 * time.Second
4032
)
4133

@@ -81,29 +73,11 @@ func NewCloud(cfg CloudConfig, clusterName string, metricsCollector *aws_metrics
8173
}
8274
cfg.Region = region
8375
}
84-
awsConfig, err := config.LoadDefaultConfig(context.TODO(),
85-
config.WithRegion(cfg.Region),
86-
config.WithRetryer(func() aws.Retryer {
87-
return retry.NewStandard(func(o *retry.StandardOptions) {
88-
o.RateLimiter = ratelimit.None
89-
o.MaxAttempts = cfg.MaxRetries
90-
})
91-
}),
92-
config.WithEC2IMDSEndpointMode(ec2IMDSEndpointMode),
93-
config.WithAPIOptions([]func(stack *smithymiddleware.Stack) error{
94-
awsmiddleware.AddUserAgentKeyValue(userAgent, version.GitVersion),
95-
}),
96-
)
97-
98-
if cfg.ThrottleConfig != nil {
99-
throttler := throttle.NewThrottler(cfg.ThrottleConfig)
100-
awsConfig.APIOptions = append(awsConfig.APIOptions, func(stack *smithymiddleware.Stack) error {
101-
return throttle.WithSDKRequestThrottleMiddleware(throttler)(stack)
102-
})
103-
}
10476

105-
if metricsCollector != nil {
106-
awsConfig.APIOptions = aws_metrics.WithSDKMetricCollector(metricsCollector, awsConfig.APIOptions)
77+
awsConfigGenerator := NewAWSConfigGenerator(cfg, ec2IMDSEndpointMode, metricsCollector)
78+
awsConfig, err := awsConfigGenerator.GenerateAWSConfig()
79+
if err != nil {
80+
return nil, errors.Wrap(err, "Unable to generate AWS config")
10781
}
10882

10983
if awsClientsProvider == nil {
@@ -132,6 +106,8 @@ func NewCloud(cfg CloudConfig, clusterName string, metricsCollector *aws_metrics
132106
shield: services.NewShield(awsClientsProvider),
133107
rgt: services.NewRGT(awsClientsProvider),
134108

109+
awsConfigGenerator: awsConfigGenerator,
110+
135111
assumeRoleElbV2Cache: cache.NewExpiring(),
136112

137113
awsClientsProvider: awsClientsProvider,
@@ -229,6 +205,8 @@ type defaultCloud struct {
229205

230206
clusterName string
231207

208+
awsConfigGenerator AWSConfigGenerator
209+
232210
// A cache holding elbv2 clients that are assuming a role.
233211
assumeRoleElbV2Cache *cache.Expiring
234212
// assumeRoleElbV2CacheMutex protects assumeRoleElbV2Cache
@@ -251,31 +229,33 @@ func (c *defaultCloud) GetAssumedRoleELBV2(ctx context.Context, assumeRoleArn st
251229
if exists {
252230
return assumedRoleELBV2.(services.ELBV2), nil
253231
}
254-
c.logger.Info("awsCloud", "method", "GetAssumedRoleELBV2", "AssumeRoleArn", assumeRoleArn, "externalId", externalId)
232+
c.logger.Info("Constructing new elbv2 client", "AssumeRoleArn", assumeRoleArn, "externalId", externalId)
255233

256-
existingAwsConfig, _ := c.awsClientsProvider.GetAWSConfig(ctx, "GetAWSConfigForIAMRoleImpersonation")
234+
stsClient, err := c.awsClientsProvider.GetSTSClient(ctx, "AssumeRole")
235+
if err != nil {
236+
// This should never happen, but let's be forward-looking.
237+
return nil, err
238+
}
257239

258-
sourceAccount := sts.NewFromConfig(*existingAwsConfig)
259-
response, err := sourceAccount.AssumeRole(ctx, &sts.AssumeRoleInput{
240+
response, err := stsClient.AssumeRole(ctx, &sts.AssumeRoleInput{
260241
RoleArn: aws.String(assumeRoleArn),
261242
RoleSessionName: aws.String(generateAssumeRoleSessionName(c.clusterName)),
262243
ExternalId: aws.String(externalId),
263244
})
264245
if err != nil {
265-
c.logger.Error(err, "Unable to assume target role, %v")
246+
c.logger.Error(err, "Unable to assume target role", "roleArn", assumeRoleArn)
266247
return nil, err
267248
}
268249
assumedRoleCreds := response.Credentials
269250
newCreds := credentials.NewStaticCredentialsProvider(*assumedRoleCreds.AccessKeyId, *assumedRoleCreds.SecretAccessKey, *assumedRoleCreds.SessionToken)
270-
newAwsConfig, err := config.LoadDefaultConfig(ctx, config.WithRegion(c.cfg.Region), config.WithCredentialsProvider(newCreds))
251+
newAwsConfig, err := c.awsConfigGenerator.GenerateAWSConfig(config.WithCredentialsProvider(newCreds))
271252
if err != nil {
272-
c.logger.Error(err, "Unable to load static credentials for service client config, %v. Attempting to use default client")
253+
c.logger.Error(err, "Create new service client config service client config", "roleArn", assumeRoleArn)
273254
return nil, err
274255
}
275256

276257
cacheTTL := assumedRoleCreds.Expiration.Sub(time.Now())
277-
existingAwsConfig.Credentials = newAwsConfig.Credentials
278-
elbv2WithAssumedRole := services.NewELBV2(c.awsClientsProvider, c)
258+
elbv2WithAssumedRole := services.NewELBV2FromStaticClient(c.awsClientsProvider.GenerateNewELBv2Client(newAwsConfig), c)
279259

280260
c.assumeRoleElbV2CacheMutex.Lock()
281261
defer c.assumeRoleElbV2CacheMutex.Unlock()

pkg/aws/provider/default_aws_clients_provider.go

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
99
"github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi"
1010
"github.com/aws/aws-sdk-go-v2/service/shield"
11+
"github.com/aws/aws-sdk-go-v2/service/sts"
1112
"github.com/aws/aws-sdk-go-v2/service/wafregional"
1213
"github.com/aws/aws-sdk-go-v2/service/wafv2"
1314
"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/endpoints"
@@ -21,29 +22,30 @@ type defaultAWSClientsProvider struct {
2122
wafRegionClient *wafregional.Client
2223
shieldClient *shield.Client
2324
rgtClient *resourcegroupstaggingapi.Client
25+
stsClient *sts.Client
2426

25-
awsConfig *aws.Config
27+
// used for dynamic creation of ELBv2 client
28+
elbv2CustomEndpoint *string
2629
}
2730

28-
func NewDefaultAWSClientsProvider(cfg aws.Config, endpointsResolver *endpoints.Resolver) (*defaultAWSClientsProvider, error) {
31+
func NewDefaultAWSClientsProvider(cfg aws.Config, endpointsResolver *endpoints.Resolver) (AWSClientsProvider, error) {
2932
ec2CustomEndpoint := endpointsResolver.EndpointFor(ec2.ServiceID)
3033
elbv2CustomEndpoint := endpointsResolver.EndpointFor(elasticloadbalancingv2.ServiceID)
3134
acmCustomEndpoint := endpointsResolver.EndpointFor(acm.ServiceID)
3235
wafv2CustomEndpoint := endpointsResolver.EndpointFor(wafv2.ServiceID)
3336
wafregionalCustomEndpoint := endpointsResolver.EndpointFor(wafregional.ServiceID)
3437
shieldCustomEndpoint := endpointsResolver.EndpointFor(shield.ServiceID)
3538
rgtCustomEndpoint := endpointsResolver.EndpointFor(resourcegroupstaggingapi.ServiceID)
39+
stsCustomEndpoint := endpointsResolver.EndpointFor(sts.ServiceID)
3640

3741
ec2Client := ec2.NewFromConfig(cfg, func(o *ec2.Options) {
3842
if ec2CustomEndpoint != nil {
3943
o.BaseEndpoint = ec2CustomEndpoint
4044
}
4145
})
42-
elbv2Client := elasticloadbalancingv2.NewFromConfig(cfg, func(o *elasticloadbalancingv2.Options) {
43-
if elbv2CustomEndpoint != nil {
44-
o.BaseEndpoint = elbv2CustomEndpoint
45-
}
46-
})
46+
47+
elbv2Client := generateNewELBv2ClientHelper(cfg, elbv2CustomEndpoint)
48+
4749
acmClient := acm.NewFromConfig(cfg, func(o *acm.Options) {
4850
if acmCustomEndpoint != nil {
4951
o.BaseEndpoint = acmCustomEndpoint
@@ -68,6 +70,12 @@ func NewDefaultAWSClientsProvider(cfg aws.Config, endpointsResolver *endpoints.R
6870
}
6971
})
7072

73+
stsClient := sts.NewFromConfig(cfg, func(o *sts.Options) {
74+
if stsCustomEndpoint != nil {
75+
o.BaseEndpoint = stsCustomEndpoint
76+
}
77+
})
78+
7179
return &defaultAWSClientsProvider{
7280
ec2Client: ec2Client,
7381
elbv2Client: elbv2Client,
@@ -76,8 +84,9 @@ func NewDefaultAWSClientsProvider(cfg aws.Config, endpointsResolver *endpoints.R
7684
wafRegionClient: wafregionalClient,
7785
shieldClient: shieldClient,
7886
rgtClient: rgtClient,
87+
stsClient: stsClient,
7988

80-
awsConfig: &cfg,
89+
elbv2CustomEndpoint: elbv2CustomEndpoint,
8190
}, nil
8291
}
8392

@@ -112,6 +121,18 @@ func (p *defaultAWSClientsProvider) GetRGTClient(ctx context.Context, operationN
112121
return p.rgtClient, nil
113122
}
114123

115-
func (p *defaultAWSClientsProvider) GetAWSConfig(ctx context.Context, operationName string) (*aws.Config, error) {
116-
return p.awsConfig, nil
124+
func (p *defaultAWSClientsProvider) GetSTSClient(ctx context.Context, operationName string) (*sts.Client, error) {
125+
return p.stsClient, nil
126+
}
127+
128+
func (p *defaultAWSClientsProvider) GenerateNewELBv2Client(cfg aws.Config) *elasticloadbalancingv2.Client {
129+
return generateNewELBv2ClientHelper(cfg, p.elbv2CustomEndpoint)
130+
}
131+
132+
func generateNewELBv2ClientHelper(cfg aws.Config, elbv2CustomEndpoint *string) *elasticloadbalancingv2.Client {
133+
return elasticloadbalancingv2.NewFromConfig(cfg, func(o *elasticloadbalancingv2.Options) {
134+
if elbv2CustomEndpoint != nil {
135+
o.BaseEndpoint = elbv2CustomEndpoint
136+
}
137+
})
117138
}

0 commit comments

Comments
 (0)