Skip to content

Instantly share code, notes, and snippets.

@paraddise
Last active December 24, 2023 20:21
Show Gist options
  • Save paraddise/010095d0e52c7cae8044f30d20cd36a4 to your computer and use it in GitHub Desktop.
Save paraddise/010095d0e52c7cae8044f30d20cd36a4 to your computer and use it in GitHub Desktop.
Writing Kyvenro policy to disallow acccess for not permitted keys in Shared Secret Store

Problem

Sometimes you need to dynamically get acces to secrets from different namespaces.

For example: You have dynamically created namespaces, so you need individual token for each namespace to access secret by individual key.

The easiest, but unscalable solution to create SecretStore in each namespace, but for that, you need to create one more token in Vault.

One more solution

Inspired by this article - https://external-secrets.io/latest/guides/multi-tenancy/

We create Shared Secret Store, that has access to all secrets for needed namespaces

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: shared-secret-store
spec:
  provider:
    vault:
      auth:
        tokenSecretRef:
          namespace: external-secrets
          name: vault-shared-secret-store-token
          key: vault-token
      server: "http://vault.vault.svc.cluster.local:8200"
      path: "k8s"
      version: 'v2'

So we need the option, to set allowed base path for all secret keys in that namespace or ArgoProject

apiVersion: v1
kind: Namespace
metadata:
  labels:
    kubernetes.io/metadata.name: ns1
    name: ns1
    external-secrets/allowed-path: ns1/ # allowed base path for all secrets
  name: ns1

Therefore this secret should be allowed

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: ns1-secret
  namespace: ns1
spec:
  secretStoreRef:
    name: shared-secret-store
    kind: ClusterSecretStore
  refreshInterval: 5m
  target:
    deletionPolicy: Delete
  dataFrom:
    - extract:
        key: ns1/story-1/task-1

But this not

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: ns2-secret
  namespace: ns2
spec:
  secretStoreRef:
    name: shared-secret-store
    kind: ClusterSecretStore
  refreshInterval: 5m
  target:
    deletionPolicy: Delete
  dataFrom:
    - extract:
        key: ns1/story-1/task-1 # take secret from one ns for another ns (steal secret)

Create ClusterPolicy to reject that behaviour

---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: shared-secret-store-policy
spec:
  validationFailureAction: Enforce
  rules:
    - name: allow-only-permitted-keys
      context:
      # Get allowed path for this namespace
      - name: allowed_path
        apiCall:
          # Get namespace where resource is applied
          urlPath: "/api/v1/namespaces/{{ request.namespace }}"
          method: GET
          # Get our label and cnonicalize it, e.g. remove redundant slashed // -> /
          jmesPath: path_canonicalize(metadata.annotations."external-secrets/allowed-path")
      - name: secret_store_name
        variable:
          # Name of ClusterSecretStore, so we allow create ExternalSecret from another SecretStore
          value: "shared-secret-store"
      match:
        resources:
          kinds:
            - ExternalSecret
      preconditions:
        all:
          - key: "{{ request.operation || 'BACKGROUND' }}"
            operator: NotEquals
            value: DELETE
      validate:
        message: "Path not allowed for this namespace"
        pattern:
          spec:
            secretStoreRef:
              <(name): "{{ secret_store_name }}"
              # Or the target SecretStore namespaced
              <(kind): ClusterSecretStore
            dataFrom:
              - extract:
                  key: "{{ allowed_path }}*" # wildcard match
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment