Skip to content

Commit

Permalink
This commit adds --ca-secret-name so users attempting to run
Browse files Browse the repository at this point in the history
bundles hosted by a registry using a custom CA can configure
the registry Pod's `opm registry add` command with the root cert file.

Signed-off-by: Eric Stroczynski <ericstroczynski@gmail.com>
  • Loading branch information
estroz committed Mar 26, 2021
1 parent 4eb50a4 commit b0b9be9
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 38 deletions.
66 changes: 46 additions & 20 deletions internal/olm/operator/registry/index/registry_pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ type RegistryPod struct {
// can pull bundle images from a private registry.
SecretName string

// SecretName holds the name of a secret for a CA cert file containing root certificates.
// This file is transiently added to the registry Pod's cert pool via `opm registry add --ca-file`.
// The secret's key for this file must be "cert.pem".
CASecretName string

// pod represents a kubernetes *corev1.pod that will be created on a cluster using an index image
pod *corev1.Pod

Expand Down Expand Up @@ -113,11 +118,11 @@ func (rp *RegistryPod) Create(ctx context.Context, cfg *operator.Configuration,

// make catalog source the owner of registry pod object
if err := controllerutil.SetOwnerReference(cs, rp.pod, rp.cfg.Scheme); err != nil {
return nil, fmt.Errorf("set registry pod owner reference: %v", err)
return nil, fmt.Errorf("error setting owner reference: %w", err)
}

if err := rp.cfg.Client.Create(ctx, rp.pod); err != nil {
return nil, fmt.Errorf("create registry pod: %v", err)
return nil, fmt.Errorf("error creating pod: %w", err)
}

// get registry pod key
Expand Down Expand Up @@ -225,37 +230,58 @@ func (rp *RegistryPod) podForBundleRegistry() (*corev1.Pod, error) {
}

addImagePullSecret(rp.pod, rp.SecretName)
addCertSecret(rp.pod, rp.CASecretName)

return rp.pod, nil
}

// addImagePullSecret creates and mounts an image pull secret volume
// for a docker config secret "secretName" in each container in pod.
// addImagePullSecret creates a docker config volume for secretName
// and a volumeMount for that secret in each container in pod.
func addImagePullSecret(pod *corev1.Pod, secretName string) {
if secretName == "" {
return
}

pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{
// Require a non-legacy docker config secret.
volume := makeSecretVolume(secretName, corev1.KeyToPath{Key: ".dockerconfigjson", Path: ".docker/config.json"})
pod.Spec.Volumes = append(pod.Spec.Volumes, volume)

addVolumeMountForSecret(pod, volume.Name, "/root")
}

// addCertSecret creates and mounts a volume containing a CA root certificate
// to pass to `opm registry add`.
func addCertSecret(pod *corev1.Pod, secretName string) {
if secretName == "" {
return
}

// Ensure the secret contains a key "cert.pem".
volume := makeSecretVolume(secretName, corev1.KeyToPath{Key: "cert.pem", Path: "cert.pem"})
pod.Spec.Volumes = append(pod.Spec.Volumes, volume)

addVolumeMountForSecret(pod, volume.Name, "/certs")
}

func makeSecretVolume(secretName string, items ...corev1.KeyToPath) corev1.Volume {
return corev1.Volume{
Name: secretName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: secretName,
DefaultMode: newInt32(0400),
Optional: newBool(false),
Items: []corev1.KeyToPath{
// Require a non-legacy docker config secret.
{Key: ".dockerconfigjson", Path: ".docker/config.json"},
},
Items: items,
},
},
})
}
}

