Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 156 additions & 2 deletions USAGEGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
4. [Logging level](#logging-level)
5. [Usage examples](#usage-examples)
6. [How 1Password Items Map to Kubernetes Secrets](#how-1password-items-map-to-kubernetes-secrets)
7. [Configuring Automatic Rolling Restarts of Deployments](#configuring-automatic-rolling-restarts-of-deployments)
8. [Development](#development)
7. [Secret Templates](#secret-templates)
8. [Configuring Automatic Rolling Restarts of Deployments](#configuring-automatic-rolling-restarts-of-deployments)
9. [Development](#development)


---
Expand Down Expand Up @@ -126,6 +127,159 @@ Titles and field names that include white space and other characters that are no

---

## Secret Templates

By default, each field in a 1Password item maps directly to a key in the
Kubernetes Secret. **Secret templates** let you transform item data into custom
formats using [Go templates](https://pkg.go.dev/text/template) so that a
single `OnePasswordItem` can produce exactly the secret layout your application
expects.

### Basic example

```yaml
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: my-database-config
spec:
itemPath: "vaults/my-vault/items/my-db-item"
template:
data:
DSN: "postgresql://{{ .Fields.username }}:{{ .Fields.password }}@{{ .Fields.host }}:{{ .Fields.port }}/{{ .Fields.database }}"
```

Instead of creating a secret with individual keys for `username`, `password`,
`host`, `port`, and `database`, the operator creates a single `DSN` key whose
value is the rendered connection string.

### Multiple keys

You can define as many output keys as you need:

```yaml
spec:
itemPath: "vaults/my-vault/items/my-item"
template:
data:
config.yaml: |
server:
username: {{ .Fields.username }}
password: {{ .Fields.password }}
DB_HOST: "{{ .Fields.host }}"
```

### Template context

The following data is available inside templates:

| Expression | Description |
|---|---|
| `{{ .Fields.<label> }}` | Value of a field by its label (works when the label is a valid Go identifier). |
| `{{ index .Fields "<label>" }}` | Value of a field by its label. Required for labels that contain hyphens or other special characters, e.g. `{{ index .Fields "api-key" }}`. |
| `{{ .Sections.<title>.<label> }}` | Value of a field within a named section, e.g. `{{ .Sections.Database.username }}`. |
| `{{ index .Sections "<title>" "<label>" }}` | Same, using `index` for special-character titles/labels. |
| `{{ .FieldsByID.<id> }}` | Value of a field by its unique 1Password field ID. Use this when labels are duplicated across sections. |

### Behaviour notes

- When a `template` is specified, **only** the keys defined in `template.data`
appear in the Kubernetes Secret. Individual item fields are **not** added as
separate keys.
- If a template fails to render (e.g. syntax error or missing field), that key
is skipped and an error is logged. Other keys in the same template are still
rendered.
- If `template` is omitted (or its `data` map is empty), the operator falls
back to the default behaviour of mapping fields, URLs and files directly.
- All standard [Go template functions](https://pkg.go.dev/text/template#hdr-Functions)
are available (`index`, `printf`, `len`, `eq`, conditional blocks, ranges,
etc.).

---

## Image Pull Secrets

The operator can automatically generate `kubernetes.io/dockerconfigjson` secrets
for use as Kubernetes
[image pull secrets](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/).
Store your registry credentials in a 1Password item and let the operator
construct the properly formatted `.dockerconfigjson` for you.

### Quick example

Given a 1Password item with the following fields:

| Field label | Value |
|---|---|
| `registry` | `ghcr.io` |
| `username` | `my-user` |
| `password` | `ghp_xxxxxxxxxxxx` |
| `email` | `me@example.com` |

Create a `OnePasswordItem` with `spec.imagePullSecret`:

```yaml
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: ghcr-pull-secret
spec:
itemPath: "vaults/my-vault/items/ghcr-credentials"
imagePullSecret:
registryField: "registry"
usernameField: "username"
passwordField: "password"
emailField: "email" # optional
```

The operator will:
1. Look up each field by its label in the 1Password item.
2. Build a `.dockerconfigjson` with the base64-encoded `auth` string.
3. Automatically set the secret type to `kubernetes.io/dockerconfigjson`.

The resulting Kubernetes Secret is equivalent to running:

```sh
kubectl create secret docker-registry ghcr-pull-secret \
--docker-server=ghcr.io \
--docker-username=my-user \
--docker-password=ghp_xxxxxxxxxxxx \
--docker-email=me@example.com
```

You can then reference it in your Pods / Deployments:

```yaml
spec:
imagePullSecrets:
- name: ghcr-pull-secret
```

### Configuration reference

| Field | Required | Description |
|---|---|---|
| `registryField` | **Yes** | Label of the 1Password field containing the registry URL (e.g. `ghcr.io`, `docker.io`). |
| `usernameField` | **Yes** | Label of the 1Password field containing the username. |
| `passwordField` | **Yes** | Label of the 1Password field containing the password or access token. |
| `emailField` | No | Label of the 1Password field containing the email address. Omit if your registry does not require it. |

### Behaviour notes

- When `imagePullSecret` is set, the operator **only** produces the
`.dockerconfigjson` key. Individual fields are **not** added as separate
keys.
- If the required fields (registry, username, or password) cannot be resolved
from the 1Password item, the operator logs an error and falls back to the
default field-mapping behaviour.
- You can explicitly set `type: kubernetes.io/dockerconfigjson` on the
`OnePasswordItem`, but it is not required — the operator sets it
automatically when `imagePullSecret` is configured.
- `imagePullSecret` takes priority over `template`. If both are set,
`imagePullSecret` wins.

---

## Configuring Automatic Rolling Restarts of Deployments

If a 1Password Item that is linked to a Kubernetes Secret is updated, any deployments configured to `auto-restart` AND are using that secret will be given a rolling restart the next time 1Password Connect is polled for updates.
Expand Down
37 changes: 37 additions & 0 deletions api/v1/onepassworditem_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,49 @@ import (
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

// SecretTemplate defines Go templates for generating secret data keys.
// Each key in Data is a secret data key, and its value is a Go template string
// that will be rendered using the 1Password item's fields as context.
type SecretTemplate struct {
// Data is a map of secret data key names to Go template strings.
// Templates can access fields via .Fields (flat map), .Sections (nested by section),
// or .FieldsByID (by field ID).
// +optional
Data map[string]string `json:"data,omitempty"`
}

// ImagePullSecretConfig configures automatic dockerconfigjson generation for image pull secrets.
// When set, the operator constructs a properly formatted .dockerconfigjson from the specified
// 1Password item fields, and automatically sets the secret type to kubernetes.io/dockerconfigjson.
type ImagePullSecretConfig struct {
// RegistryField is the label of the 1Password field containing the registry URL (e.g. "ghcr.io").
RegistryField string `json:"registryField,omitempty"`
// UsernameField is the label of the 1Password field containing the registry username.
UsernameField string `json:"usernameField,omitempty"`
// PasswordField is the label of the 1Password field containing the registry password or token.
PasswordField string `json:"passwordField,omitempty"`
// EmailField is the label of the 1Password field containing the email (optional).
EmailField string `json:"emailField,omitempty"`
}

// OnePasswordItemSpec defines the desired state of OnePasswordItem
type OnePasswordItemSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file

ItemPath string `json:"itemPath,omitempty"`

// Template defines Go templates for generating custom secret data.
// When set, the secret data will be generated by rendering the templates
// instead of using the default 1:1 field-to-key mapping.
// +optional
Template *SecretTemplate `json:"template,omitempty"`

// ImagePullSecret configures automatic dockerconfigjson generation.
// When set, the operator builds a .dockerconfigjson from the 1Password item fields
// mapped by the config, and sets the secret type to kubernetes.io/dockerconfigjson.
// +optional
ImagePullSecret *ImagePullSecretConfig `json:"imagePullSecret,omitempty"`
}

type OnePasswordItemConditionType string
Expand Down
49 changes: 48 additions & 1 deletion api/v1/zz_generated.deepcopy.go

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

38 changes: 38 additions & 0 deletions config/crd/bases/onepassword.com_onepassworditems.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,46 @@ spec:
spec:
description: OnePasswordItemSpec defines the desired state of OnePasswordItem
properties:
imagePullSecret:
description: |-
ImagePullSecret configures automatic dockerconfigjson generation.
When set, the operator builds a .dockerconfigjson from the 1Password item fields
mapped by the config, and sets the secret type to kubernetes.io/dockerconfigjson.
properties:
emailField:
description: EmailField is the label of the 1Password field containing
the email (optional).
type: string
passwordField:
description: PasswordField is the label of the 1Password field
containing the registry password or token.
type: string
registryField:
description: RegistryField is the label of the 1Password field
containing the registry URL (e.g. "ghcr.io").
type: string
usernameField:
description: UsernameField is the label of the 1Password field
containing the registry username.
type: string
type: object
itemPath:
type: string
template:
description: |-
Template defines Go templates for generating custom secret data.
When set, the secret data will be generated by rendering the templates
instead of using the default 1:1 field-to-key mapping.
properties:
data:
additionalProperties:
type: string
description: |-
Data is a map of secret data key names to Go template strings.
Templates can access fields via .Fields (flat map), .Sections (nested by section),
or .FieldsByID (by field ID).
type: object
type: object
type: object
status:
description: OnePasswordItemStatus defines the observed state of OnePasswordItem
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/deployment_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,5 @@ func (r *DeploymentReconciler) handleApplyingDeployment(ctx context.Context, dep
UID: deployment.GetUID(),
}

return kubeSecrets.CreateKubernetesSecretFromItem(ctx, r.Client, secretName, namespace, item, annotations[op.AutoRestartWorkloadAnnotation], secretLabels, annotations, secretType, ownerRef, r.Config.AllowEmptyValues)
return kubeSecrets.CreateKubernetesSecretFromItem(ctx, r.Client, secretName, namespace, item, annotations[op.AutoRestartWorkloadAnnotation], secretLabels, annotations, secretType, ownerRef, r.Config.AllowEmptyValues, nil, nil)
}
11 changes: 10 additions & 1 deletion internal/controller/onepassworditem_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,15 @@ func (r *OnePasswordItemReconciler) handleOnePasswordItem(ctx context.Context, r
return fmt.Errorf("failed to retrieve item: %w", err)
}

// Extract template and imagePullSecret config from spec.
secretTemplate := resource.Spec.Template
imagePullSecret := resource.Spec.ImagePullSecret

// If imagePullSecret is configured, automatically set secret type to dockerconfigjson.
if imagePullSecret != nil && secretType == "" {
secretType = string(corev1.SecretTypeDockerConfigJson)
}

// Create owner reference.
gvk, err := apiutil.GVKForObject(resource, r.Scheme)
if err != nil {
Expand All @@ -188,7 +197,7 @@ func (r *OnePasswordItemReconciler) handleOnePasswordItem(ctx context.Context, r
UID: resource.GetUID(),
}

return kubeSecrets.CreateKubernetesSecretFromItem(ctx, r.Client, secretName, resource.Namespace, item, autoRestart, labels, annotations, secretType, ownerRef, r.Config.AllowEmptyValues)
return kubeSecrets.CreateKubernetesSecretFromItem(ctx, r.Client, secretName, resource.Namespace, item, autoRestart, labels, annotations, secretType, ownerRef, r.Config.AllowEmptyValues, secretTemplate, imagePullSecret)
}

func (r *OnePasswordItemReconciler) updateStatus(ctx context.Context, resource *onepasswordv1.OnePasswordItem, err error) error {
Expand Down
Loading