Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
const (
Provider = util.AWSCloudProviderName
ProviderConfigKey = "aws.conf"
CABundleKey = "ca-bundle.pem"
)

func AWSKMSCredsSecret(controlPlaneNamespace string) *corev1.Secret {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1"
"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane"
"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/cloud/aws"
"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/cloud/azure"
"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/cloud/openstack"
kubevirtcsi "github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/csi/kubevirt"
Expand Down Expand Up @@ -43,6 +44,7 @@ import (
hyperapi "github.com/openshift/hypershift/support/api"
"github.com/openshift/hypershift/support/azureutil"
"github.com/openshift/hypershift/support/capabilities"
"github.com/openshift/hypershift/support/certs"
"github.com/openshift/hypershift/support/config"
"github.com/openshift/hypershift/support/globalconfig"
"github.com/openshift/hypershift/support/releaseinfo"
Expand Down Expand Up @@ -2028,6 +2030,53 @@ func (r *reconciler) reconcileObservedConfiguration(ctx context.Context, hcp *hy
func (r *reconciler) reconcileCloudConfig(ctx context.Context, hcp *hyperv1.HostedControlPlane) error {

switch hcp.Spec.Platform.Type {
case hyperv1.AWSPlatform:
// Sync custom CA certificates to the cloud-provider-config ConfigMap in the guest cluster
// so that cloud components (CCM, CSI drivers, etc.) can use them for AWS API endpoints.
// This mirrors the behavior added in the OpenShift installer via CORS-1584.
// Both .spec.additionalTrustBundle and .spec.configuration.proxy.trustedCA serve the same
// function, so we use the trusted-ca-bundle-managed ConfigMap which merges both sources.
// The source key (ca-bundle.crt) is the standard OpenShift user CA bundle key, while the
// destination key (ca-bundle.pem) is what the AWS cloud provider expects for custom CA certificates.
hasAdditionalTrustBundle := hcp.Spec.AdditionalTrustBundle != nil
hasProxyTrustedCA := hcp.Spec.Configuration != nil && hcp.Spec.Configuration.Proxy != nil && hcp.Spec.Configuration.Proxy.TrustedCA.Name != ""
if hasAdditionalTrustBundle || hasProxyTrustedCA {
cpTrustedCABundle := cpomanifests.TrustedCABundleConfigMap(hcp.Namespace)
if err := r.cpClient.Get(ctx, client.ObjectKeyFromObject(cpTrustedCABundle), cpTrustedCABundle); err != nil {
return fmt.Errorf("failed to fetch %s/%s configmap from management cluster: %w", cpTrustedCABundle.Namespace, cpTrustedCABundle.Name, err)
}

cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Namespace: ConfigNamespace, Name: CloudProviderCMName}}
if _, err := r.CreateOrUpdate(ctx, r.client, cm, func() error {
if cm.Data == nil {
cm.Data = map[string]string{}
}
cm.Data[aws.CABundleKey] = cpTrustedCABundle.Data[certs.UserCABundleMapKey]
return nil
}); err != nil {
return fmt.Errorf("failed to reconcile the %s/%s configmap: %w", cm.Namespace, cm.Name, err)
}
} else {
// If no trust bundle is configured, remove any previously synced CA bundle from the
// guest cloud-provider-config ConfigMap to avoid trusting stale/removed CAs.
cm := &corev1.ConfigMap{}
if err := r.client.Get(ctx, client.ObjectKey{Namespace: ConfigNamespace, Name: CloudProviderCMName}, cm); err != nil {
if !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to get %s/%s configmap: %w", ConfigNamespace, CloudProviderCMName, err)
}
} else if cm.Data != nil {
if _, ok := cm.Data[aws.CABundleKey]; ok {
delete(cm.Data, aws.CABundleKey)
if len(cm.Data) == 0 {
if _, err := util.DeleteIfNeeded(ctx, r.client, cm); err != nil {
return fmt.Errorf("failed to delete empty %s/%s configmap: %w", cm.Namespace, cm.Name, err)
}
} else if err := r.client.Update(ctx, cm); err != nil {
return fmt.Errorf("failed to update %s/%s configmap: %w", cm.Namespace, cm.Name, err)
}
}
}
}
case hyperv1.AzurePlatform:
// This is needed for the e2e tests and only for Azure: https://github.com/openshift/origin/blob/625733dd1ce7ebf40c3dd0abd693f7bb54f2d580/test/extended/util/cluster/cluster.go#L186
reference := cpomanifests.AzureProviderConfig(hcp.Namespace)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import (
. "github.com/onsi/gomega"

hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1"
"github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/cloud/aws"
cpomanifests "github.com/openshift/hypershift/control-plane-operator/controllers/hostedcontrolplane/manifests"
"github.com/openshift/hypershift/control-plane-operator/hostedclusterconfigoperator/api"
"github.com/openshift/hypershift/control-plane-operator/hostedclusterconfigoperator/controllers/resources/kas"
"github.com/openshift/hypershift/control-plane-operator/hostedclusterconfigoperator/controllers/resources/manifests"
"github.com/openshift/hypershift/hypershift-operator/controllers/nodepool"
"github.com/openshift/hypershift/support/azureutil"
"github.com/openshift/hypershift/support/certs"
"github.com/openshift/hypershift/support/config"
"github.com/openshift/hypershift/support/globalconfig"
fakereleaseprovider "github.com/openshift/hypershift/support/releaseinfo/fake"
supportutil "github.com/openshift/hypershift/support/util"
Expand Down Expand Up @@ -2626,3 +2629,253 @@ func TestReconcileImageRegistry(t *testing.T) {
})
}
}

