Skip to content

Commit

Permalink
Inject CA trust bundle into managed containers and set SSL_CERT_DIR
Browse files Browse the repository at this point in the history
  • Loading branch information
osmman committed Jul 3, 2024
1 parent d0881cf commit 0738637
Show file tree
Hide file tree
Showing 20 changed files with 560 additions and 73 deletions.
24 changes: 12 additions & 12 deletions api/v1alpha1/rekor_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package v1alpha1
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/securesign/operator/internal/controller/common/utils"
"golang.org/x/net/context"
_ "k8s.io/api/apps/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
k8sresource "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
)

var _ = Describe("Rekor", func() {
Expand Down Expand Up @@ -77,27 +77,27 @@ var _ = Describe("Rekor", func() {
When("changing Rekor Search UI", func() {
It("enabled false->true", func() {
created := generateRekorObject("rekor-ui-1")
created.Spec.RekorSearchUI.Enabled = utils.Pointer(false)
created.Spec.RekorSearchUI.Enabled = pointer.Bool(false)
Expect(k8sClient.Create(context.Background(), created)).To(Succeed())

fetched := &Rekor{}
Expect(k8sClient.Get(context.Background(), getKey(created), fetched)).To(Succeed())
Expect(fetched).To(Equal(created))

fetched.Spec.RekorSearchUI.Enabled = utils.Pointer(true)
fetched.Spec.RekorSearchUI.Enabled = pointer.Bool(true)
Expect(k8sClient.Update(context.Background(), fetched)).To(Succeed())
})

It("enabled true->false", func() {
created := generateRekorObject("rekor-ui-2")
created.Spec.RekorSearchUI.Enabled = utils.Pointer(true)
created.Spec.RekorSearchUI.Enabled = pointer.Bool(true)
Expect(k8sClient.Create(context.Background(), created)).To(Succeed())

fetched := &Rekor{}
Expect(k8sClient.Get(context.Background(), getKey(created), fetched)).To(Succeed())
Expect(fetched).To(Equal(created))

fetched.Spec.RekorSearchUI.Enabled = utils.Pointer(false)
fetched.Spec.RekorSearchUI.Enabled = pointer.Bool(false)
Expect(apierrors.IsInvalid(k8sClient.Update(context.Background(), fetched))).To(BeTrue())
Expect(k8sClient.Update(context.Background(), fetched)).
To(MatchError(ContainSubstring("Feature cannot be disabled")))
Expand Down Expand Up @@ -150,7 +150,7 @@ var _ = Describe("Rekor", func() {

invalidObject := &Rekor{}
Expect(k8sClient.Get(context.Background(), getKey(validObject), invalidObject)).To(Succeed())
invalidObject.Spec.Pvc.Retain = utils.Pointer(false)
invalidObject.Spec.Pvc.Retain = pointer.Bool(false)

Expect(apierrors.IsInvalid(k8sClient.Update(context.Background(), invalidObject))).To(BeTrue())
Expect(k8sClient.Update(context.Background(), invalidObject)).
Expand Down Expand Up @@ -214,18 +214,18 @@ var _ = Describe("Rekor", func() {
Host: "hostname",
},
RekorSearchUI: RekorSearchUI{
Enabled: utils.Pointer(true),
Enabled: pointer.Bool(true),
},
BackFillRedis: BackFillRedis{
Enabled: utils.Pointer(true),
Enabled: pointer.Bool(true),
Schedule: "* */2 * * 0-3",
},
TreeID: &tree,
Pvc: Pvc{
Name: "name",
Size: &storage,
StorageClass: "name",
Retain: utils.Pointer(true),
Retain: pointer.Bool(true),
},
Signer: RekorSigner{
KMS: "secret",
Expand Down Expand Up @@ -294,18 +294,18 @@ func generateRekorObject(name string) *Rekor {
},
Spec: RekorSpec{
BackFillRedis: BackFillRedis{
Enabled: utils.Pointer(true),
Enabled: pointer.Bool(true),
Schedule: "0 0 * * *",
},
Signer: RekorSigner{
KMS: "secret",
},
Pvc: Pvc{
Retain: utils.Pointer(true),
Retain: pointer.Bool(true),
Size: &storage,
},
Trillian: TrillianService{
Port: utils.Pointer(int32(8091)),
Port: pointer.Int32(int32(8091)),
},
},
}
Expand Down
18 changes: 9 additions & 9 deletions api/v1alpha1/trillian_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package v1alpha1
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/securesign/operator/internal/controller/common/utils"
"golang.org/x/net/context"
_ "k8s.io/api/apps/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
k8sresource "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
)

var _ = Describe("Trillian", func() {
Expand Down Expand Up @@ -58,7 +58,7 @@ var _ = Describe("Trillian", func() {

invalidObject := &Trillian{}
Expect(k8sClient.Get(context.Background(), getKey(validObject), invalidObject)).To(Succeed())
invalidObject.Spec.Db.Create = utils.Pointer(false)
invalidObject.Spec.Db.Create = pointer.Bool(false)

Expect(apierrors.IsInvalid(k8sClient.Update(context.Background(), invalidObject))).To(BeTrue())
Expect(k8sClient.Update(context.Background(), invalidObject)).
Expand All @@ -71,7 +71,7 @@ var _ = Describe("Trillian", func() {

invalidObject := &Trillian{}
Expect(k8sClient.Get(context.Background(), getKey(validObject), invalidObject)).To(Succeed())
invalidObject.Spec.Db.Pvc.Retain = utils.Pointer(false)
invalidObject.Spec.Db.Pvc.Retain = pointer.Bool(false)

Expect(apierrors.IsInvalid(k8sClient.Update(context.Background(), invalidObject))).To(BeTrue())
Expect(k8sClient.Update(context.Background(), invalidObject)).
Expand All @@ -82,7 +82,7 @@ var _ = Describe("Trillian", func() {
It("true", func() {
By("databaseSecretRef is empty", func() {
validObject := generateTrillianObject("database-secret-1")
validObject.Spec.Db.Create = utils.Pointer(true)
validObject.Spec.Db.Create = pointer.Bool(true)
validObject.Spec.Db.DatabaseSecretRef = nil
Expect(k8sClient.Create(context.Background(), validObject)).To(Succeed())
})
Expand All @@ -91,7 +91,7 @@ var _ = Describe("Trillian", func() {
It("false", func() {
By("databaseSecretRef is mandatory", func() {
invalidObject := generateTrillianObject("database-secret-2")
invalidObject.Spec.Db.Create = utils.Pointer(false)
invalidObject.Spec.Db.Create = pointer.Bool(false)
invalidObject.Spec.Db.DatabaseSecretRef = nil
Expect(apierrors.IsInvalid(k8sClient.Create(context.Background(), invalidObject))).To(BeTrue())
Expect(k8sClient.Create(context.Background(), invalidObject)).
Expand Down Expand Up @@ -146,9 +146,9 @@ var _ = Describe("Trillian", func() {
},
Spec: TrillianSpec{
Db: TrillianDB{
Create: utils.Pointer(true),
Create: pointer.Bool(true),
Pvc: Pvc{
Retain: utils.Pointer(true),
Retain: pointer.Bool(true),
Name: "storage",
StorageClass: "storage-class",
Size: &storage,
Expand Down Expand Up @@ -208,9 +208,9 @@ func generateTrillianObject(name string) *Trillian {
},
Spec: TrillianSpec{
Db: TrillianDB{
Create: utils.Pointer(true),
Create: pointer.Bool(true),
Pvc: Pvc{
Retain: utils.Pointer(true),
Retain: pointer.Bool(true),
Size: &storage,
},
},
Expand Down
54 changes: 54 additions & 0 deletions docs/custom-ca.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Custom CA Bundle

Configuring deployments to trust custom Certificate Authorities (CAs) or self-signed certificates is often necessary for
ensuring secure communication between components or external OIDC service. This guide provides instructions how to add
custom CA bundle for managed operands.

## Prerequisites: ConfigMap with the CA Bundle

Before configuring an operand, you need to create a ConfigMap that includes your CA bundle in the same namespace where
the application will be deployed. You can achieve this using one of the following methods:

1. **Manually Create a ConfigMap**:

Create a ConfigMap manually by following the [Kubernetes documentation](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/).

Example:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: custom-ca-bundle
data:
ca-bundle.crt: |
-----BEGIN CERTIFICATE-----
MIIC... (certificate content)
-----END CERTIFICATE-----
```
2. **Use Cert-Manager**:
Cert-Manager can generate and manage trusted CA bundles. Follow the [Cert-Manager Documentation](https://cert-manager.io/docs/usage/certificate/) for detailed steps.
3. **Use OpenShift's Feature to Inject Trusted CA Bundles**:
OpenShift can automatically inject trusted CA bundles into a ConfigMap. Follow the [OpenShift Documentation](https://docs.openshift.com/container-platform/latest/networking/configuring-a-custom-pki.html#certificate-injection-using-operators_configuring-a-custom-pki) for detailed steps.
## Configure operand to use Custom CA Bundle
This is done by adding an annotation to the relevant Custom Resource Definitions (CRDs). It can be added to any CRD that
the operator manages (Securesign, Trillian, Fulcio, Rekor, CTlog). The annotation key is `rhtas.redhat.com/trusted-ca`,
and the value should be the name of the ConfigMap created in the prerequisites.

### Example on a Securesign

```yaml
apiVersion: rhtas.redhat.com/v1alpha1
kind: Securesign
metadata:
name: example-instance
annotations:
rhtas.redhat.com/trusted-ca: "name-of-your-configmap"
spec:
# other specifications
```
19 changes: 19 additions & 0 deletions internal/controller/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,23 @@ const (

// Metrics Annotation is used to control the sending of analytic metrics of the installed services managed by the operator.
Metrics = "rhtas.redhat.com/metrics"

// TrustedCA Annotation to specify name of ConfigMap with additional bundle of trusted CA
TrustedCA = "rhtas.redhat.com/trusted-ca"
)

var inheritable = []string{
TrustedCA,
}

func FilterInheritable(annotations map[string]string) map[string]string {
result := make(map[string]string, 0)
for key, value := range annotations {
for _, ia := range inheritable {
if key == ia {
result[key] = value
}
}
}
return result
}
78 changes: 78 additions & 0 deletions internal/controller/annotations/annotations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package annotations

import (
"maps"
"reflect"
"strconv"
"testing"
)

func TestFilterInheritable(t *testing.T) {
allIn := make(map[string]string)
allCopy := make(map[string]string)
for i, a := range inheritable {
allIn[a] = strconv.FormatInt(int64(i), 10)
}
maps.Copy(allCopy, allIn)

type args struct {
annotations map[string]string
}
tests := []struct {
name string
args args
want map[string]string
}{
{
name: "empty",
args: args{
annotations: make(map[string]string),
},
want: map[string]string{},
},
{
name: "nil",
args: args{
annotations: nil,
},
want: map[string]string{},
},
{
name: "no inheritable",
args: args{
annotations: map[string]string{
"name1": "value",
"name2": "value",
},
},
want: map[string]string{},
},
{
name: "all inheritable",
args: args{
annotations: allIn,
},
want: allCopy,
},
{
name: "one inheritable",
args: args{
annotations: map[string]string{
"name1": "value",
"name2": "value",
TrustedCA: "ca",
},
},
want: map[string]string{
TrustedCA: "ca",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := FilterInheritable(tt.args.annotations); !reflect.DeepEqual(got, tt.want) {
t.Errorf("FilterInheritable() = %v, want %v", got, tt.want)
}
})
}
}
68 changes: 68 additions & 0 deletions internal/controller/common/utils/set_trusted_ca.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package utils

import (
"errors"
"github.com/securesign/operator/api/v1alpha1"
"github.com/securesign/operator/internal/controller/annotations"
corev1 "k8s.io/api/core/v1"
)

// SetTrustedCA mount config map with trusted CA bundle to all deployment's containers.
func SetTrustedCA(template *corev1.PodTemplateSpec, lor *v1alpha1.LocalObjectReference) error {
if template == nil {
return errors.New("SetTrustedCA: PodTemplateSpec is not set")
}

for i, container := range template.Spec.Containers {
if template.Spec.Containers[i].Env == nil {
template.Spec.Containers[i].Env = make([]corev1.EnvVar, 0)
}
template.Spec.Containers[i].Env = append(container.Env, corev1.EnvVar{
Name: "SSL_CERT_DIR",
Value: "/var/run/configs/tas/ca-trust:/var/run/secrets/kubernetes.io/serviceaccount",
})

if template.Spec.Containers[i].VolumeMounts == nil {
template.Spec.Containers[i].VolumeMounts = make([]corev1.VolumeMount, 0)
}
template.Spec.Containers[i].VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{
Name: "ca-trust",
MountPath: "/var/run/configs/tas/ca-trust",
ReadOnly: true,
})
}

projections := make([]corev1.VolumeProjection, 0)
if lor != nil {
projections = append(projections, corev1.VolumeProjection{
ConfigMap: &corev1.ConfigMapProjection{
LocalObjectReference: corev1.LocalObjectReference{
Name: lor.Name,
},
},
})
}

if template.Spec.Volumes == nil {
template.Spec.Volumes = make([]corev1.Volume, 0)
}
template.Spec.Volumes = append(template.Spec.Volumes, corev1.Volume{
Name: "ca-trust",
VolumeSource: corev1.VolumeSource{
Projected: &corev1.ProjectedVolumeSource{
Sources: projections,
DefaultMode: Pointer(int32(420)),
},
},
})
return nil
}

func TrustedCAAnnotationToReference(anns map[string]string) *v1alpha1.LocalObjectReference {
if v, ok := anns[annotations.TrustedCA]; ok {
return &v1alpha1.LocalObjectReference{
Name: v,
}
}
return nil
}
Loading

0 comments on commit 0738637

Please sign in to comment.