diff --git a/pkg/dns/aws/dns.go b/pkg/dns/aws/dns.go index 5d321e007..2b6864496 100644 --- a/pkg/dns/aws/dns.go +++ b/pkg/dns/aws/dns.go @@ -16,6 +16,7 @@ import ( "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/client/metadata" + "github.com/aws/aws-sdk-go/aws/credentials/stscreds" "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" @@ -93,6 +94,9 @@ type Config struct { // that is used by SDK to configure the credentials. SharedCredentialFile string + // RoleARN is an optional ARN to use for the AWS client session. + RoleARN string + // Region is the AWS region ELBs are created in. Region string // ServiceEndpoints is the list of AWS API endpoints to use for @@ -140,6 +144,9 @@ func NewProvider(config Config, operatorReleaseVersion string) (*Provider, error Name: "openshift.io/ingress-operator", Fn: request.MakeAddToUserAgentHandler("openshift.io ingress-operator", operatorReleaseVersion), }) + if config.RoleARN != "" { + sess.Config.WithCredentials(stscreds.NewCredentials(sess, config.RoleARN)) + } if len(region) == 0 { if sess.Config.Region != nil { diff --git a/pkg/dns/split/dns.go b/pkg/dns/split/dns.go new file mode 100644 index 000000000..c055c43f1 --- /dev/null +++ b/pkg/dns/split/dns.go @@ -0,0 +1,58 @@ +package split + +import ( + "reflect" + + iov1 "github.com/openshift/api/operatoringress/v1" + "github.com/openshift/cluster-ingress-operator/pkg/dns" + logf "github.com/openshift/cluster-ingress-operator/pkg/log" + + configv1 "github.com/openshift/api/config/v1" +) + +var ( + _ dns.Provider = &Provider{} + log = logf.Logger.WithName("dns") +) + +// Provider is a dns.Provider that wraps two other providers. The first +// provider is used for public hosted zones, and the second provider is used for +// private hosted zones. +type Provider struct { + private, public dns.Provider + privateZone *configv1.DNSZone +} + +// NewProvider returns a new Provider that wraps the provided wrappers, using +// the first for the public zone and the second for the private zone. +func NewProvider(public, private dns.Provider, privateZone *configv1.DNSZone) *Provider { + return &Provider{ + public: public, + private: private, + privateZone: privateZone, + } +} + +// Ensure calls the Ensure method of one of the wrapped DNS providers. +func (p *Provider) Ensure(record *iov1.DNSRecord, zone configv1.DNSZone) error { + if reflect.DeepEqual(zone, *p.privateZone) { + return p.private.Ensure(record, zone) + } + return p.public.Ensure(record, zone) +} + +// Delete calls the Delete method of one of the wrapped DNS providers. +func (p *Provider) Delete(record *iov1.DNSRecord, zone configv1.DNSZone) error { + if reflect.DeepEqual(zone, *p.privateZone) { + return p.private.Delete(record, zone) + } + return p.public.Delete(record, zone) +} + +// Replace calls the Replace method of one of the wrapped DNS providers. +func (p *Provider) Replace(record *iov1.DNSRecord, zone configv1.DNSZone) error { + if reflect.DeepEqual(zone, *p.privateZone) { + return p.private.Replace(record, zone) + } + return p.public.Replace(record, zone) +} diff --git a/pkg/dns/split/dns_test.go b/pkg/dns/split/dns_test.go new file mode 100644 index 000000000..f2a669f83 --- /dev/null +++ b/pkg/dns/split/dns_test.go @@ -0,0 +1,126 @@ +package split_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + configv1 "github.com/openshift/api/config/v1" + iov1 "github.com/openshift/api/operatoringress/v1" + + "github.com/openshift/cluster-ingress-operator/pkg/dns" + splitdns "github.com/openshift/cluster-ingress-operator/pkg/dns/split" +) + +// TestSplitDNSProvider verifies that the split DNS provider dispatches to the +// public or private provider as appropriate for the DNS zone. +func TestSplitDNSProvider(t *testing.T) { + var ( + // ch is a channel that is used in the fake public and private + // providers to record which one is called. + ch = make(chan string, 6) + // getResult reads and returns one item from ch, or returns the + // empty string if ch is empty. + getResult = func() string { + var result string + select { + case result = <-ch: + default: + } + return result + } + // publicProvider is a fake dns.Provider for the public zone. + publicProvider = newFakeProvider("public", ch) + // privateProvider is a fake dns.Provider for the private zone. + privateProvider = newFakeProvider("private", ch) + // publicZoneWithID is a public zone that is defined by ID. + publicZoneWithID = configv1.DNSZone{ID: "public_zone"} + // privateZoneWithID is a private zone that is defined by ID. + privateZoneWithID = configv1.DNSZone{ID: "private_zone"} + // publicZoneWithTags is a public zone that is defined by tags. + publicZoneWithTags = configv1.DNSZone{Tags: map[string]string{"zone": "public"}} + // privateZoneWithID is a private zone that is defined by tags. + privateZoneWithTags = configv1.DNSZone{Tags: map[string]string{"zone": "private"}} + ) + testCases := []struct { + name string + publicZone configv1.DNSZone + privateZone configv1.DNSZone + publishToZone configv1.DNSZone + expect string + }{ + { + name: "publish to public zone specified by id", + publicZone: publicZoneWithID, + privateZone: privateZoneWithID, + publishToZone: publicZoneWithID, + expect: "public", + }, + { + name: "publish to private zone specified by id", + publicZone: publicZoneWithID, + privateZone: privateZoneWithID, + publishToZone: privateZoneWithID, + expect: "private", + }, + { + name: "publish to public zone specified by tags", + publicZone: publicZoneWithTags, + privateZone: privateZoneWithID, + publishToZone: publicZoneWithTags, + expect: "public", + }, + { + name: "publish to private zone specified by tags", + publicZone: publicZoneWithTags, + privateZone: privateZoneWithTags, + publishToZone: privateZoneWithTags, + expect: "private", + }, + { + name: "publish to other zone should fall back to the public zone", + publicZone: publicZoneWithID, + privateZone: privateZoneWithID, + publishToZone: configv1.DNSZone{ID: "other_zone"}, + expect: "public", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + provider := splitdns.NewProvider(publicProvider, privateProvider, &tc.privateZone) + assert.NoError(t, provider.Ensure(&iov1.DNSRecord{}, tc.publishToZone)) + assert.Equal(t, tc.expect, getResult()) + assert.NoError(t, provider.Replace(&iov1.DNSRecord{}, tc.publishToZone)) + assert.Equal(t, tc.expect, getResult()) + assert.NoError(t, provider.Delete(&iov1.DNSRecord{}, tc.publishToZone)) + assert.Equal(t, tc.expect, getResult()) + assert.Empty(t, ch) + }) + } + +} + +var _ dns.Provider = &fakeProvider{} + +type fakeProvider struct { + name string + recorder chan string +} + +func (p *fakeProvider) Ensure(record *iov1.DNSRecord, zone configv1.DNSZone) error { + p.recorder <- p.name + return nil +} +func (p *fakeProvider) Delete(record *iov1.DNSRecord, zone configv1.DNSZone) error { + p.recorder <- p.name + return nil +} +func (p *fakeProvider) Replace(record *iov1.DNSRecord, zone configv1.DNSZone) error { + p.recorder <- p.name + return nil +} + +// newFakeProvider returns a new dns.Provider that records invocations. +func newFakeProvider(name string, ch chan string) dns.Provider { + return &fakeProvider{name, ch} +} diff --git a/pkg/operator/controller/dns/controller.go b/pkg/operator/controller/dns/controller.go index 6bd5494e9..02fe29207 100644 --- a/pkg/operator/controller/dns/controller.go +++ b/pkg/operator/controller/dns/controller.go @@ -22,6 +22,7 @@ import ( ibm "github.com/openshift/cluster-ingress-operator/pkg/dns/ibm" ibmprivatedns "github.com/openshift/cluster-ingress-operator/pkg/dns/ibm/private" ibmpublicdns "github.com/openshift/cluster-ingress-operator/pkg/dns/ibm/public" + splitdns "github.com/openshift/cluster-ingress-operator/pkg/dns/split" logf "github.com/openshift/cluster-ingress-operator/pkg/log" "github.com/openshift/cluster-ingress-operator/pkg/manifests" "github.com/openshift/cluster-ingress-operator/pkg/operator/controller" @@ -110,6 +111,10 @@ type Config struct { CredentialsRequestNamespace string DNSRecordNamespaces []string OperatorReleaseVersion string + + // SharedVPCEnabled indicates whether the "SharedVPC" feature gate is + // enabled. + SharedVPCEnabled bool } type reconciler struct { @@ -662,7 +667,21 @@ func (r *reconciler) createDNSProvider(dnsConfig *configv1.DNS, platformStatus * if err != nil { return nil, fmt.Errorf("failed to create AWS DNS manager: %v", err) } - dnsProvider = provider + var roleARN string + if dnsConfig.Spec.Platform.AWS != nil { + roleARN = dnsConfig.Spec.Platform.AWS.PrivateZoneIAMRole + } + if roleARN != "" && r.config.SharedVPCEnabled { + cfg := cfg + cfg.RoleARN = roleARN + privateProvider, err := awsdns.NewProvider(cfg, r.config.OperatorReleaseVersion) + if err != nil { + return nil, fmt.Errorf("failed to create AWS DNS manager for shared VPC: %v", err) + } + dnsProvider = splitdns.NewProvider(provider, privateProvider, dnsConfig.Spec.PrivateZone) + } else { + dnsProvider = provider + } case configv1.AzurePlatformType: environment := platformStatus.Azure.CloudName if environment == "" { diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 9a2ad7a71..4c3e177d9 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -128,6 +128,7 @@ func New(config operatorconfig.Config, kubeConfig *rest.Config) (*Operator, erro return nil, err } // example of future featuregate read and usage to set a variable to pass to a controller + sharedVPCEnabled := featureGates.Enabled(configv1.FeatureGateGatewayAPI) gatewayAPIEnabled := featureGates.Enabled(configv1.FeatureGateGatewayAPI) // Set up an operator manager for the operator namespace. @@ -240,6 +241,7 @@ func New(config operatorconfig.Config, kubeConfig *rest.Config) (*Operator, erro operatorcontroller.DefaultOperandNamespace, }, OperatorReleaseVersion: config.OperatorReleaseVersion, + SharedVPCEnabled: sharedVPCEnabled, }); err != nil { return nil, fmt.Errorf("failed to create dns controller: %v", err) }