-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Read the openshift-config/user-ca-bundle ConfigMap. (#239)
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
Showing
6 changed files
with
424 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)), | ||
) | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.