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
87 changes: 87 additions & 0 deletions stable/yopass/README.md.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,90 @@ memcached:
{{ template "chart.requirementsSection" . }}

{{ template "chart.valuesSection" . }}

## Read-Only Mode & Split Deployments

Read-only mode disables all secret creation endpoints while keeping retrieval fully functional. This chart can deploy an optional second instance in read-only mode within the same release.

### Enable read-only instance

```yaml
readOnly:
enabled: true
ingress:
enabled: true
hosts:
- host: yopass.example.com
paths:
- path: /
pathType: Prefix
```

Resources created for the read-only instance are suffixed with `-readonly` (Deployment, Service, Ingress). Both instances share the same database configured via `.Values.database.*`.

In read-only mode the server is started with `--read-only`. Endpoint behavior:

| Endpoint | Behavior |
|----------|----------|
| `POST /create/secret` | 404 Not Found |
| `POST /create/file` | 404 Not Found |
| `GET /secret/{key}` | Active |
| `GET /file/{key}` | Active |
| `DELETE /secret/{key}` | Active |

The frontend detects `READ_ONLY: true` from the `/config` endpoint and shows a read-only landing page instead of the create form.

### Split deployment pattern (one release)

Deploy one Helm release with both instances sharing one database:

```yaml
database:
type: memcached
dsn: memcached:11211

memcached:
enabled: true

ingress:
enabled: true
hosts:
- host: internal.example.com
paths:
- path: /
pathType: Prefix

readOnly:
enabled: true
ingress:
enabled: true
hosts:
- host: yopass.example.com
paths:
- path: /
pathType: Prefix
```

Both instances connect to the same Memcached or Redis database. Secrets created on the internal instance can be retrieved via the public read-only instance.

### OIDC on internal, public read-only

Require authentication only on the internal instance while the public instance remains open for retrieval:

```yaml
extraEnvVariables:
REQUIRE_AUTH: "true"
OIDC_ISSUER: https://accounts.google.com
OIDC_CLIENT_ID: "123456789-abc.apps.googleusercontent.com"
OIDC_CLIENT_SECRET: "GOCSPX-..."
OIDC_REDIRECT_URL: https://internal.example.com/auth/callback
OIDC_ALLOWED_DOMAIN: example.com

readOnly:
enabled: true
# no auth variables here
```

Notes:
- Deletion of one-time secrets happens automatically via `DELETE /secret/{key}` when a recipient opens a secret; this endpoint remains active in read-only mode intentionally.
- If you use a CDN or caching layer in front of the public instance, ensure retrieval endpoints (`GET /secret/*`, `GET /file/*`) are not cached.
25 changes: 25 additions & 0 deletions stable/yopass/ci/readonly-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
readOnly:
enabled: true
ingress:
enabled: true
hosts:
- host: yopass-public.example.com
paths:
- path: /
pathType: Prefix

memcached:
enabled: true

ingress:
enabled: true
hosts:
- host: yopass-internal.example.com
paths:
- path: /
pathType: Prefix

resources: {}

...
142 changes: 142 additions & 0 deletions stable/yopass/templates/deployment-readonly.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
{{- if .Values.readOnly.enabled }}
apiVersion: apps/v1
kind: Deployment

metadata:
name: {{ include "yopass.fullname" . }}-readonly
namespace: {{ .Release.Namespace }}
labels:
{{- include "yopass.labels" . | nindent 4 }}

spec:
replicas: {{ default .Values.replicaCount .Values.readOnly.replicaCount }}

selector:
matchLabels:
{{- include "yopass.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: readonly

template:
metadata:
labels:
{{- include "yopass.labels" . | nindent 8 }}
app.kubernetes.io/component: readonly
{{- if .Values.annotations }}
annotations:
{{- toYaml .Values.annotations | nindent 8 }}
{{- end }}

spec:
automountServiceAccountToken: {{ .Values.serviceAccount.automountToken }}
serviceAccountName: {{ include "yopass.serviceAccountName" . }}
{{- if .Values.image.pullSecrets }}

imagePullSecrets:
{{- range .Values.image.pullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}
{{- with .Values.securityContext }}

securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.extraInitContainers }}

initContainers:
{{- toYaml .Values.extraInitContainers | nindent 8 }}
{{- end }}

containers:
- name: {{ .Chart.Name }}
image: {{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}
imagePullPolicy: {{ .Values.image.pullPolicy }}

args:
- --address=0.0.0.0
- --port={{ default .Values.service.internalPort .Values.readOnly.service.internalPort }}
{{- if .Values.metrics.enabled }}
- --metrics-port={{ .Values.metrics.internalPort }}
{{- end }}
- --database={{ .Values.database.type }}
{{- if eq .Values.database.type "redis" }}
- --redis={{ .Values.database.dsn }}
{{- end }}
{{- if eq .Values.database.type "memcached" }}
- --memcached={{ .Values.database.dsn }}
{{- end }}
- --max-length={{ .Values.config.maxLength }}
- --read-only
{{- with .Values.podSecurityContext }}

securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if or .Values.readOnly.extraEnvSecrets .Values.readOnly.extraEnvVariables }}

