Skip to content

Docs

Welcome to the documentation.
The code and chart is avalible at github.com.

The purpose of this Operator is to create secrets from a CRD resource. This resource contains the encrypted secrets. As the name suggests, it uses age to decrypt secrets. The controller will create keys in the namespaces you specify. You can use these keys to encrypt your secrets, store them in Git or elsewhere, and apply them to the cluster. The controller will then check the encrypted secrets and decrypt them, creating a Kubernetes Secret from them.

The controller also exposes metrics, which can be monitored by creating a ServiceMonitor with the chart if desired. Additionally, a GUI is available that allows you to encrypt and decrypt secrets interactively. A CLI tool is also planned, which will allow users to perform encryption and decryption directly from the command line.

In addition, this Operator was created to address Bitnami’s recent licensing changes affecting Helm charts and images. Although Bitnami states that the SealSecrets Operator chart and images will remain free, it’s unclear what Broadcom might do next.

The way their Operator was built did not feel right to me. Their tools often contain multiple CVEs, and the CRDs they create are not the sole source of truth. For example, manually changing a secret can affect the CRDs, which will then report that they are no longer managed by SealSecrets. In my opinion, if you deploy CRDs, they should be the only source of truth. Any updates should be made through the CRDs themselves, not the resources created from them.

This project was initialized using Kubebuilder, a common tool used by most developers building an Operator. All code comments are generated by Kubebuilder. I initially removed the default commented-out code but later re-added it as optional features. This way, anyone who wants to fork the project can make use of these features if needed.

I also consulted AI at certain points to get ideas for implementation. Suggestions are never blindly copied. Each one is reviewed and applied if needed.

Note: I am relatively new to Go, so contributions and feedback are welcome!

Getting started

  • create crd ressource
  • crd has to be applied before using helm to install
kubectl apply -f https://raw.githubusercontent.com/callmewhatuwant/age-secret-operator/refs/heads/main/config/crd/bases/security.age.io_agesecrets.yaml
  • install
helm repo add age-secrets-operator \
https://age-secrets.com
helm install age-secrets-operator age-secrets-operator/age-secrets \
--namespace age-system --create-namespace
  • check install
kubectl wait --for=condition=Ready pods --all -n age-system
  • uninstall
helm uninstall -n age-system age-secrets-operator
kubectl delete namespace age-system

First secret

  • install age
sudo apt install age
  • get key
LATEST=$(kubectl get secrets -n age-system --no-headers -o custom-columns=":metadata.name" \
  | grep '^age-key-' | sort | tail -n1)

PUBLIC_KEY=$(kubectl get secret "$LATEST" -n age-system -o jsonpath='{.data.public}' | base64 --decode)

## public key:
echo "$PUBLIC_KEY"
  • encrypt with ur public key
echo test123 > secret.txt

age --armor -r "$PUBLIC_KEY" secret.txt
  • create namespace for testing the crd
kubectl create ns test
  • exmaple secret crd ressource
  • change password: value with your value from the age command
apiVersion: security.age.io/v1alpha1
kind: AgeSecret
metadata:
  name: db-passwd
  namespace: test
spec:
  encryptedData:
    password: |
      -----BEGIN AGE ENCRYPTED FILE-----
      YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWbHhqcGhyZ0ZSbXhQZXJ1
      aU1kL1NmZjYyaU9JQXlQazBuekdmMk8ySkYwCkloMGJxR0lXVG0yM2FXV3hrT3BI
      OXVwdzhrYWtGU0hwTUtLTHN5dzJBTGsKLS0tIEc0V1JmTUVpWkZuNGFGWXJJV3ow
      cWZpL09JTnFCVFFZbXRFQUY2QTdTbm8KdkZOvCXRqENpCw9ncrVP+qzDBTKwntfi
      ihgfMGuoy3Q37Dkqsw==
      -----END AGE ENCRYPTED FILE-----
  # define public key used with: recipients if you want to
  # the controller will try this key first
  # recipients:
  #   - string
  • verify
kubectl get secret -n test

Helm Options

## name override
fullnameOverride: age-secret-controller

## namespaces in wich new keys will be generated
## controller will also check in them for keys to decrypt
keyNamespaces: "age-secrets,test"

## controller values
ageSecretController:

## leader election
  leaderElection:
    enabled: true
    namespace: age-system

  ## replicas for ha
  replicas: 3

  # strategy for updating
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: "25%"
      maxUnavailable: "50%"

  controller:
    ## image
    image:
      repository: callmewhatuwant/age-secrets-operator
      tag: 0.0.01
    imagePullPolicy: IfNotPresent

    ## resources
    resources:
      limits:
        cpu: 200m
        memory: 128Mi
      requests:
        cpu: 100m
        memory: 64Mi

    ## security
    containerSecurityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop:
        - ALL
      seccompProfile:
        type: "RuntimeDefault"
      runAsNonRoot: true
      privileged: false
      runAsUser: 65532
      runAsGroup: 65532

## prometheus
metricsService:
  bindAddress: 8443
  secure: true
  auth: false

  type: ClusterIP
  ports:
    - port: 8443
      name: metrics
      targetPort: 8443

## monitor for prometheus
ServiceMonitor:
  enabled: false
  endpoints:
    - port: metrics
      scheme: https
      interval: 30s
      path: /metrics
      tlsConfig:
        insecureSkipVerify: true
        serverName: localhost

## job
ageKeyRotation:
  schedule: "0 0 1 * *"
  ## initial key
  initialRun:
    enabled: true

  ## image for cron and init job
  image:
    repository: callmewhatuwant/age-job
    tag: "3.23.0"
    pullPolicy: IfNotPresent

## gui
ageGui:
  enabled: true

  # replicas (ha is not tested)
  replicas: 1

  # strategy for updating
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: "25%"
      maxUnavailable: "50%"

  # image for gui
  image:
    repository: callmewhatuwant/age-gui
    tag: "alpine3.23-perl"
    pullPolicy: IfNotPresent

  resources:
    limits:
      cpu: 200m
      memory: 128Mi
    requests:
      cpu: 100m
      memory: 64Mi

  #sec context
  containerSecurityContext:
    allowPrivilegeEscalation: false
    capabilities:
      drop:
        - ALL
    seccompProfile:
      type: "RuntimeDefault"
    runAsNonRoot: true
    privileged: false
    runAsUser: 101
    runAsGroup: 101

  # service for gui
  service:
    type: ClusterIP
    ports:
      - name: http
        port: 80
        targetPort: 8080
        protocol: TCP

  # ingress for gui
  ingress:
    enabled: false
    host: age-gui.local
    ingressClassName: nginx
    annotations: {}
    tls: []
      # - hosts:
      #     - age-gui.local
      #   secretName: age-gui-tls

Enhancements

  • Open a merge request if you want to contribute to the project.
  • The project just started, so there’s probably a lot to improve.
  • Please don’t be too harsh on me. 🙂

Issue

  • Found a bug or have a feature request?
  • Please open an issue on github.com.

Other projects by me

Support me if you want

BTC:

bc1q7zgprykqzj4vprzxzafy5lskhpv7qau9p7a28r

Solana:

B6aGswkR4tpYDCaLny4B1rZWwQNrDk4dEvpEGjJw3GGG