Skip to main content
Skip table of contents

Monofor Pam Operator

Monofor PAM Operator — Kubernetes Integration Guide

Monofor PAM Operator is a native Kubernetes operator that automatically synchronises
secrets from Monofor PAM Vault into Kubernetes Secret objects. Unlike the webhook approach, PAM Operator connects directly to the PAM API — no middleware or webhook configuration is required.

Prerequisites: A valid license and a running Monofor PAM instance are required before
proceeding.

How It Works

CODE
MonoSecret (CRD)
      │
      ▼
MonoStore (CRD) ──► Monofor PAM API
      │
      ▼
Kubernetes Secret
  • MonoStore holds the PAM connection URL and the Access Key reference.

  • MonoSecret defines which vaults to fetch and which Kubernetes Secret to write into.

  • The operator reconciles on creation, update, and periodically based on refreshInterval.

Step 1 — Install the CRDs

Apply the two CustomResourceDefinitions that define MonoStore and MonoSecret:

YAML
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    controller-gen.kubebuilder.io/version: v0.20.1
  name: monostores.pam.monofor.com
spec:
  group: pam.monofor.com
  names:
    kind: MonoStore
    listKind: MonoStoreList
    plural: monostores
    shortNames:
    - mstore
    singular: monostore
  scope: Namespaced
  versions:
  - additionalPrinterColumns:
    - jsonPath: .spec.provider.monoforPAM.url
      name: URL
      type: string
    - jsonPath: .status.conditions[?(@.type=='Ready')].status
      name: Ready
      type: string
    - jsonPath: .metadata.creationTimestamp
      name: Age
      type: date
    name: v1alpha1
    schema:
      openAPIV3Schema:
        description: MonoStore defines the connection configuration (URL and credentials)
          for Monofor PAM Vault.
        properties:
          apiVersion:
            type: string
          kind:
            type: string
          metadata:
            type: object
          spec:
            properties:
              provider:
                properties:
                  monoforPAM:
                    properties:
                      auth:
                        properties:
                          accessKeySecretRef:
                            properties:
                              key:
                                type: string
                              name:
                                type: string
                              namespace:
                                type: string
                            required:
                            - key
                            - name
                            type: object
                        type: object
                      url:
                        type: string
                    required:
                    - auth
                    - url
                    type: object
                required:
                - monoforPAM
                type: object
            required:
            - provider
            type: object
          status:
            properties:
              conditions:
                items:
                  properties:
                    lastTransitionTime:
                      format: date-time
                      type: string
                    message:
                      maxLength: 32768
                      type: string
                    observedGeneration:
                      format: int64
                      minimum: 0
                      type: integer
                    reason:
                      maxLength: 1024
                      minLength: 1
                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
                      type: string
                    status:
                      enum:
                      - "True"
                      - "False"
                      - Unknown
                      type: string
                    type:
                      maxLength: 316
                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
                      type: string
                  required:
                  - lastTransitionTime
                  - message
                  - reason
                  - status
                  - type
                  type: object
                type: array
                x-kubernetes-list-map-keys:
                - type
                x-kubernetes-list-type: map
            type: object
        type: object
    served: true
    storage: true
    subresources:
      status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    controller-gen.kubebuilder.io/version: v0.20.1
  name: monosecrets.pam.monofor.com