func TestReconcileCloudConfigAWS(t *testing.T) {
t.Parallel()
testNamespace := "master-cluster1"
testHCPName := "cluster1"
tests := map[string]struct {
inputHCP *hyperv1.HostedControlPlane
inputObjects []client.Object
existingGuestObjects []client.Object
expectCloudProviderConfig bool
expectedCABundleContent string
expectError bool
}{
"When AWS platform has no trust bundle it should not create cloud-provider-config": {
inputHCP: &hyperv1.HostedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: testHCPName,
Namespace: testNamespace,
},
Spec: hyperv1.HostedControlPlaneSpec{
Platform: hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
},
},
inputObjects: []client.Object{},
expectCloudProviderConfig: false,
},
"When AWS platform has additionalTrustBundle it should create cloud-provider-config with ca-bundle.pem": {
inputHCP: &hyperv1.HostedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: testHCPName,
Namespace: testNamespace,
},
Spec: hyperv1.HostedControlPlaneSpec{
Platform: hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
AdditionalTrustBundle: &corev1.LocalObjectReference{
Name: "user-ca-bundle",
},
},
},
inputObjects: []client.Object{
&corev1.ConfigMap{
ObjectMeta: cpomanifests.TrustedCABundleConfigMap(testNamespace).ObjectMeta,
Data: map[string]string{
certs.UserCABundleMapKey: "-----BEGIN CERTIFICATE-----\ntest-ca-bundle\n-----END CERTIFICATE-----",
},
},
},
expectCloudProviderConfig: true,
expectedCABundleContent: "-----BEGIN CERTIFICATE-----\ntest-ca-bundle\n-----END CERTIFICATE-----",
},
"When AWS platform has proxy trustedCA it should create cloud-provider-config with ca-bundle.pem": {
inputHCP: &hyperv1.HostedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: testHCPName,
Namespace: testNamespace,
},
Spec: hyperv1.HostedControlPlaneSpec{
Platform: hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
Configuration: &hyperv1.ClusterConfiguration{
Proxy: &configv1.ProxySpec{
TrustedCA: configv1.ConfigMapNameReference{
Name: "proxy-trusted-ca",
},
},
},
},
},
inputObjects: []client.Object{
&corev1.ConfigMap{
ObjectMeta: cpomanifests.TrustedCABundleConfigMap(testNamespace).ObjectMeta,
Data: map[string]string{
certs.UserCABundleMapKey: "-----BEGIN CERTIFICATE-----\nproxy-ca-bundle\n-----END CERTIFICATE-----",
},
},
},
expectCloudProviderConfig: true,
expectedCABundleContent: "-----BEGIN CERTIFICATE-----\nproxy-ca-bundle\n-----END CERTIFICATE-----",
},
"When AWS platform has both additionalTrustBundle and proxy trustedCA it should create cloud-provider-config with merged bundle": {
inputHCP: &hyperv1.HostedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: testHCPName,
Namespace: testNamespace,
},
Spec: hyperv1.HostedControlPlaneSpec{
Platform: hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
AdditionalTrustBundle: &corev1.LocalObjectReference{
Name: "user-ca-bundle",
},
Configuration: &hyperv1.ClusterConfiguration{
Proxy: &configv1.ProxySpec{
TrustedCA: configv1.ConfigMapNameReference{
Name: "proxy-trusted-ca",
},
},
},
},
},
inputObjects: []client.Object{
&corev1.ConfigMap{
ObjectMeta: cpomanifests.TrustedCABundleConfigMap(testNamespace).ObjectMeta,
Data: map[string]string{
certs.UserCABundleMapKey: "-----BEGIN CERTIFICATE-----\nmerged-ca-bundle\n-----END CERTIFICATE-----",
},
},
},
expectCloudProviderConfig: true,
expectedCABundleContent: "-----BEGIN CERTIFICATE-----\nmerged-ca-bundle\n-----END CERTIFICATE-----",
},
"When AWS platform has additionalTrustBundle but managed bundle ConfigMap is missing it should return an error": {
inputHCP: &hyperv1.HostedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: testHCPName,
Namespace: testNamespace,
},
Spec: hyperv1.HostedControlPlaneSpec{
Platform: hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
AdditionalTrustBundle: &corev1.LocalObjectReference{
Name: "user-ca-bundle",
},
},
},
inputObjects: []client.Object{},
expectError: true,
},
"When AWS platform has additionalTrustBundle but managed bundle ConfigMap has no ca-bundle.crt key it should write empty string": {
inputHCP: &hyperv1.HostedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: testHCPName,
Namespace: testNamespace,
},
Spec: hyperv1.HostedControlPlaneSpec{
Platform: hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
AdditionalTrustBundle: &corev1.LocalObjectReference{
Name: "user-ca-bundle",
},
},
},
inputObjects: []client.Object{
&corev1.ConfigMap{
ObjectMeta: cpomanifests.TrustedCABundleConfigMap(testNamespace).ObjectMeta,
Data: map[string]string{},
},
},
expectCloudProviderConfig: true,
expectedCABundleContent: "",
},
"When AWS platform has no trust bundle but cloud-provider-config exists with ca-bundle.pem it should remove the stale ConfigMap": {
inputHCP: &hyperv1.HostedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: testHCPName,
Namespace: testNamespace,
},
Spec: hyperv1.HostedControlPlaneSpec{
Platform: hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
},
},
inputObjects: []client.Object{},
existingGuestObjects: []client.Object{
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Namespace: ConfigNamespace, Name: CloudProviderCMName},
Data: map[string]string{
aws.CABundleKey: "-----BEGIN CERTIFICATE-----\nstale-ca-bundle\n-----END CERTIFICATE-----",
},
},
},
expectCloudProviderConfig: false,
},
"When AWS platform has additionalTrustBundle and cloud-provider-config already exists it should update it": {
inputHCP: &hyperv1.HostedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: testHCPName,
Namespace: testNamespace,
},
Spec: hyperv1.HostedControlPlaneSpec{
Platform: hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
AdditionalTrustBundle: &corev1.LocalObjectReference{
Name: "user-ca-bundle",
},
},
},
inputObjects: []client.Object{
&corev1.ConfigMap{
ObjectMeta: cpomanifests.TrustedCABundleConfigMap(testNamespace).ObjectMeta,
Data: map[string]string{
certs.UserCABundleMapKey: "-----BEGIN CERTIFICATE-----\nupdated-ca-bundle\n-----END CERTIFICATE-----",
},
},
},
existingGuestObjects: []client.Object{
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Namespace: ConfigNamespace, Name: CloudProviderCMName},
Data: map[string]string{
aws.CABundleKey: "-----BEGIN CERTIFICATE-----\nold-ca-bundle\n-----END CERTIFICATE-----",
},
},
},
expectCloudProviderConfig: true,
expectedCABundleContent: "-----BEGIN CERTIFICATE-----\nupdated-ca-bundle\n-----END CERTIFICATE-----",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()
g := NewGomegaWithT(t)
guestClientBuilder := fake.NewClientBuilder().WithScheme(api.Scheme)
if len(test.existingGuestObjects) > 0 {
guestClientBuilder = guestClientBuilder.WithObjects(test.existingGuestObjects...)
}
r := &reconciler{
client: guestClientBuilder.Build(),
CreateOrUpdateProvider: &simpleCreateOrUpdater{},
cpClient: fake.NewClientBuilder().WithScheme(api.Scheme).WithObjects(append(test.inputObjects, test.inputHCP)...).Build(),
hcpName: testHCPName,
hcpNamespace: testNamespace,
}
err := r.reconcileCloudConfig(t.Context(), test.inputHCP)
if test.expectError {
g.Expect(err).ToNot(BeNil())
return
}
g.Expect(err).To(BeNil())

cloudProviderCM := &corev1.ConfigMap{}
getErr := r.client.Get(t.Context(), client.ObjectKey{Namespace: ConfigNamespace, Name: CloudProviderCMName}, cloudProviderCM)
if test.expectCloudProviderConfig {
g.Expect(getErr).To(BeNil())
g.Expect(cloudProviderCM.Data[aws.CABundleKey]).To(Equal(test.expectedCABundleContent))
} else {
g.Expect(apierrors.IsNotFound(getErr)).To(BeTrue())
}
})
}
}