env:
{{- range $key, $value := .Values.readOnly.extraEnvSecrets }}
- name: {{ $key }}
valueFrom:
secretKeyRef:
name: {{ required "Must specify secret!" $value.secret }}
key: {{ required "Must specify key!" $value.key }}
{{- end }}
{{- range $key, $value := .Values.readOnly.extraEnvVariables }}
- name: {{ $key }}
value: "{{ $value }}"
{{- end }}
{{- end }}
{{- if or .Values.readOnly.envFromSecrets .Values.readOnly.envFromConfigMaps }}

envFrom:
{{- range $name := .Values.readOnly.envFromSecrets }}
- secretRef:
name: {{ $name }}
{{- end }}
{{- range $name := .Values.readOnly.envFromConfigMaps }}
- configMapRef:
name: {{ $name }}
{{- end }}
{{- end }}

ports:
- name: http
containerPort: {{ default .Values.service.internalPort .Values.readOnly.service.internalPort }}
protocol: TCP
{{- if .Values.metrics.enabled }}
- name: metrics
containerPort: {{ .Values.metrics.internalPort }}
protocol: TCP
{{- end }}

livenessProbe:
httpGet:
path: /
port: http

readinessProbe:
httpGet:
path: /
port: http
{{- if (default .Values.resources .Values.readOnly.resources) }}

resources:
{{- toYaml (default .Values.resources .Values.readOnly.resources) | nindent 12 }}
{{- end }}
{{- if (default .Values.nodeSelector .Values.readOnly.nodeSelector) }}

nodeSelector:
{{- toYaml (default .Values.nodeSelector .Values.readOnly.nodeSelector) | nindent 8 }}
{{- end }}
{{- with (default .Values.affinity .Values.readOnly.affinity) }}

affinity:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- with (default .Values.tolerations .Values.readOnly.tolerations) }}

tolerations:
{{ toYaml . | nindent 8 }}
{{- end }}
{{- end }}
2 changes: 2 additions & 0 deletions stable/yopass/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ spec:
selector:
matchLabels:
{{- include "yopass.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: internal

template:
metadata:
labels:
{{- include "yopass.labels" . | nindent 8 }}
app.kubernetes.io/component: internal
{{- if .Values.annotations }}
annotations:
{{- toYaml .Values.annotations | nindent 8 }}
Expand Down
50 changes: 50 additions & 0 deletions stable/yopass/templates/ingress-readonly.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{{- if and .Values.readOnly.enabled .Values.readOnly.ingress.enabled -}}
{{- $serviceName := print (include "yopass.fullname" .) "-readonly" }}
apiVersion: networking.k8s.io/v1
kind: Ingress

metadata:
name: {{ include "yopass.fullname" . }}-readonly
namespace: {{ .Release.Namespace }}
labels:
{{- include "yopass.labels" . | nindent 4 }}
{{- if .Values.readOnly.ingress.labels }}
{{- toYaml .Values.readOnly.ingress.labels | nindent 4 }}
{{- end }}
{{- if .Values.readOnly.ingress.annotations }}
annotations:
{{- toYaml .Values.readOnly.ingress.annotations | nindent 4 }}
{{- end }}

spec:
{{- if .Values.readOnly.ingress.className }}
ingressClassName: {{ .Values.readOnly.ingress.className }}
{{- end }}
{{- if .Values.readOnly.ingress.tls }}
tls:
{{- range .Values.readOnly.ingress.tls }}
- secretName: {{ .secretName }}
{{- if .hosts }}
hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
rules:
{{- range .Values.readOnly.ingress.hosts }}
- host: {{ .host }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ $serviceName }}
port:
name: http
{{- end }}
{{- end }}
{{- end }}
36 changes: 36 additions & 0 deletions stable/yopass/templates/service-readonly.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{{- if and .Values.readOnly.enabled .Values.readOnly.service.enabled }}
apiVersion: v1
kind: Service

metadata:
name: {{ template "yopass.fullname" . }}-readonly
namespace: {{ .Release.Namespace }}
labels:
{{- include "yopass.labels" . | nindent 4 }}
{{- if .Values.readOnly.service.labels }}
{{- toYaml .Values.readOnly.service.labels | nindent 4 }}
{{- end }}
{{- if .Values.readOnly.service.annotations }}
annotations:
{{- toYaml .Values.readOnly.service.annotations | nindent 4 }}
{{- end }}

spec:
type: {{ default .Values.service.type .Values.readOnly.service.type }}

ports:
- name: http
port: {{ default .Values.service.port .Values.readOnly.service.port }}
targetPort: http
protocol: TCP
{{- if .Values.metrics.enabled }}
- name: metrics
port: {{ .Values.metrics.port }}
targetPort: metrics
protocol: TCP
{{- end }}

selector:
{{- include "yopass.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: readonly
{{- end }}
1 change: 1 addition & 0 deletions stable/yopass/templates/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ spec:

selector:
{{- include "yopass.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: internal
Loading
Loading