Skip to content

Commit

Permalink
feat: support secret as a target
Browse files Browse the repository at this point in the history
Inspired by #108

Signed-off-by: Jiawei Wang <[email protected]>,Guillaume Ludinard <[email protected]>
  • Loading branch information
Jiawei0227 committed Oct 10, 2023
1 parent 3875e15 commit 834772e
Show file tree
Hide file tree
Showing 18 changed files with 1,251 additions and 112 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ ensure-cert-manager: ensure-kind $(BINDIR)/kubeconfig.yaml | $(BINDIR)/helm-$(HE
.PHONY: ensure-trust-manager
ensure-trust-manager: ensure-kind kind-load ensure-cert-manager | $(BINDIR)/helm-$(HELM_VERSION)/helm ## ensure trust-manager is available on cluster, built from local checkout
$(BINDIR)/helm-$(HELM_VERSION)/helm uninstall --kubeconfig $(BINDIR)/kubeconfig.yaml -n cert-manager trust-manager || :
$(BINDIR)/helm-$(HELM_VERSION)/helm upgrade --kubeconfig $(BINDIR)/kubeconfig.yaml -i -n cert-manager trust-manager deploy/charts/trust-manager/. --set image.tag=latest --set defaultTrustPackage.tag=latest$(DEBIAN_TRUST_PACKAGE_SUFFIX) --set app.logLevel=2 --wait
$(BINDIR)/helm-$(HELM_VERSION)/helm upgrade --kubeconfig $(BINDIR)/kubeconfig.yaml -i -n cert-manager trust-manager deploy/charts/trust-manager/. --set image.tag=latest --set defaultTrustPackage.tag=latest$(DEBIAN_TRUST_PACKAGE_SUFFIX) --set app.logLevel=2 --set authorizedSecrets="" --wait

# When running in our CI environment the Docker network's subnet choice
# causees issues with routing.
Expand Down
1 change: 1 addition & 0 deletions deploy/charts/trust-manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Kubernetes: `>= 1.25.0-0`
| app.webhook.tls.approverPolicy.certManagerNamespace | string | `"cert-manager"` | Namespace in which cert-manager was installed. Only used if app.webhook.tls.approverPolicy.enabled is true |
| app.webhook.tls.approverPolicy.certManagerServiceAccount | string | `"cert-manager"` | Name of cert-manager's ServiceAccount. Only used if app.webhook.tls.approverPolicy.enabled is true |
| app.webhook.tls.approverPolicy.enabled | bool | `false` | Whether to create an approver-policy CertificateRequestPolicy allowing auto-approval of the trust-manager webhook certificate. If you have approver-policy installed, you almost certainly want to enable this. |
| authorizedSecrets | list | `["ca-bundle"]` | List of secrets trust-manager is allowed to read/write. Used when the secret target is set when creating a bundle. |
| crds.enabled | bool | `true` | Whether or not to install the crds. |
| defaultPackage.enabled | bool | `true` | Whether to load the default trust package during pod initialization and include it in main container args. This container enables the 'useDefaultCAs' source on Bundles. |
| defaultPackageImage.digest | string | `nil` | Target image digest. Will override any tag if set. for example: digest: sha256:0e072dddd1f7f8fc8909a2ca6f65e76c5f0d2fcfb8be47935ae3457e8bbceb20 |
Expand Down
14 changes: 14 additions & 0 deletions deploy/charts/trust-manager/templates/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,17 @@ rules:
resources:
- "events"
verbs: ["create", "patch"]

- apiGroups:
- ""
resources:
- "secrets"
verbs: ["create", "list", "watch"]
- apiGroups:
- ""
resources:
- "secrets"
verbs: ["get", "update", "delete", "patch"]
{{- if .Values.authorizedSecrets }}
resourceNames: {{ .Values.authorizedSecrets | toYaml | nindent 2 }}
{{- end -}}
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ spec:
type: object
additionalProperties:
type: string
secret:
description: Secret is the target Secret in Namespaces that all Bundle source data will be synced to.
type: object
required:
- key
properties:
key:
description: Key is the key of the entry in the object's `data` field to be used.
type: string
status:
description: Status of the Bundle. This is set and managed automatically.
type: object
Expand Down Expand Up @@ -220,6 +229,15 @@ spec:
type: object
additionalProperties:
type: string
secret:
description: Secret is the target Secret in Namespaces that all Bundle source data will be synced to.
type: object
required:
- key
properties:
key:
description: Key is the key of the entry in the object's `data` field to be used.
type: string
served: true
storage: true
subresources:
Expand Down
4 changes: 4 additions & 0 deletions deploy/charts/trust-manager/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ app:
# -- If false, disables the default seccomp profile, which might be required to run on certain platforms
seccompProfileEnabled: true

# -- List of secrets trust-manager is allowed to read/write. Used when the secret target is set when creating
# a bundle. If the list is empty it means read/write access to all secrets.
authorizedSecrets: ["test-bundle"]

resources: {}
# -- Kubernetes pod resource limits for trust.
# limits:
Expand Down
18 changes: 18 additions & 0 deletions deploy/crds/trust.cert-manager.io_bundles.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ spec:
type: object
additionalProperties:
type: string
secret:
description: Secret is the target Secret in Namespaces that all Bundle source data will be synced to.
type: object
required:
- key
properties:
key:
description: Key is the key of the entry in the object's `data` field to be used.
type: string
status:
description: Status of the Bundle. This is set and managed automatically.
type: object
Expand Down Expand Up @@ -219,6 +228,15 @@ spec:
type: object
additionalProperties:
type: string
secret:
description: Secret is the target Secret in Namespaces that all Bundle source data will be synced to.
type: object
required:
- key
properties:
key:
description: Key is the key of the entry in the object's `data` field to be used.
type: string
served: true
storage: true
subresources:
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/trust/v1alpha1/types_bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ type BundleTarget struct {
// data will be synced to.
ConfigMap *KeySelector `json:"configMap,omitempty"`

// Secret is the target Secret in Namespaces that all Bundle source
// data will be synced to.
Secret *KeySelector `json:"secret,omitempty"`

// AdditionalFormats specifies any additional formats to write to the target
// +optional
AdditionalFormats *AdditionalFormats `json:"additionalFormats,omitempty"`
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/trust/v1alpha1/zz_generated.deepcopy.go

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

115 changes: 94 additions & 21 deletions pkg/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type bundle struct {
// a direct Kubernetes client (only used for CSA to CSA migration)
directClient client.Client

// targetCache is a cache.Cache that holds cached ConfigMap
// targetCache is a cache.Cache that holds cached ConfigMap and Secret
// resources that are used as targets for Bundles.
targetCache client.Reader

Expand Down Expand Up @@ -204,7 +204,7 @@ func (b *bundle) reconcileBundle(ctx context.Context, req ctrl.Request) (result
}

// Find all old existing ConfigMap targetResources.
{
if bundle.Spec.Target.ConfigMap != nil {
configMapList := &metav1.PartialObjectMetadataList{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Expand Down Expand Up @@ -250,32 +250,105 @@ func (b *bundle) reconcileBundle(ctx context.Context, req ctrl.Request) (result
}
}

// Find all old existing Secret targetResources.
if bundle.Spec.Target.Secret != nil {
secretLists := &metav1.PartialObjectMetadataList{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
}
err := b.targetCache.List(ctx, secretLists, &client.ListOptions{
LabelSelector: labels.SelectorFromSet(map[string]string{
trustapi.BundleLabelKey: bundle.Name,
}),
})
if err != nil {
log.Error(err, "failed to list secrets")
b.recorder.Eventf(&bundle, corev1.EventTypeWarning, "SecretListError", "Failed to list secrets: %s", err)
return ctrl.Result{}, nil, fmt.Errorf("failed to list Secrets: %w", err)
}

for _, secret := range secretLists.Items {
key := types.NamespacedName{
Name: secret.Name,
Namespace: secret.Namespace,
}

secretLog := log.WithValues("secret", key)

if _, ok := targetResources[key]; ok {
// This Secret is still a target, so we don't need to remove it.
continue
}

// Don't reconcile target for Secrets that are being deleted.
if secret.GetDeletionTimestamp() != nil {
secretLog.V(2).WithValues("deletionTimestamp", secret.GetDeletionTimestamp()).Info("skipping sync for secret as it is being deleted")
continue
}

if !metav1.IsControlledBy(&secret, &bundle) {
secretLog.V(2).Info("skipping sync for configmap as it is not controlled by bundle")
continue
}

targetResources[key] = false
}
}

var needsUpdate bool

for target, shouldExist := range targetResources {
targetLog := log.WithValues("target", target)
var cmSynced, secretSynced bool
var err error

if bundle.Spec.Target.ConfigMap != nil {
cmSynced, err = b.syncConfigMapTarget(ctx, targetLog, &bundle, target.Name, target.Namespace, resolvedBundle.data, shouldExist)
if err != nil {
targetLog.Error(err, "failed sync bundle to ConfigMap target namespace")
b.recorder.Eventf(&bundle, corev1.EventTypeWarning, "SyncConfigMapTargetFailed", "Failed to sync target in Namespace %q: %s", target.Namespace, err)

b.setBundleCondition(
bundle.Status.Conditions,
&statusPatch.Conditions,
trustapi.BundleCondition{
Type: trustapi.BundleConditionSynced,
Status: metav1.ConditionFalse,
Reason: "SyncConfigMapTargetFailed",
Message: fmt.Sprintf("Failed to sync bundle to namespace %q: %s", target.Namespace, err),
ObservedGeneration: bundle.Generation,
},
)

return ctrl.Result{Requeue: true}, statusPatch, nil
}
}

synced, err := b.syncTarget(ctx, targetLog, &bundle, target.Name, target.Namespace, resolvedBundle.data, shouldExist)
if err != nil {
targetLog.Error(err, "failed sync bundle to target namespace")
b.recorder.Eventf(&bundle, corev1.EventTypeWarning, "SyncTargetFailed", "Failed to sync target in Namespace %q: %s", target.Namespace, err)

b.setBundleCondition(
bundle.Status.Conditions,
&statusPatch.Conditions,
trustapi.BundleCondition{
Type: trustapi.BundleConditionSynced,
Status: metav1.ConditionFalse,
Reason: "SyncTargetFailed",
Message: fmt.Sprintf("Failed to sync bundle to namespace %q: %s", target.Namespace, err),
ObservedGeneration: bundle.Generation,
},
)

return ctrl.Result{Requeue: true}, statusPatch, nil
if bundle.Spec.Target.Secret != nil {
secretSynced, err = b.syncSecretTarget(ctx, targetLog, &bundle, target.Name, target.Namespace, resolvedBundle.data, shouldExist)
if err != nil {
targetLog.Error(err, "failed sync bundle to Secret target namespace")
b.recorder.Eventf(&bundle, corev1.EventTypeWarning, "SyncSecretTargetFailed", "Failed to sync target in Namespace %q: %s", target.Namespace, err)

b.setBundleCondition(
bundle.Status.Conditions,
&statusPatch.Conditions,
trustapi.BundleCondition{
Type: trustapi.BundleConditionSynced,
Status: metav1.ConditionFalse,
Reason: "SyncSecretTargetFailed",
Message: fmt.Sprintf("Failed to sync bundle to namespace %q: %s", target.Namespace, err),
ObservedGeneration: bundle.Generation,
},
)

return ctrl.Result{Requeue: true}, statusPatch, nil
}
}

if synced {
if cmSynced || secretSynced {
// We need to update if any target is synced.
needsUpdate = true
}
Expand Down
13 changes: 13 additions & 0 deletions pkg/bundle/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ func AddBundleController(
builder.OnlyMetadata,
).

// Reconcile a Bundle on events against a Secret that it
// owns. Only cache Secret metadata.
WatchesRawSource(
source.Kind(targetCache, &corev1.Secret{}),
handler.EnqueueRequestForOwner(
mgr.GetScheme(),
mgr.GetRESTMapper(),
&trustapi.Bundle{},
handler.OnlyControllerOwner(),
),
builder.OnlyMetadata,
).

////// Sources //////

// Reconcile trust.cert-manager.io Bundles
Expand Down
23 changes: 23 additions & 0 deletions pkg/bundle/internal/ssa_client/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,26 @@ func GenerateConfigMapPatch(

return configmap, applyPatch{encodedPatch}, nil
}

func GenerateSecretPatch(
secretPatch *coreapplyconfig.SecretApplyConfiguration,
) (*corev1.Secret, client.Patch, error) {
if secretPatch == nil || secretPatch.Name == nil || secretPatch.Namespace == nil {
panic("secretPatch must be non-nil and have a name and namespace")
}

// This object is used to deduce the name & namespace + unmarshall the return value in
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: *secretPatch.Name,
Namespace: *secretPatch.Namespace,
},
}

encodedPatch, err := json.Marshal(secretPatch)
if err != nil {
return secret, nil, err
}

return secret, applyPatch{encodedPatch}, nil
}
Loading

0 comments on commit 834772e

Please sign in to comment.