Skip to content

Commit

Permalink
Read the openshift-config/user-ca-bundle ConfigMap. (#239)
Browse files Browse the repository at this point in the history
This ConfigMap is configured at installation time and can contain a
PEM-formatted bundle of CA certificates that should be copied on the node.
For SRO to be able to pull "just like the nodes' Docker daemon", we need to
read that bundle (if it exists).

This PR extracts all the logic that fetches OpenShift user-defined CA
certificates into a new OpenShiftCAGetter interface in a separate file.
It addresses BZ 2100891.
  • Loading branch information
qbarrand authored Jun 29, 2022
1 parent 520b0c3 commit e01ece2
Show file tree
Hide file tree
Showing 6 changed files with 424 additions and 51 deletions.
8 changes: 7 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,13 @@ func main() {
proxyAPI,
resourcehelper.New())

registryAPI := registry.NewRegistry(kubeClient, registry.NewCraneWrapper(kubeClient, registry.RegistryConfFilePath))
craneWrapperAPI := registry.NewCraneWrapper(
kubeClient,
registry.NewOpenShiftCAGetter(kubeClient),
registry.RegistryConfFilePath,
)

registryAPI := registry.NewRegistry(kubeClient, craneWrapperAPI)

clusterInfoAPI := upgrade.NewClusterInfo(registryAPI, clusterAPI)
runtimeAPI := runtime.NewRuntimeAPI(kubeClient, clusterAPI, kernelAPI, clusterInfoAPI, proxyAPI)
Expand Down
65 changes: 65 additions & 0 deletions pkg/registry/mock_openshift_ca_api.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

87 changes: 87 additions & 0 deletions pkg/registry/openshift_ca.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package registry

import (
"context"
"fmt"

"github.com/openshift/special-resource-operator/pkg/clients"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
)

type openShiftCAGetter struct {
kubeClient clients.ClientsInterface
}

//go:generate mockgen -source=openshift_ca.go -package=registry -destination=mock_openshift_ca_api.go

type OpenShiftCAGetter interface {
AdditionalTrustedCAs(ctx context.Context) (map[string][]byte, error)
CABundle(ctx context.Context) ([]byte, error)
}

func NewOpenShiftCAGetter(kubeClient clients.ClientsInterface) OpenShiftCAGetter {
return &openShiftCAGetter{kubeClient: kubeClient}
}

func (ocg *openShiftCAGetter) AdditionalTrustedCAs(ctx context.Context) (map[string][]byte, error) {
logger := ctrl.LoggerFrom(ctx)

var certs map[string][]byte

logger.Info("Getting image config", "name", imageClusterName)

img, err := ocg.kubeClient.GetImage(ctx, imageClusterName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("could not retrieve image.config.openshift.io/%s: %w", imageClusterName, err)
}

if cmName := img.Spec.AdditionalTrustedCA.Name; cmName != "" {
logger.Info("Getting ConfigMap", "cm-name", cmName, "cm-namespace", configNamespace)

cm, err := ocg.kubeClient.GetConfigMap(ctx, configNamespace, cmName, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("could not retrieve configmap %s/%s: %w", configNamespace, cmName, err)
}

certs = make(map[string][]byte, len(cm.Data))

for registry, cert := range cm.Data {
certs[registry] = []byte(cert)
}
}

return certs, nil

}

func (ocg *openShiftCAGetter) CABundle(ctx context.Context) ([]byte, error) {
logger := ctrl.LoggerFrom(ctx)

const userCABundleCMName = "user-ca-bundle"

cmLogger := logger.WithValues("cm-name", userCABundleCMName, "cm-namespace", configNamespace)

cmLogger.Info("Getting ConfigMap")

userCABundle, err := ocg.kubeClient.GetConfigMap(ctx, configNamespace, userCABundleCMName, metav1.GetOptions{})
if err != nil {
// It's okay if that ConfigMap does not exist.
if !k8serrors.IsNotFound(err) {
return nil, fmt.Errorf("could not get ConfigMap %s/%s: %v", configNamespace, userCABundleCMName, err)
}

cmLogger.Info("No such ConfigMap; skipping")
return nil, nil
}

const caBundleKey = "ca-bundle.crt"

if data, ok := userCABundle.Data[caBundleKey]; ok {
return []byte(data), nil
}

cmLogger.Info("No such key; skipping", "key", caBundleKey)
return nil, nil
}
172 changes: 172 additions & 0 deletions pkg/registry/openshift_ca_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package registry

import (
"context"
"errors"

"github.com/golang/mock/gomock"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
configv1 "github.com/openshift/api/config/v1"
"github.com/openshift/special-resource-operator/pkg/clients"
v1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)

var _ = Describe("OpenShiftCAGetter", func() {
var kubeClient *clients.MockClientsInterface

BeforeEach(func() {
ctrl := gomock.NewController(GinkgoT())
kubeClient = clients.NewMockClientsInterface(ctrl)
})

Describe("AdditionalTrustedCAs", func() {
const imageConfigName = "cluster"

It("should return an error if we cannot get the image.config.openshift.io", func() {
kubeClient.
EXPECT().
GetImage(context.Background(), imageConfigName, metav1.GetOptions{}).
Return(nil, errors.New("random error"))

_, err := NewOpenShiftCAGetter(kubeClient).AdditionalTrustedCAs(context.Background())
Expect(err).To(HaveOccurred())
})

It("should return an empty slice if the additionalTrustedCA is empty", func() {
kubeClient.
EXPECT().
GetImage(context.Background(), imageConfigName, metav1.GetOptions{}).
Return(&configv1.Image{}, nil)

c, err := NewOpenShiftCAGetter(kubeClient).AdditionalTrustedCAs(context.Background())
Expect(err).NotTo(HaveOccurred())
Expect(c).To(BeEmpty())
})

It("should an empty slice if the configmap is empty", func() {
const cmName = "cm-name"

img := configv1.Image{
Spec: configv1.ImageSpec{
AdditionalTrustedCA: configv1.ConfigMapNameReference{Name: cmName},
},
}

gomock.InOrder(
kubeClient.
EXPECT().
GetImage(context.Background(), imageConfigName, metav1.GetOptions{}).
Return(&img, nil),
kubeClient.
EXPECT().
GetConfigMap(context.Background(), "openshift-config", cmName, metav1.GetOptions{}).
Return(&v1.ConfigMap{}, nil),
)

c, err := NewOpenShiftCAGetter(kubeClient).AdditionalTrustedCAs(context.Background())
Expect(err).NotTo(HaveOccurred())
Expect(c).To(BeEmpty())
})

It("should work as expected", func() {
const cmName = "cm-name"

img := configv1.Image{
Spec: configv1.ImageSpec{
AdditionalTrustedCA: configv1.ConfigMapNameReference{Name: cmName},
},
}

const (
cmKey = "key"
cmValue = "value"
)

cm := v1.ConfigMap{
Data: map[string]string{cmKey: cmValue},
}

gomock.InOrder(
kubeClient.
EXPECT().
GetImage(context.Background(), imageConfigName, metav1.GetOptions{}).
Return(&img, nil),
kubeClient.
EXPECT().
GetConfigMap(context.Background(), "openshift-config", cmName, metav1.GetOptions{}).
Return(&cm, nil),
)

c, err := NewOpenShiftCAGetter(kubeClient).AdditionalTrustedCAs(context.Background())
Expect(err).NotTo(HaveOccurred())
Expect(c).To(
Equal(
map[string][]byte{cmKey: []byte(cmValue)},
),
)
})
})

Describe("CABundle", func() {
const cmName = "user-ca-bundle"

It("should return an error if we cannot fetch the ConfigMap", func() {
randomError := errors.New("random error")

kubeClient.
EXPECT().
GetConfigMap(context.Background(), "openshift-config", cmName, metav1.GetOptions{}).
Return(nil, randomError)

_, err := NewOpenShiftCAGetter(kubeClient).CABundle(context.Background())
Expect(err).To(HaveOccurred())
})

It("should return an empty slice if the ConfigMap does not exist", func() {
notFound := k8serrors.NewNotFound(schema.GroupResource{}, cmName)

kubeClient.
EXPECT().
GetConfigMap(context.Background(), "openshift-config", cmName, metav1.GetOptions{}).
Return(nil, notFound)

s, err := NewOpenShiftCAGetter(kubeClient).CABundle(context.Background())
Expect(err).NotTo(HaveOccurred())
Expect(s).To(BeEmpty())
})

It("should return an empty slice if the ConfigMap does not have the expected key", func() {
kubeClient.
EXPECT().
GetConfigMap(context.Background(), "openshift-config", cmName, metav1.GetOptions{}).
Return(&v1.ConfigMap{}, nil)

s, err := NewOpenShiftCAGetter(kubeClient).CABundle(context.Background())
Expect(err).NotTo(HaveOccurred())
Expect(s).To(BeEmpty())
})

It("should work as expected", func() {
const value = "test-value"

cm := v1.ConfigMap{
Data: map[string]string{"ca-bundle.crt": value},
}

kubeClient.
EXPECT().
GetConfigMap(context.Background(), "openshift-config", cmName, metav1.GetOptions{}).
Return(&cm, nil)

s, err := NewOpenShiftCAGetter(kubeClient).CABundle(context.Background())
Expect(err).NotTo(HaveOccurred())
Expect(s).To(
Equal([]byte(value)),
)
})
})
})
Loading

0 comments on commit e01ece2

Please sign in to comment.