Skip to content

Commit 5ece3e4

Browse files
Merge pull request openshift#7225 from patrickdillon/aws-shared-vpc
CORS-2613: AWS: Cross-account Shared VPC Support
2 parents 15403a5 + f3c2b06 commit 5ece3e4

File tree

23 files changed

+364
-66
lines changed

23 files changed

+364
-66
lines changed

data/data/aws/cluster/main.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,11 @@ module "dns" {
7070
cluster_id = var.cluster_id
7171
tags = local.tags
7272
internal_zone = var.aws_internal_zone
73+
internal_zone_role = var.aws_internal_zone_role
7374
vpc_id = module.vpc.vpc_id
7475
region = var.aws_region
7576
publish_strategy = var.aws_publish_strategy
77+
custom_endpoints = var.custom_endpoints
7678
}
7779

7880
module "vpc" {

data/data/aws/cluster/route53/base.tf

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,27 @@ locals {
99
use_alias = ! local.use_cname
1010
}
1111

12+
provider "aws" {
13+
alias = "private_hosted_zone"
14+
15+
assume_role {
16+
role_arn = var.internal_zone_role
17+
}
18+
19+
region = var.region
20+
21+
skip_region_validation = true
22+
23+
endpoints {
24+
ec2 = lookup(var.custom_endpoints, "ec2", null)
25+
elb = lookup(var.custom_endpoints, "elasticloadbalancing", null)
26+
iam = lookup(var.custom_endpoints, "iam", null)
27+
route53 = lookup(var.custom_endpoints, "route53", null)
28+
s3 = lookup(var.custom_endpoints, "s3", null)
29+
sts = lookup(var.custom_endpoints, "sts", null)
30+
}
31+
}
32+
1233
data "aws_route53_zone" "public" {
1334
count = local.public_endpoints ? 1 : 0
1435

@@ -18,6 +39,8 @@ data "aws_route53_zone" "public" {
1839
}
1940

2041
data "aws_route53_zone" "int" {
42+
provider = aws.private_hosted_zone
43+
2144
zone_id = var.internal_zone == null ? aws_route53_zone.new_int[0].id : var.internal_zone
2245
}
2346

@@ -54,7 +77,8 @@ resource "aws_route53_record" "api_external_alias" {
5477
}
5578

5679
resource "aws_route53_record" "api_internal_alias" {
57-
count = local.use_alias ? 1 : 0
80+
provider = aws.private_hosted_zone
81+
count = local.use_alias ? 1 : 0
5882

5983
zone_id = data.aws_route53_zone.int.zone_id
6084
name = "api-int.${var.cluster_domain}"
@@ -68,7 +92,8 @@ resource "aws_route53_record" "api_internal_alias" {
6892
}
6993

7094
resource "aws_route53_record" "api_external_internal_zone_alias" {
71-
count = local.use_alias ? 1 : 0
95+
provider = aws.private_hosted_zone
96+
count = local.use_alias ? 1 : 0
7297

7398
zone_id = data.aws_route53_zone.int.zone_id
7499
name = "api.${var.cluster_domain}"
@@ -93,7 +118,8 @@ resource "aws_route53_record" "api_external_cname" {
93118
}
94119

95120
resource "aws_route53_record" "api_internal_cname" {
96-
count = local.use_cname ? 1 : 0
121+
provider = aws.private_hosted_zone
122+
count = local.use_cname ? 1 : 0
97123

98124
zone_id = data.aws_route53_zone.int.zone_id
99125
name = "api-int.${var.cluster_domain}"
@@ -104,7 +130,8 @@ resource "aws_route53_record" "api_internal_cname" {
104130
}
105131

106132
resource "aws_route53_record" "api_external_internal_zone_cname" {
107-
count = local.use_cname ? 1 : 0
133+
provider = aws.private_hosted_zone
134+
count = local.use_cname ? 1 : 0
108135

109136
zone_id = data.aws_route53_zone.int.zone_id
110137
name = "api.${var.cluster_domain}"

data/data/aws/cluster/route53/variables.tf

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ variable "internal_zone" {
2828
description = "An existing hosted zone (zone ID) to use for the internal API."
2929
}
3030

31+
variable "internal_zone_role" {
32+
type = string
33+
default = null
34+
description = "(optional) A role to assume when using an existing hosted zone from another account."
35+
}
36+
3137
variable "api_external_lb_dns_name" {
3238
description = "External API's LB DNS name"
3339
type = string
@@ -63,3 +69,16 @@ variable "region" {
6369
type = string
6470
description = "The target AWS region for the cluster."
6571
}
72+
73+
variable "custom_endpoints" {
74+
type = map(string)
75+
76+
description = <<EOF
77+
(optional) Custom AWS endpoints to override existing services.
78+
Check - https://www.terraform.io/docs/providers/aws/guides/custom-service-endpoints.html
79+
80+
Example: `{ "key" = "value", "foo" = "bar" }`
81+
EOF
82+
83+
default = {}
84+
}

data/data/aws/variables-aws.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,13 @@ variable "aws_internal_zone" {
144144
description = "(optional) An existing hosted zone (zone ID) to use for the internal API."
145145
}
146146

147+
variable "aws_internal_zone_role" {
148+
type = string
149+
default = null
150+
description = "(optional) A role to assume when using an existing hosted zone from another account."
151+
}
152+
153+
147154
variable "aws_publish_strategy" {
148155
type = string
149156
description = "The cluster publishing strategy, either Internal or External"

data/data/install.openshift.io_installconfigs.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2267,6 +2267,14 @@ spec:
22672267
the subnets. Leave the hosted zone unset to have the installer
22682268
create the hosted zone on your behalf.
22692269
type: string
2270+
hostedZoneRole:
2271+
description: HostedZoneRole is the ARN of a role to be assumed
2272+
when performing operations on the provided HostedZone. HostedZoneRole
2273+
can be used in a shared VPC scenario when the private hosted
2274+
zone belongs to a different account than the rest of the cluster
2275+
resources. If HostedZoneRole is set, HostedZone must also be
2276+
set.
2277+
type: string
22702278
lbType:
22712279
description: "LBType is an optional field to specify a load balancer
22722280
type. \n When this field is specified, the default ingresscontroller

pkg/asset/cluster/aws/aws.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/pkg/errors"
1212

1313
"github.com/openshift/installer/pkg/asset/installconfig"
14+
awsic "github.com/openshift/installer/pkg/asset/installconfig/aws"
1415
"github.com/openshift/installer/pkg/types"
1516
awstypes "github.com/openshift/installer/pkg/types/aws"
1617
)
@@ -26,6 +27,7 @@ func Metadata(clusterID, infraID string, config *types.InstallConfig) *awstypes.
2627
}},
2728
ServiceEndpoints: config.AWS.ServiceEndpoints,
2829
ClusterDomain: config.ClusterDomain(),
30+
HostedZoneRole: config.AWS.HostedZoneRole,
2931
}
3032
}
3133

@@ -79,7 +81,8 @@ func tagSharedVPCResources(ctx context.Context, clusterID string, installConfig
7981
}
8082

8183
if zone := installConfig.Config.AWS.HostedZone; zone != "" {
82-
route53Client := route53.New(session)
84+
r53cfg := awsic.GetR53ClientCfg(session, installConfig.Config.AWS.HostedZoneRole)
85+
route53Client := route53.New(session, r53cfg)
8386
if _, err := route53Client.ChangeTagsForResourceWithContext(ctx, &route53.ChangeTagsForResourceInput{
8487
ResourceType: aws.String("hostedzone"),
8588
ResourceId: aws.String(zone),

pkg/asset/cluster/tfvars.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ func (t *TerraformVariables) Generate(parents asset.Parents) error {
292292
PrivateSubnets: privateSubnets,
293293
PublicSubnets: publicSubnets,
294294
InternalZone: installConfig.Config.AWS.HostedZone,
295+
InternalZoneRole: installConfig.Config.AWS.HostedZoneRole,
295296
Services: installConfig.Config.AWS.ServiceEndpoints,
296297
Publish: installConfig.Config.Publish,
297298
MasterConfigs: masterConfigs,

pkg/asset/installconfig/aws/mock/awsroute53_generated.go

Lines changed: 13 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/asset/installconfig/aws/route53.go

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"strings"
66

77
"github.com/aws/aws-sdk-go/aws"
8+
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
89
awss "github.com/aws/aws-sdk-go/aws/session"
910
"github.com/aws/aws-sdk-go/service/route53"
1011
"github.com/pkg/errors"
@@ -17,10 +18,10 @@ import (
1718

1819
// API represents the calls made to the API.
1920
type API interface {
20-
GetHostedZone(hostedZone string) (*route53.GetHostedZoneOutput, error)
21-
ValidateZoneRecords(zone *route53.HostedZone, zoneName string, zonePath *field.Path, ic *types.InstallConfig) field.ErrorList
21+
GetHostedZone(hostedZone string, cfg *aws.Config) (*route53.GetHostedZoneOutput, error)
22+
ValidateZoneRecords(zone *route53.HostedZone, zoneName string, zonePath *field.Path, ic *types.InstallConfig, cfg *aws.Config) field.ErrorList
2223
GetBaseDomain(baseDomainName string) (*route53.HostedZone, error)
23-
GetSubDomainDNSRecords(hostedZone *route53.HostedZone, ic *types.InstallConfig) ([]string, error)
24+
GetSubDomainDNSRecords(hostedZone *route53.HostedZone, ic *types.InstallConfig, cfg *aws.Config) ([]string, error)
2425
}
2526

2627
// Client makes calls to the AWS Route53 API.
@@ -37,9 +38,9 @@ func NewClient(ssn *awss.Session) *Client {
3738
}
3839

3940
// GetHostedZone attempts to get the hosted zone from the AWS Route53 instance
40-
func (c *Client) GetHostedZone(hostedZone string) (*route53.GetHostedZoneOutput, error) {
41+
func (c *Client) GetHostedZone(hostedZone string, cfg *aws.Config) (*route53.GetHostedZoneOutput, error) {
4142
// build a new Route53 instance from the same session that made it here
42-
r53 := route53.New(c.ssn)
43+
r53 := route53.New(c.ssn, cfg)
4344

4445
// validate that the hosted zone exists
4546
hostedZoneOutput, err := r53.GetHostedZone(&route53.GetHostedZoneInput{Id: aws.String(hostedZone)})
@@ -50,10 +51,10 @@ func (c *Client) GetHostedZone(hostedZone string) (*route53.GetHostedZoneOutput,
5051
}
5152

5253
// ValidateZoneRecords Attempts to validate each of the candidate HostedZones against the Config
53-
func (c *Client) ValidateZoneRecords(zone *route53.HostedZone, zoneName string, zonePath *field.Path, ic *types.InstallConfig) field.ErrorList {
54+
func (c *Client) ValidateZoneRecords(zone *route53.HostedZone, zoneName string, zonePath *field.Path, ic *types.InstallConfig, cfg *aws.Config) field.ErrorList {
5455
allErrs := field.ErrorList{}
5556

56-
problematicRecords, err := c.GetSubDomainDNSRecords(zone, ic)
57+
problematicRecords, err := c.GetSubDomainDNSRecords(zone, ic, cfg)
5758
if err != nil {
5859
allErrs = append(allErrs, field.InternalError(zonePath,
5960
errors.Wrapf(err, "could not list record sets for domain %q", zoneName)))
@@ -72,15 +73,15 @@ func (c *Client) ValidateZoneRecords(zone *route53.HostedZone, zoneName string,
7273

7374
// GetSubDomainDNSRecords Validates the hostedZone against the cluster domain, and ensures that the
7475
// cluster domain does not have a current record set for the hostedZone
75-
func (c *Client) GetSubDomainDNSRecords(hostedZone *route53.HostedZone, ic *types.InstallConfig) ([]string, error) {
76+
func (c *Client) GetSubDomainDNSRecords(hostedZone *route53.HostedZone, ic *types.InstallConfig, cfg *aws.Config) ([]string, error) {
7677
dottedClusterDomain := ic.ClusterDomain() + "."
7778

7879
// validate that the domain of the hosted zone is the cluster domain or a parent of the cluster domain
7980
if !isHostedZoneDomainParentOfClusterDomain(hostedZone, dottedClusterDomain) {
8081
return nil, errors.Errorf("hosted zone domain %q is not a parent of the cluster domain %q", *hostedZone.Name, dottedClusterDomain)
8182
}
8283

83-
r53 := route53.New(c.ssn)
84+
r53 := route53.New(c.ssn, cfg)
8485

8586
var problematicRecords []string
8687
// validate that the hosted zone does not already have any record sets for the cluster domain
@@ -134,3 +135,14 @@ func (c *Client) GetBaseDomain(baseDomainName string) (*route53.HostedZone, erro
134135
}
135136
return baseDomainZone, nil
136137
}
138+
139+
// GetR53ClientCfg creates a config for the route53 client by determining
140+
// whether it is needed to obtain STS assume role credentials.
141+
func GetR53ClientCfg(sess *awss.Session, roleARN string) *aws.Config {
142+
if roleARN == "" {
143+
return nil
144+
}
145+
146+
creds := stscreds.NewCredentials(sess, roleARN)
147+
return &aws.Config{Credentials: creds}
148+
}

pkg/asset/installconfig/aws/validation.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -385,21 +385,22 @@ func ValidateForProvisioning(client API, ic *types.InstallConfig, metadata *Meta
385385
var zonePath *field.Path
386386
var zone *route53.HostedZone
387387

388-
errors := field.ErrorList{}
389388
allErrs := field.ErrorList{}
389+
r53cfg := GetR53ClientCfg(metadata.session, ic.AWS.HostedZoneRole)
390390

391391
if ic.AWS.HostedZone != "" {
392392
zoneName = ic.AWS.HostedZone
393393
zonePath = field.NewPath("aws", "hostedZone")
394-
zoneOutput, err := client.GetHostedZone(zoneName)
394+
zoneOutput, err := client.GetHostedZone(zoneName, r53cfg)
395395
if err != nil {
396+
errMsg := errors.Wrapf(err, "unable to retrieve hosted zone").Error()
396397
return field.ErrorList{
397-
field.Invalid(zonePath, zoneName, "cannot find hosted zone"),
398+
field.Invalid(zonePath, zoneName, errMsg),
398399
}.ToAggregate()
399400
}
400401

401-
if errors = validateHostedZone(zoneOutput, zonePath, zoneName, metadata); len(errors) > 0 {
402-
allErrs = append(allErrs, errors...)
402+
if errs := validateHostedZone(zoneOutput, zonePath, zoneName, metadata); len(errs) > 0 {
403+
allErrs = append(allErrs, errs...)
403404
}
404405

405406
zone = zoneOutput.HostedZone
@@ -416,8 +417,8 @@ func ValidateForProvisioning(client API, ic *types.InstallConfig, metadata *Meta
416417
zone = baseDomainOutput
417418
}
418419

419-
if errors = client.ValidateZoneRecords(zone, zoneName, zonePath, ic); len(errors) > 0 {
420-
allErrs = append(allErrs, errors...)
420+
if errs := client.ValidateZoneRecords(zone, zoneName, zonePath, ic, r53cfg); len(errs) > 0 {
421+
allErrs = append(allErrs, errs...)
421422
}
422423

423424
return allErrs.ToAggregate()

0 commit comments

Comments
 (0)