func addVolumeMountForSecret(pod *corev1.Pod, secretName, mountPath string) {
volumeMount := corev1.VolumeMount{
Name: secretName,
ReadOnly: true,
// Mount in $HOME.
MountPath: "/root",
Name: secretName,
ReadOnly: true,
MountPath: mountPath,
}
for i := range pod.Spec.Containers {
pod.Spec.Containers[i].VolumeMounts = append(pod.Spec.Containers[i].VolumeMounts, volumeMount)
Expand All @@ -274,9 +300,9 @@ func newBool(b bool) *bool {
return bp
}

const containerCommand = `/bin/mkdir -p {{ dirname .DBPath }} && \
const cmdTemplate = `/bin/mkdir -p {{ dirname .DBPath }} && \
{{- range $i, $item := .BundleItems }}
/bin/opm registry add -d {{ $.DBPath }} -b {{ $item.ImageTag }} --mode={{ $item.AddMode }} && \
/bin/opm registry add -d {{ $.DBPath }} -b {{ $item.ImageTag }} --mode={{ $item.AddMode }}{{ if $.CASecretName }} --ca-file=/certs/cert.pem{{ end }} && \
{{- end }}
/bin/opm registry serve -d {{ .DBPath }} -p {{ .GRPCPort }}
`
Expand All @@ -291,13 +317,13 @@ func (rp *RegistryPod) getContainerCmd() (string, error) {
}

// add the custom dirname template function to the
// template's FuncMap and parse the containerCommand
tmp := template.Must(template.New("cmd").Funcs(funcMap).Parse(containerCommand))
// template's FuncMap and parse the cmdTemplate
t := template.Must(template.New("cmd").Funcs(funcMap).Parse(cmdTemplate))

// execute the command by applying the parsed tmp to command
// execute the command by applying the parsed t to command
// and write command output to out
out := &bytes.Buffer{}
if err := tmp.Execute(out, rp); err != nil {
if err := t.Execute(out, rp); err != nil {
return "", fmt.Errorf("parse container command: %w", err)
}

Expand Down
36 changes: 23 additions & 13 deletions internal/olm/operator/registry/index/registry_pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ func TestCreateRegistryPod(t *testing.T) {

var _ = Describe("RegistryPod", func() {

var defaultBundleItem = BundleItem{
var defaultBundleItems = []BundleItem{{
ImageTag: "quay.io/example/example-operator-bundle:0.2.0",
AddMode: SemverBundleAddMode,
}
}}

Describe("creating registry pod", func() {

Expand All @@ -67,7 +67,7 @@ var _ = Describe("RegistryPod", func() {
Namespace: "test-default",
}
rp = &RegistryPod{
BundleItems: []BundleItem{defaultBundleItem},
BundleItems: defaultBundleItems,
IndexImage: testIndexImageTag,
}
By("initializing the RegistryPod")
Expand All @@ -94,29 +94,35 @@ var _ = Describe("RegistryPod", func() {
It("should return a valid container command for one image", func() {
output, err := rp.getContainerCmd()
Expect(err).To(BeNil())
Expect(output).Should(Equal(containerCommandFor(defaultDBPath, []BundleItem{defaultBundleItem})))
Expect(output).Should(Equal(containerCommandFor(defaultDBPath, defaultBundleItems, false)))
})

It("should return a container command with --ca-file", func() {
rp.CASecretName = "foo-secret"
output, err := rp.getContainerCmd()
Expect(err).To(BeNil())
Expect(output).Should(Equal(containerCommandFor(defaultDBPath, defaultBundleItems, true)))
})

It("should return a valid container command for three images", func() {
bundleItems := []BundleItem{
defaultBundleItem,
{
bundleItems := append(defaultBundleItems,
BundleItem{
ImageTag: "quay.io/example/example-operator-bundle:0.3.0",
AddMode: ReplacesBundleAddMode,
},
{
BundleItem{
ImageTag: "quay.io/example/example-operator-bundle:1.0.1",
AddMode: SemverBundleAddMode,
},
}
)
rp2 := RegistryPod{
DBPath: defaultDBPath,
GRPCPort: defaultGRPCPort,
BundleItems: bundleItems,
}
output, err := rp2.getContainerCmd()
Expect(err).To(BeNil())
Expect(output).Should(Equal(containerCommandFor(defaultDBPath, bundleItems)))
Expect(output).Should(Equal(containerCommandFor(defaultDBPath, bundleItems, false)))
})

It("check pod status should return successfully when pod check is true", func() {
Expand Down Expand Up @@ -188,7 +194,7 @@ var _ = Describe("RegistryPod", func() {

It("checkPodStatus should return error when pod check is false and context is done", func() {
rp := &RegistryPod{
BundleItems: []BundleItem{defaultBundleItem},
BundleItems: defaultBundleItems,
IndexImage: testIndexImageTag,
}
Expect(rp.init(cfg)).To(Succeed())
Expand All @@ -211,10 +217,14 @@ var _ = Describe("RegistryPod", func() {
})

// containerCommandFor returns the expected container command for a db path and set of bundle items.
func containerCommandFor(dbPath string, items []BundleItem) string {
func containerCommandFor(dbPath string, items []BundleItem, hasCA bool) string {
var caFlag string
if hasCA {
caFlag = " --ca-file=/certs/cert.pem"
}
additions := &strings.Builder{}
for _, item := range items {
additions.WriteString(fmt.Sprintf("/bin/opm registry add -d %s -b %s --mode=%s && \\\n", dbPath, item.ImageTag, item.AddMode))
additions.WriteString(fmt.Sprintf("/bin/opm registry add -d %s -b %s --mode=%s%s && \\\n", dbPath, item.ImageTag, item.AddMode, caFlag))
}
return fmt.Sprintf("/bin/mkdir -p /database && \\\n%s/bin/opm registry serve -d /database/index.db -p 50051\n", additions.String())
}
16 changes: 11 additions & 5 deletions internal/olm/operator/registry/index_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type IndexImageCatalogCreator struct {
BundleImage string
BundleAddMode index.BundleAddMode
SecretName string
CASecretName string

cfg *operator.Configuration
}
Expand All @@ -82,6 +83,10 @@ func (c *IndexImageCatalogCreator) BindFlags(fs *pflag.FlagSet) {
"Name of image pull secret (\"type: kubernetes.io/dockerconfigjson\") required "+
"to pull bundle images. This secret *must* be both in the namespace and an "+
"imagePullSecret of the service account that this command is configured to run in")
fs.StringVar(&c.CASecretName, "ca-secret-name", "",
"Name of a generic secret containing a PEM root certificate file required to pull bundle images. "+
"This secret *must* be in the namespace that this command is configured to run in, "+
"and the file *must* be encoded under the key \"cert.pem\"")
}

func (c IndexImageCatalogCreator) CreateCatalog(ctx context.Context, name string) (*v1alpha1.CatalogSource, error) {
Expand Down Expand Up @@ -153,7 +158,7 @@ func (c IndexImageCatalogCreator) UpdateCatalog(ctx context.Context, cs *v1alpha

if prevRegistryPodName != "" {
if err = c.deleteRegistryPod(ctx, prevRegistryPodName); err != nil {
return fmt.Errorf("error cleaning up previous registry pod: %v", err)
return fmt.Errorf("error cleaning up previous registry: %v", err)
}
}

Expand Down Expand Up @@ -182,16 +187,17 @@ func (c IndexImageCatalogCreator) createAnnotatedRegistry(ctx context.Context, c
}
// Initialize and create registry pod
registryPod := index.RegistryPod{
BundleItems: items,
IndexImage: c.IndexImage,
SecretName: c.SecretName,
BundleItems: items,
IndexImage: c.IndexImage,
SecretName: c.SecretName,
CASecretName: c.CASecretName,
}
if registryPod.DBPath, err = c.getDBPath(ctx); err != nil {
return fmt.Errorf("get database path: %v", err)
}
pod, err := registryPod.Create(ctx, c.cfg, cs)
if err != nil {
return fmt.Errorf("error creating registry pod: %v", err)
return err
}

// JSON marshal injected bundles
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ operator-sdk run bundle-upgrade <bundle-image> [flags]
### Options

```
--ca-secret-name string Name of a generic secret containing a PEM root certificate file required to pull bundle images. This secret *must* be in the namespace that this command is configured to run in, and the file *must* be encoded under the key "cert.pem"
-h, --help help for bundle-upgrade
--kubeconfig string Path to the kubeconfig file to use for CLI requests.
-n, --namespace string If present, namespace scope for this CLI request
Expand Down
1 change: 1 addition & 0 deletions website/content/en/docs/cli/operator-sdk_run_bundle.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ operator-sdk run bundle <bundle-image> [flags]
### Options

```
--ca-secret-name string Name of a generic secret containing a PEM root certificate file required to pull bundle images. This secret *must* be in the namespace that this command is configured to run in, and the file *must* be encoded under the key "cert.pem"
-h, --help help for bundle
--index-image string index image in which to inject bundle (default "quay.io/operator-framework/upstream-opm-builder:latest")
--install-mode InstallModeValue install mode
Expand Down

0 comments on commit b0b9be9

Please sign in to comment.