Skip to content

Commit

Permalink
support custom CA bundle for AWS API
Browse files Browse the repository at this point in the history
When a cluster is installed in a AWS C2S region, access to the AWS
API requires a custom CA bundle for trust. The custom CA bundle
is read from the "ca-bundle.pem" key of the kube-cloud-config
ConfigMap in the openshift-config-managed namespace.

https://issues.redhat.com/browse/CORS-1584
  • Loading branch information
staebler committed Dec 7, 2020
1 parent 2808223 commit e61b30e
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 0 deletions.
36 changes: 36 additions & 0 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
"io/ioutil"
"os"
"strings"

corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/cluster-api-provider-aws/pkg/version"
Expand Down Expand Up @@ -53,6 +54,13 @@ const (

// globalInfrastuctureName default name for infrastructure object
globalInfrastuctureName = "cluster"

// kubeCloudConfigNamespace is the namespace where the kube cloud config ConfigMap is located
kubeCloudConfigNamespace = "openshift-config-managed"
// kubeCloudConfigName is the name of the kube cloud config ConfigMap
kubeCloudConfigName = "kube-cloud-config"
// cloudCABundleKey is the key in the kube cloud config ConfigMap where the custom CA bundle is located
cloudCABundleKey = "ca-bundle.pem"
)

// AwsClientBuilderFuncType is function type for building aws client
Expand Down Expand Up @@ -176,6 +184,10 @@ func NewClient(ctrlRuntimeClient client.Client, secretName, namespace, region st
return nil, err
}

if err := useCustomCABundle(&sessionOptions, ctrlRuntimeClient); err != nil {
return nil, fmt.Errorf("failed to set the custom CA bundle: %w", err)
}

// Otherwise default to relying on the IAM role of the masters where the actuator is running:
s, err := session.NewSessionWithOptions(sessionOptions)
if err != nil {
Expand Down Expand Up @@ -308,3 +320,27 @@ func newConfigForStaticCreds(accessKey string, accessSecret string) []byte {
fmt.Fprintf(buf, "aws_secret_access_key = %s\n", accessSecret)
return buf.Bytes()
}

// useCustomCABundle will set up a custom CA bundle in the AWS options if a CA bundle is configured in the
// kube cloud config.
func useCustomCABundle(awsOptions *session.Options, ctrlRuntimeClient client.Client) error {
cm := &corev1.ConfigMap{}
switch err := ctrlRuntimeClient.Get(
context.Background(),
client.ObjectKey{Namespace: kubeCloudConfigNamespace, Name: kubeCloudConfigName},
cm,
); {
case apimachineryerrors.IsNotFound(err):
// no cloud config ConfigMap, so no custom CA bundle
return nil
case err != nil:
return fmt.Errorf("failed to get kube-cloud-config ConfigMap: %w", err)
}
caBundle, ok := cm.Data[cloudCABundleKey]
if !ok {
// no "ca-bundle.pem" key in the ConfigMap, so no custom CA bundle
return nil
}
awsOptions.CustomCABundle = strings.NewReader(caBundle)
return nil
}
76 changes: 76 additions & 0 deletions pkg/client/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package client

import (
"io/ioutil"
"testing"

"github.com/aws/aws-sdk-go/aws/session"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestUseCustomCABundle(t *testing.T) {
cases := []struct {
name string
cm *corev1.ConfigMap
expectedCABundle string
}{
{
name: "no configmap",
},
{
name: "no CA bundle in configmap",
cm: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: "openshift-config-managed",
Name: "kube-cloud-config",
},
Data: map[string]string{
"other-key": "other-data",
},
},
},
{
name: "custom CA bundle",
cm: &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: "openshift-config-managed",
Name: "kube-cloud-config",
},
Data: map[string]string{
"ca-bundle.pem": "a custom bundle",
},
},
expectedCABundle: "a custom bundle",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
scheme := runtime.NewScheme()
corev1.AddToScheme(scheme)
resources := []runtime.Object{}
if tc.cm != nil {
resources = append(resources, tc.cm)
}
ctrlRuntimeClient := fake.NewFakeClientWithScheme(scheme, resources...)
awsOptions := &session.Options{}
err := useCustomCABundle(awsOptions, ctrlRuntimeClient)
if err != nil {
t.Fatalf("unexpected error from useCustomCABundle: %v", err)
}
actualCABundle := ""
if awsOptions.CustomCABundle != nil {
bundleBytes, err := ioutil.ReadAll(awsOptions.CustomCABundle)
if err != nil {
t.Fatalf("unexpected error reading bundle: %v", err)
}
actualCABundle = string(bundleBytes)
}
if a, e := actualCABundle, tc.expectedCABundle; a != e {
t.Errorf("unexpected CA bundle: expected=%s; got %s", e, a)
}
})
}
}

0 comments on commit e61b30e

Please sign in to comment.