Skip to content

Commit 388f1df

Browse files
authored
Merge pull request #4026 from zac-nixon/znixon/multi-acc-tgb
Follow up for 3691 [TargetGroupBindings can now manipulate target groups from different aws accounts]
2 parents 4d5ce64 + dfae145 commit 388f1df

27 files changed

+636
-294
lines changed

apis/elbv2/v1alpha1/targetgroupbinding_types.go

+8
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ type TargetGroupBindingSpec struct {
128128
// networking provides the networking setup for ELBV2 LoadBalancer to access targets in TargetGroup.
129129
// +optional
130130
Networking *TargetGroupBindingNetworking `json:"networking,omitempty"`
131+
132+
// IAM Role ARN to assume when calling AWS APIs. Useful if the target group is in a different AWS account
133+
// +optional
134+
IamRoleArnToAssume string `json:"iamRoleArnToAssume,omitempty"`
135+
136+
// IAM Role ARN to assume when calling AWS APIs. Needed to assume a role in another account and prevent the confused deputy problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html
137+
// +optional
138+
AssumeRoleExternalId string `json:"assumeRoleExternalId,omitempty"`
131139
}
132140

133141
// TargetGroupBindingStatus defines the observed state of TargetGroupBinding

apis/elbv2/v1beta1/targetgroupbinding_types.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,11 @@ type TargetGroupBindingSpec struct {
160160

161161
// IAM Role ARN to assume when calling AWS APIs. Useful if the target group is in a different AWS account
162162
// +optional
163-
IamRoleArnToAssume string `json:"-"` // `json:"iamRoleArnToAssume,omitempty"`
163+
IamRoleArnToAssume string `json:"iamRoleArnToAssume,omitempty"`
164164

165165
// IAM Role ARN to assume when calling AWS APIs. Needed to assume a role in another account and prevent the confused deputy problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html
166166
// +optional
167-
AssumeRoleExternalId string `json:"-"` // `json:"assumeRoleExternalId,omitempty"`
167+
AssumeRoleExternalId string `json:"assumeRoleExternalId,omitempty"`
168168
}
169169

170170
// TargetGroupBindingStatus defines the observed state of TargetGroupBinding

config/crd/bases/elbv2.k8s.aws_targetgroupbindings.yaml

+18
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ spec:
6565
spec:
6666
description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding
6767
properties:
68+
assumeRoleExternalId:
69+
description: IAM Role ARN to assume when calling AWS APIs. Needed
70+
to assume a role in another account and prevent the confused deputy
71+
problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html
72+
type: string
73+
iamRoleArnToAssume:
74+
description: IAM Role ARN to assume when calling AWS APIs. Useful
75+
if the target group is in a different AWS account
76+
type: string
6877
multiClusterTargetGroup:
6978
description: MultiClusterTargetGroup Denotes if the TargetGroup is
7079
shared among multiple clusters
@@ -242,6 +251,15 @@ spec:
242251
spec:
243252
description: TargetGroupBindingSpec defines the desired state of TargetGroupBinding
244253
properties:
254+
assumeRoleExternalId:
255+
description: IAM Role ARN to assume when calling AWS APIs. Needed
256+
to assume a role in another account and prevent the confused deputy
257+
problem. https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html
258+
type: string
259+
iamRoleArnToAssume:
260+
description: IAM Role ARN to assume when calling AWS APIs. Useful
261+
if the target group is in a different AWS account
262+
type: string
245263
ipAddressType:
246264
description: ipAddressType specifies whether the target group is of
247265
type IPv4 or IPv6. If unspecified, it will be automatically inferred.

docs/guide/targetgroupbinding/spec.md

-9
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,6 @@ Kubernetes meta/v1.ObjectMeta
5555
<table>
5656
<tr><td><code>annotations</code></td><td>
5757

58-
<table>
59-
<tr><td><code>alb.ingress.kubernetes.io/IamRoleArnToAssume</code><br><i>string</i></td>
60-
<td><i>(Optional)</i> In case the target group is in a differet AWS account, you put here the role that needs to be assumed in order to manipulate the target group.
61-
</td></tr>
62-
<tr><td><code>alb.ingress.kubernetes.io/AssumeRoleExternalId</code><br><i>string</i></td>
63-
<td><i>(Optional)</i> The external ID for the assume role operation. Optional, but recommended. It helps you to prevent the <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html" target="_blank">confused deputy problem</a>.
64-
</td></tr>
65-
</table>
66-
6758
<tr><td colspan=2>
6859
Refer to the Kubernetes API documentation for the other fields of the
6960
<code>metadata</code> field.

docs/guide/targetgroupbinding/targetgroupbinding.md

+103-6
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,108 @@ spec:
112112
### AssumeRole
113113

114114
Sometimes the AWS LoadBalancer controller needs to manipulate target groups from different AWS accounts.
115-
The way to do that is assuming a role from such account. There are annotations that can help you with that:
115+
The way to do that is assuming a role from such an account. The following spec fields help you with that.
116116