spec:
  group: pam.monofor.com
  names:
    kind: MonoSecret
    listKind: MonoSecretList
    plural: monosecrets
    shortNames:
    - msec
    singular: monosecret
  scope: Namespaced
  versions:
  - additionalPrinterColumns:
    - jsonPath: .spec.monoStoreRef.name
      name: Store
      type: string
    - jsonPath: .status.conditions[?(@.type=='Ready')].status
      name: Ready
      type: string
    - jsonPath: .status.refreshTime
      name: Last Synced
      type: date
    - jsonPath: .metadata.creationTimestamp
      name: Age
      type: date
    name: v1alpha1
    schema:
      openAPIV3Schema:
        description: MonoSecret defines which vaults to fetch from Monofor PAM and
          which Kubernetes Secret to write the resulting data into.
        properties:
          apiVersion:
            type: string
          kind:
            type: string
          metadata:
            type: object
          spec:
            properties:
              data:
                items:
                  properties:
                    remoteRef:
                      properties:
                        property:
                          type: string
                        vaultID:
                          type: string
                      required:
                      - vaultID
                      type: object
                    secretKey:
                      type: string
                  required:
                  - remoteRef
                  - secretKey
                  type: object
                type: array
              dataFrom:
                items:
                  properties:
                    extract:
                      properties:
                        property:
                          type: string
                        vaultID:
                          type: string
                      required:
                      - vaultID
                      type: object
                  required:
                  - extract
                  type: object
                type: array
              monoStoreRef:
                properties:
                  name:
                    type: string
                required:
                - name
                type: object
              refreshInterval:
                default: 1h
                type: string
              rollout:
                properties:
                  enabled:
                    type: boolean
                  targets:
                    items:
                      properties:
                        kind:
                          enum:
                          - Deployment
                          - DaemonSet
                          - StatefulSet
                          type: string
                        name:
                          type: string
                      required:
                      - kind
                      - name
                      type: object
                    type: array
                type: object
              target:
                properties:
                  creationPolicy:
                    default: Owner
                    enum:
                    - Owner
                    - Merge
                    - None
                    type: string
                  deletionPolicy:
                    default: Delete
                    enum:
                    - Delete
                    - Retain
                    type: string
                  name:
                    type: string
                  template:
                    properties:
                      metadata:
                        properties:
                          annotations:
                            additionalProperties:
                              type: string
                            type: object
                          labels:
                            additionalProperties:
                              type: string
                            type: object
                        type: object
                      type:
                        type: string
                    type: object
                type: object
            required:
            - monoStoreRef
            - target
            type: object
          status:
            properties:
              conditions:
                items:
                  properties:
                    lastTransitionTime:
                      format: date-time
                      type: string
                    message:
                      maxLength: 32768
                      type: string
                    observedGeneration:
                      format: int64
                      minimum: 0
                      type: integer
                    reason:
                      maxLength: 1024
                      minLength: 1
                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
                      type: string
                    status:
                      enum:
                      - "True"
                      - "False"
                      - Unknown
                      type: string
                    type:
                      maxLength: 316
                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
                      type: string
                  required:
                  - lastTransitionTime
                  - message
                  - reason
                  - status
                  - type
                  type: object
                type: array
                x-kubernetes-list-map-keys:
                - type
                x-kubernetes-list-type: map
              refreshTime:
                format: date-time
                type: string
              secretDataHash:
                type: string
            type: object
        type: object
    served: true
    storage: true
    subresources:
      status: {}
BASH
kubectl apply -f crds.yaml

# Verify
kubectl get crds | grep monofor
# Expected:
#   monosecrets.pam.monofor.com
#   monostores.pam.monofor.com

Step 2 — Deploy the Operator

Apply the namespace, RBAC resources, and the operator Deployment in order.

Namespace

YAML
apiVersion: v1
kind: Namespace
metadata:
  name: pam-operator-system
  labels:
    app.kubernetes.io/name: pam-operator

RBAC

