Calibre-Web-Automated chart: naming scheme, security hardening (conventions batch 3)
All audit findings for the calibre-web-automated chart (#32) in one major bump, 3.1.1+upv4.0.6 -> 4.0.0+upv4.0.6: - Rename StatefulSet, Service, OnePasswordItem secret and Ingress from the inverted <chart>-<release>-cwa-<kind> scheme to <release>-<chart>-<kind>; spec.serviceName and all references follow. Both PVCs deliberately keep their historical names (documented naming exception; renaming would orphan the data). - TLS secret renamed to the standard tls-<release>-<chart>-ingress. - Ingress on the repo standard: ingress.className (default nginx), ingress.annotations merged with the chart-managed annotations, ingress.clusterIssuer now required (was silently defaulted to "letsencrypt-issuer"). - Security hardening, values-configurable via securityContext.pod / securityContext.container: allowPrivilegeEscalation false, all capabilities dropped except the minimal set the s6-overlay init needs (CHOWN, DAC_OVERRIDE, FOWNER, KILL, SETGID, SETUID). Documented deviations: the image must start as root (s6 drops to PUID 1000) and readOnlyRootFilesystem stays false (read-only mode disables PUID/PGID remapping upstream and breaks the ImageMagick policy setup). - Additive env value rendered through tpl (value / secretKeyRef / configMapKeyRef forms). - CI fixtures: secret renamed to the new scheme, clusterIssuer added to test-values. Part of #32.
This commit is contained in:
10 files changed
+100
-24
No files matched your search
@@ -5,7 +5,7 @@ name: calibre-web-automated
|
|||||||
description: A Helm chart to deploy calibere-web-automated
|
description: A Helm chart to deploy calibere-web-automated
|
||||||
icon: https://raw.githubusercontent.com/crocodilestick/Calibre-Web-Automated/main/README_images/cwa-logo-round-dark.png
|
icon: https://raw.githubusercontent.com/crocodilestick/Calibre-Web-Automated/main/README_images/cwa-logo-round-dark.png
|
||||||
|
|
||||||
version: 3.1.1+upv4.0.6
|
version: 4.0.0+upv4.0.6
|
||||||
appVersion: v4.0.6
|
appVersion: v4.0.6
|
||||||
|
|
||||||
maintainers:
|
maintainers:
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
metadata:
|
metadata:
|
||||||
|
# Deliberate naming exception (see README): this PVC keeps its historical
|
||||||
|
# <chart>-<release>-cwa-consume-pvc name — renaming a PVC would provision
|
||||||
|
# a new, empty volume and orphan the existing data.
|
||||||
name: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-consume-pvc
|
name: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-consume-pvc
|
||||||
spec:
|
spec:
|
||||||
accessModes:
|
accessModes:
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-ingress
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||||
annotations:
|
annotations:
|
||||||
cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided" .Values.ingress.clusterIssuer}}
|
cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided" .Values.ingress.clusterIssuer }}
|
||||||
nginx.ingress.kubernetes.io/proxy-body-size: {{ .Values.cwa.maxUploadFileSize }}
|
nginx.ingress.kubernetes.io/proxy-body-size: {{ .Values.cwa.maxUploadFileSize }}
|
||||||
nginx.org/client-max-body-size: {{ .Values.cwa.maxUploadFileSize }}
|
nginx.org/client-max-body-size: {{ .Values.cwa.maxUploadFileSize }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
spec:
|
spec:
|
||||||
ingressClassName: nginx
|
ingressClassName: {{ .Values.ingress.className }}
|
||||||
rules:
|
rules:
|
||||||
- host: {{ required "A Domain/Host must be provided" .Values.ingress.domain }}
|
- host: {{ required "A Domain/Host must be provided" .Values.ingress.domain }}
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- backend:
|
- backend:
|
||||||
service:
|
service:
|
||||||
name: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-service
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||||
port:
|
port:
|
||||||
name: http
|
name: http
|
||||||
path: /
|
path: /
|
||||||
@@ -22,4 +25,4 @@ spec:
|
|||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
- {{ required "A Domain/Host must be provided" .Values.ingress.domain }}
|
- {{ required "A Domain/Host must be provided" .Values.ingress.domain }}
|
||||||
secretName: tls-{{ .Chart.Name }}-{{ .Release.Name }}-ingress
|
secretName: tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
metadata:
|
metadata:
|
||||||
|
# Deliberate naming exception (see README): this PVC keeps its historical
|
||||||
|
# <chart>-<release>-cwa-pvc name — renaming a PVC would provision a new,
|
||||||
|
# empty volume and orphan the existing data.
|
||||||
name: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-pvc
|
name: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-pvc
|
||||||
spec:
|
spec:
|
||||||
accessModes:
|
accessModes:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
apiVersion: onepassword.com/v1
|
apiVersion: onepassword.com/v1
|
||||||
kind: OnePasswordItem
|
kind: OnePasswordItem
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-secret
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-secret
|
||||||
spec:
|
spec:
|
||||||
itemPath: {{ .Values.secrets.cwaSecretRef }}
|
itemPath: {{ .Values.secrets.cwaSecretRef }}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-service
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
app: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-sfs
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||||
ports:
|
ports:
|
||||||
- protocol: TCP
|
- protocol: TCP
|
||||||
port: 80
|
port: 80
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: StatefulSet
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-sfs
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||||
labels:
|
labels:
|
||||||
app: {{ .Chart.Name }}-{{ .Release.Name }}
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||||
spec:
|
spec:
|
||||||
serviceName: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-service
|
serviceName: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||||
replicas: {{ include "calibre-web-automated.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.cwa.replicas) }}
|
replicas: {{ include "calibre-web-automated.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.cwa.replicas) }}
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-sfs
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-sfs
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||||
labels:
|
labels:
|
||||||
app: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-sfs
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||||
spec:
|
spec:
|
||||||
|
automountServiceAccountToken: false
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml .Values.cwa.securityContext.pod | nindent 8 }}
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-container
|
- name: {{ .Release.Name }}-{{ .Chart.Name }}-container
|
||||||
image: "ghcr.io/crocodilestick/calibre-web-automated:{{ .Chart.AppVersion }}"
|
image: "ghcr.io/crocodilestick/calibre-web-automated:{{ .Chart.AppVersion }}"
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
ports:
|
ports:
|
||||||
@@ -34,12 +37,32 @@ spec:
|
|||||||
- name: HARDCOVER_TOKEN
|
- name: HARDCOVER_TOKEN
|
||||||
valueFrom:
|
valueFrom:
|
||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-secret
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-secret
|
||||||
key: hardcover-api-token
|
key: hardcover-api-token
|
||||||
- name: CALIBRE_CONFIG_DIR
|
- name: CALIBRE_CONFIG_DIR
|
||||||
value: "/config/.config/calibre"
|
value: "/config/.config/calibre"
|
||||||
- name: NETWORK_SHARE_MODE
|
- name: NETWORK_SHARE_MODE
|
||||||
value: "false"
|
value: "false"
|
||||||
|
{{- range $value := .Values.cwa.env }}
|
||||||
|
{{- if $value.value }}
|
||||||
|
- name: {{ $value.name | quote }}
|
||||||
|
value: {{ tpl $value.value $ | quote }}
|
||||||
|
{{- else if $value.valueFrom }}
|
||||||
|
{{- if $value.valueFrom.secretKeyRef }}
|
||||||
|
- name: {{ $value.name }}
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ tpl $value.valueFrom.secretKeyRef.name $ | quote }}
|
||||||
|
key: {{ tpl $value.valueFrom.secretKeyRef.key $ | quote }}
|
||||||
|
{{- else if $value.valueFrom.configMapKeyRef }}
|
||||||
|
- name: {{ $value.name }}
|
||||||
|
valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: {{ tpl $value.valueFrom.configMapKeyRef.name $ | quote }}
|
||||||
|
key: {{ tpl $value.valueFrom.configMapKeyRef.key $ | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- mountPath: "/config"
|
- mountPath: "/config"
|
||||||
name: cwa
|
name: cwa
|
||||||
@@ -63,12 +86,13 @@ spec:
|
|||||||
{{- toYaml .Values.cwa.startupProbe | nindent 12 }}
|
{{- toYaml .Values.cwa.startupProbe | nindent 12 }}
|
||||||
resources:
|
resources:
|
||||||
{{- include "calibre-web-automated.resources" .Values.cwa.resources | nindent 12 }}
|
{{- include "calibre-web-automated.resources" .Values.cwa.resources | nindent 12 }}
|
||||||
automountServiceAccountToken: false
|
|
||||||
securityContext:
|
securityContext:
|
||||||
fsGroup: 1000
|
{{- toYaml .Values.cwa.securityContext.container | nindent 12 }}
|
||||||
fsGroupChangePolicy: Always
|
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
volumes:
|
volumes:
|
||||||
|
# Deliberate naming exception (see README): both PVCs keep their
|
||||||
|
# historical <chart>-<release>-cwa-* names — renaming a PVC would
|
||||||
|
# provision a new, empty volume and orphan the existing data.
|
||||||
- name: cwa
|
- name: cwa
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-pvc
|
claimName: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-pvc
|
||||||
@@ -77,7 +101,7 @@ spec:
|
|||||||
claimName: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-consume-pvc
|
claimName: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-consume-pvc
|
||||||
- name: cwa-plugins-config
|
- name: cwa-plugins-config
|
||||||
secret:
|
secret:
|
||||||
secretName: {{ .Chart.Name }}-{{ .Release.Name }}-cwa-secret
|
secretName: {{ .Release.Name }}-{{ .Chart.Name }}-secret
|
||||||
items:
|
items:
|
||||||
- key: calibre-plugins
|
- key: calibre-plugins
|
||||||
path: customize.py.json
|
path: customize.py.json
|
||||||
|
|||||||
@@ -46,7 +46,47 @@ cwa:
|
|||||||
cpu: 0.1
|
cpu: 0.1
|
||||||
memory: 512Mi
|
memory: 512Mi
|
||||||
ephemeralStorage: 100Mi
|
ephemeralStorage: 100Mi
|
||||||
|
# Documented deviation from the repo-wide hardening standard: the CWA image
|
||||||
|
# (linuxserver.io-style) must start as root — it boots via the s6-overlay
|
||||||
|
# init system, whose stage-1/2 setup runs as uid 0 and then drops the app
|
||||||
|
# to the PUID/PGID user (1000), so runAsNonRoot/runAsUser are deliberately
|
||||||
|
# not set (s6 supervisors stay root, the application runs as uid 1000).
|
||||||
|
# readOnlyRootFilesystem is deliberately false: in read-only mode the image
|
||||||
|
# disables the PUID/PGID remapping (the runtime uid would silently change
|
||||||
|
# from 1000 to the built-in 911 and break existing volume data) and its
|
||||||
|
# init fails to write the ImageMagick policy under /etc. Everything else
|
||||||
|
# is hardened: no privilege escalation and all capabilities dropped except
|
||||||
|
# the minimal set the s6 init/supervision needs (verified against v4.0.6):
|
||||||
|
# CHOWN/DAC_OVERRIDE/FOWNER/SETGID/SETUID for the ownership fix-up of
|
||||||
|
# /config etc. and the root->PUID drop, KILL so the root supervisor can
|
||||||
|
# signal the uid-1000 services (without it the pod cannot shut down
|
||||||
|
# cleanly and gets SIGKILLed).
|
||||||
|
securityContext:
|
||||||
|
pod:
|
||||||
|
fsGroup: 1000
|
||||||
|
fsGroupChangePolicy: Always
|
||||||
|
container:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
add:
|
||||||
|
- CHOWN
|
||||||
|
- DAC_OVERRIDE
|
||||||
|
- FOWNER
|
||||||
|
- KILL
|
||||||
|
- SETGID
|
||||||
|
- SETUID
|
||||||
|
# Extra environment variables, appended to the ones the chart always sets.
|
||||||
|
# Rendered through tpl; value, secretKeyRef and configMapKeyRef forms are
|
||||||
|
# supported (see template-chart).
|
||||||
|
env: []
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
clusterIssuer: "letsencrypt-issuer"
|
clusterIssuer: null
|
||||||
domain: null
|
domain: null
|
||||||
|
className: nginx
|
||||||
|
# Extra annotations for the Ingress; merged with the cert-manager
|
||||||
|
# cluster-issuer and proxy-body-size annotations the chart always sets.
|
||||||
|
annotations: {}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
# Fixtures for the calibre-web-automated install-test: the cwa secret the
|
# Fixtures for the calibre-web-automated install-test: the cwa secret the
|
||||||
# 1Password operator would otherwise materialize and static hostPath PVs
|
# 1Password operator would otherwise materialize and static hostPath PVs
|
||||||
# (the consume PVC is ReadWriteMany, which local-path cannot provision).
|
# (the consume PVC is ReadWriteMany, which local-path cannot provision).
|
||||||
# This chart names resources <chart>-<release>-..., release name is ci.
|
# Release name is ci; resources are named <release>-<chart>-..., except the
|
||||||
|
# PVCs, which keep their historical <chart>-<release>-cwa-* names
|
||||||
|
# (documented naming exception, see README).
|
||||||
apiVersion: storage.k8s.io/v1
|
apiVersion: storage.k8s.io/v1
|
||||||
kind: StorageClass
|
kind: StorageClass
|
||||||
metadata:
|
metadata:
|
||||||
@@ -74,7 +76,7 @@ spec:
|
|||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
name: calibre-web-automated-ci-cwa-secret
|
name: ci-calibre-web-automated-secret
|
||||||
stringData:
|
stringData:
|
||||||
hardcover-api-token: ci-dummy-token
|
hardcover-api-token: ci-dummy-token
|
||||||
# Mounted as /config/.config/calibre/customize.py.json (calibre plugin
|
# Mounted as /config/.config/calibre/customize.py.json (calibre plugin
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ cwa:
|
|||||||
storage: 2Gi
|
storage: 2Gi
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
|
clusterIssuer: ci
|
||||||
domain: cwa.ci.local
|
domain: cwa.ci.local
|
||||||
Reference in new issue
Block a user