117-
* `alb.ingress.kubernetes.io/IamRoleArnToAssume`: the ARN that you need to assume
118-
* `alb.ingress.kubernetes.io/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 )
117+
* `iamRoleArnToAssume`: the ARN that you need to assume
118+
* `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 )
119+
120+
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 the following examples, we will refer to Cluster Owner (CO) and Target Group Owner (TGO) accounts.
145+
146+
First, in the TGO account creates a role that will allow the AWS LBC in the CO account to assume it.
147+
For improved security, we only allow the AWS LBC role in CO account to assume the role.
148+
149+
```json
150+
{
151+
"Version": "2012-10-17",
152+
"Statement": [
153+
{
154+
"Sid": "",
155+
"Effect": "Allow",
156+
"Principal": {
157+
"AWS": "arn:aws:iam::565768096483:role/eksctl-awslbc-loadtest-addon-iamserviceaccoun-Role1-13RdJCMqV6p2"
158+
},
159+
"Action": "sts:AssumeRole",
160+
"Condition": {
161+
"StringEquals": {
162+
"sts:ExternalId": "very-secret-string"
163+
}
164+
}
165+
}
166+
]
167+
}
168+
```
169+
170+
Next, still in the TGO account we need to add the following permissions to the Role created in the first step.
171+
172+
```json
173+
{
174+
"Version": "2012-10-17",
175+
"Statement": [
176+
{
177+
"Sid": "VisualEditor0",
178+
"Effect": "Allow",
179+
"Action": [
180+
"elasticloadbalancing:RegisterTargets",
181+
"elasticloadbalancing:DeregisterTargets"
182+
],
183+
"Resource": [
184+
"arn:aws:elasticloadbalancing:us-west-2:155642222660:targetgroup/tg1/*",
185+
"arn:aws:elasticloadbalancing:us-west-2:155642222660:targetgroup/tg2/*"
186+
// add more here //
187+
]
188+
},
189+
{
190+
"Sid": "VisualEditor1",
191+
"Effect": "Allow",
192+
"Action": [
193+
"elasticloadbalancing:DescribeTargetGroups",
194+
"elasticloadbalancing:DescribeTargetHealth"
195+
],
196+
"Resource": "*"
197+
}
198+
]
199+
}
200+
```
201+
202+
203+
Next, in the CO account, we need to allow the AWS LBC to perform the AssumeRole call.
204+
By default, this permission is not a part of the standard IAM policy that is vended with the LBC installation scripts.
205+
For improved security, it is possible to scope the AssumeRole permissions down to only roles that you know ahead of time the
206+
LBC will need to Assume.
207+
208+
```json
209+
{
210+
"Effect": "Allow",
211+
"Action": [
212+
"sts:AssumeRole"
213+
],
214+
"Resource": "*"
215+
}
216+
```
119217

120218

121219
## Sample YAML
@@ -125,10 +223,9 @@ apiVersion: elbv2.k8s.aws/v1beta1
125223
kind: TargetGroupBinding
126224
metadata:
127225
name: my-tgb
128-
annotations:
129-
alb.ingress.kubernetes.io/IamRoleArnToAssume: "arn:aws:iam::999999999999:role/alb-controller-policy-to-assume"
130-
alb.ingress.kubernetes.io/AssumeRoleExternalId: "some-magic-string"
131226
spec:
227+
iamRoleArnToAssume: "arn:aws:iam::999999999999:role/alb-controller-policy-to-assume"
228+
assumeRoleExternalId: "some-magic-string"
132229
...
133230
```
134231

go.sum

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0 h1:ta62lid9JkIpKZtZZXSj6rP2AqY
6060
github.com/aws/aws-sdk-go-v2/service/ec2 v1.173.0/go.mod h1:o6QDjdVKpP5EF0dp/VlvqckzuSDATr1rLdHt3A5m0YY=
6161
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1 h1:L9Wt9zgtoYKIlaeFTy+EztGjL4oaXBBGtVXA+jaeYko=
6262
github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.43.1/go.mod h1:yxzLdxt7bVGvIOPYIKFtiaJCJnx2ChlIIvlhW4QgI6M=
63+
github.com/aws/aws-sdk-go-v2/service/iam v1.36.3/go.mod h1:HSvujsK8xeEHMIB18oMXjSfqaN9cVqpo/MtHJIksQRk=
6364
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
6465
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
6566
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=

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

+18
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.

main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func main() {
9090
awsMetricsCollector = awsmetrics.NewCollector(metrics.Registry)
9191
}
9292

93-
cloud, err := aws.NewCloud(controllerCFG.AWSConfig, awsMetricsCollector, ctrl.Log, nil)
93+
cloud, err := aws.NewCloud(controllerCFG.AWSConfig, controllerCFG.ClusterName, awsMetricsCollector, ctrl.Log, nil)
9494
if err != nil {
9595
setupLog.Error(err, "unable to initialize AWS cloud")
9696
os.Exit(1)

pkg/aws/aws_config.go

+81
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{}

0 commit comments

Comments
 (0)