YAML
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: pam-operator
  namespace: pam-operator-system
  labels:
    app.kubernetes.io/name: pam-operator
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pam-operator-manager-role
rules:
  - apiGroups: ["pam.monofor.com"]
    resources: ["monosecrets"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: ["pam.monofor.com"]
    resources: ["monosecrets/status"]
    verbs: ["get", "update", "patch"]
  - apiGroups: ["pam.monofor.com"]
    resources: ["monosecrets/finalizers"]
    verbs: ["update"]
  - apiGroups: ["pam.monofor.com"]
    resources: ["monostores"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "patch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "daemonsets", "statefulsets"]
    verbs: ["get", "list", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: pam-operator-manager-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: pam-operator-manager-role
subjects:
  - kind: ServiceAccount
    name: pam-operator
    namespace: pam-operator-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pam-operator-leader-election-role
  namespace: pam-operator-system
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: ["coordination.k8s.io"]
    resources: ["leases"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pam-operator-leader-election-rolebinding
  namespace: pam-operator-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: pam-operator-leader-election-role
subjects:
  - kind: ServiceAccount
    name: pam-operator
    namespace: pam-operator-system

Deployment

Replace <VERSION> with the image version you have pushed to the registry
(e.g. cr.monofor.com/monofor/pam-operator:1.0.0).

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pam-operator
  namespace: pam-operator-system
  labels:
    app.kubernetes.io/name: pam-operator
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: pam-operator
  template:
    metadata:
      labels:
        app.kubernetes.io/name: pam-operator
    spec:
      serviceAccountName: pam-operator
      securityContext:
        runAsNonRoot: true
      containers:
        - name: manager
          image: cr.monofor.com/monofor/pam-operator:<VERSION>
          imagePullPolicy: IfNotPresent
          command: ["/manager"]
          args:
            - "--leader-elect=true"
            - "--metrics-bind-address=:8080"
            - "--health-probe-bind-address=:8081"
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
          ports:
            - name: metrics
              containerPort: 8080
              protocol: TCP
            - name: health
              containerPort: 8081
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8081
            initialDelaySeconds: 15
            periodSeconds: 20
          readinessProbe:
            httpGet:
              path: /readyz
              port: 8081
            initialDelaySeconds: 5
            periodSeconds: 10
          resources:
            limits:
              cpu: 200m
              memory: 128Mi
            requests:
              cpu: 10m
              memory: 64Mi
      terminationGracePeriodSeconds: 10
BASH
kubectl apply -f namespace.yaml
kubectl apply -f rbac.yaml
kubectl apply -f deployment.yaml

# Verify the pod is running
kubectl -n pam-operator-system get pods
# Expected:
#   NAME                       READY   STATUS    RESTARTS   AGE
#   pam-operator-xxxx-xxxx     1/1     Running   0          30s

# Follow operator logs
kubectl -n pam-operator-system logs -f deployment/pam-operator

Step 3 — Create the PAM Access Key Secret

In Monofor PAM, navigate to Access Keys and generate a new Access Key.
Copy the generated UUID.

Store the Access Key in a Kubernetes Secret in the same namespace where you will create
your MonoStore:

YAML
apiVersion: v1
kind: Secret
metadata:
  name: pam-access-key
  namespace: default
type: Opaque
stringData:
  key: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
BASH
kubectl apply -f pam-access-key.yaml

Step 4 — Create a MonoStore

A MonoStore defines the connection to your Monofor PAM instance. Replace <MONOPAM-URL>
with your PAM API base URL.

YAML
apiVersion: pam.monofor.com/v1alpha1
kind: MonoStore
metadata:
  name: monofor-pam
  namespace: default
spec:
  provider:
    monoforPAM:
      url: "https://<MONOPAM-URL>"
      auth:
        accessKeySecretRef:
          name: pam-access-key
          key: key
          namespace: default
BASH
kubectl apply -f monostore.yaml

# Verify
kubectl get mstore -n default

Parameter

Description

url

Base URL of your Monofor PAM API (e.g. <https://pam.monofor.com)>

accessKeySecretRef.name

Name of the Kubernetes Secret containing the Access Key

accessKeySecretRef.key

Key within the Secret whose value is the Access Key UUID

accessKeySecretRef.namespace

Namespace of the Secret (defaults to MonoStore namespace if omitted)

Step 5 — Create a MonoSecret

A MonoSecret tells the operator which PAM vaults to fetch and where to write the result.
The Vault ID (UUID) is found in the Monofor PAM UI on each vault item's detail page.

Reading a SecretText vault

YAML
apiVersion: pam.monofor.com/v1alpha1
kind: MonoSecret
metadata:
  name: app-secret
  namespace: default
spec:
  monoStoreRef:
    name: monofor-pam
  target:
    name: app-secret-k8s
  refreshInterval: "1h"
  data:
    - secretKey: SECRET_VALUE
      remoteRef:
        vaultID: "<VAULT-ID>"
        property: "SecretText"

Reading UserName and Password from a UserNamePassword vault

YAML
apiVersion: pam.monofor.com/v1alpha1
kind: MonoSecret
metadata:
  name: database-credentials
  namespace: default
spec:
  monoStoreRef:
    name: monofor-pam
  target:
    name: database-credentials-secret
  refreshInterval: "1h"
  data:
    - secretKey: username
      remoteRef:
        vaultID: "<VAULT-ID>"
        property: "UserName"
    - secretKey: password
      remoteRef:
        vaultID: "<VAULT-ID>"
        property: "Password"

Reading all fields at once with dataFrom

dataFrom fetches all fields from a vault in a single operation. Each field is written as
a separate key in the Kubernetes Secret — no prefix is added.

YAML
apiVersion: pam.monofor.com/v1alpha1
kind: MonoSecret
metadata:
  name: pki-credentials
  namespace: default
spec:
  monoStoreRef:
    name: monofor-pam
  target:
    name: pki-credentials-secret
  refreshInterval: "24h"
  dataFrom:
    - extract:
        vaultID: "<VAULT-ID>"

Reading an OTP code

Note: OTP codes are time-based. Use a short refreshInterval (e.g. "30s") to keep
the Kubernetes Secret in sync with the current code.

YAML
apiVersion: pam.monofor.com/v1alpha1
kind: MonoSecret
metadata:
  name: otp-code
  namespace: default
spec:
  monoStoreRef:
    name: monofor-pam
  target:
    name: otp-code-secret
  refreshInterval: "30s"
  data:
    - secretKey: OTP_CODE
      remoteRef:
        vaultID: "<VAULT-ID>"
        property: "Code"

Parameter reference

Parameter

Description

monoStoreRef.name

Name of the MonoStore to use for PAM connectivity

target.name

Name of the Kubernetes Secret to create or update

target.creationPolicy

Owner (default) — operator owns the Secret; Merge — merge into existing; None — only update existing

target.deletionPolicy

Delete (default) — Secret is deleted when MonoSecret is deleted; Retain — Secret is kept

refreshInterval

How often to re-sync from PAM. Valid formats: "30s", "5m", "1h", "2h30m", "0" (sync once)

data[].secretKey

Key name in the target Kubernetes Secret

data[].remoteRef.vaultID

UUID of the vault item in Monofor PAM

data[].remoteRef.property

Specific field to read from the vault (see table below)

dataFrom[].extract.vaultID

UUID of the vault item — all fields are fetched and written as separate keys

Vault item types and available properties

Vault Item Type

Available Properties

SecretText

SecretText

UserName and Password

UserName, Password

UserName and Password (with Domain)

UserName, Password, Domain

PKI

UserName, PrivateKey, Passphrase

OTP

Code

Step 6 — Verify the Sync

BASH
# Check sync status
kubectl get msec -n default

# Expected output:
#   NAME                   STORE         READY   LAST SYNCED   AGE
#   database-credentials   monofor-pam   True    2s            5s

# Inspect the synced Kubernetes Secret
kubectl get secret database-credentials-secret -n default -o yaml

# Decode a specific value
kubectl get secret database-credentials-secret -n default \
  -o jsonpath='{.data.username}' | base64 -d

Step 7 — Test with a Deployment (Optional)

The following example deploys an nginx pod that reads credentials from the synced Secret
as environment variables.

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: monofor-test
  namespace: default
  labels:
    app: monofor-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: monofor-test
  template:
    metadata:
      labels:
        app: monofor-test
    spec:
      containers:
        - name: monofor-test
          image: nginx
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 250m
              memory: 256Mi
          env:
            - name: DB_USERNAME
              valueFrom:
                secretKeyRef:
                  name: database-credentials-secret
                  key: username
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: database-credentials-secret
                  key: password
BASH
kubectl apply -f monofor-test.yaml

# Wait for the pod to start
kubectl get pods -n default

# Verify the environment variables are injected
kubectl exec -it -n default deploy/monofor-test -- /bin/sh -c 'env | grep DB_'

# Cleanup
kubectl delete -f monofor-test.yaml

Step 8 — Auto-Rollout on Secret Change (Optional)

PAM Operator can automatically restart workloads when a PAM-synced secret changes.
The restart is triggered only when the secret data actually changes — not on every
refreshInterval tick. No external tools (e.g. Stakater Reloader) are required.

Mode A — Explicit targets in the MonoSecret spec

YAML
spec:
  rollout:
    enabled: true
    targets:
      - kind: Deployment
        name: my-app
      - kind: DaemonSet
        name: my-daemon
      - kind: StatefulSet
        name: my-stateful

Mode B — Workload annotation

Add the annotation to the top-level metadata of any Deployment, DaemonSet, or
StatefulSet. The value must match the target Kubernetes Secret name
(MonoSecret.spec.target.name):

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: default
  annotations:
    pam.monofor.com/reload-on: "database-credentials-secret"

Important: The annotation must be placed on the workload's top-level
metadata.annotations, not inside spec.template.metadata.annotations.

Both modes are evaluated on every sync. A workload matched by either mode is restarted once
per change (deduplication is handled automatically). Supported workload kinds: Deployment,
DaemonSet, StatefulSet.

BASH
# Confirm a rollout was triggered
kubectl get deployment my-app -n default \
  -o jsonpath='{.spec.template.metadata.annotations.pam\.monofor\.com/restartedAt}'

# Watch the rollout
kubectl rollout status deployment/my-app -n default
JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.