From 5f3dd8a292b43b8c4199c2fcbabe882a8b376652 Mon Sep 17 00:00:00 2001 From: Thomas Matysik Date: Wed, 6 May 2020 15:07:11 +1200 Subject: [PATCH] Use Secrets for passwords and tokens Signed-off-by: Thomas Matysik --- README.md | 75 +++++++++++++++++++++++- templates/_helpers.tpl | 32 ++++++++++ templates/deployment.yaml | 9 +++ templates/gitea/_container.tpl | 11 ---- templates/gitea/gitea-config.yaml | 32 ++++++---- templates/init/_container.tpl | 41 +++++++++---- templates/secrets/externaldb-secret.yaml | 16 +++++ templates/secrets/gitea-secret.yaml | 25 ++++++++ values.yaml | 17 +++++- 9 files changed, 220 insertions(+), 38 deletions(-) create mode 100644 templates/secrets/externaldb-secret.yaml create mode 100644 templates/secrets/gitea-secret.yaml diff --git a/README.md b/README.md index c62b513..9577f1d 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,76 @@ When upgrading, make sure you have the following enabled: - Persistency for both mariadb + Gitea - Using `existingGiteaClaim` - - Due to using the [bitnami/mariadb](https://github.com/helm/charts/tree/master/stable/mariadb) chart, make sure to HARDCODE your passwords within `values.yaml`. Or else you'll be unable to update mariadb + - Due to using the [bitnami/mariadb](https://github.com/helm/charts/tree/master/stable/mariadb) chart, make sure to HARDCODE your passwords within `values.yaml`, + or (better) set them in a separate secret named in mariadb.existingSecret. Or else you'll be unable to update mariadb + +## Secrets + +Secret values (database passwords, Gitea internal secrets / tokens) are passed to the containers using Kubernetes secrets. + +These secrets can be automatically created using parameters from values.yaml or created externally and specified by name. + +### MariaDB + +If using the default MariaDB database, create the secret per the bitnami mariadb chart and specify its name in `mariadb.existingSecret`. + +The secret will be created automatically if unspecified or if the password is supplied via `values.yaml`. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: RELEASE-NAME-mariadb +type: Opaque +data: + mariadb-root-password: "" + mariadb-password: "" +``` + +### ExternalDB + +If using a different database, specify the secret name in `externalDB.secretName`. + +If this secret is shared with the database itself and has the password in a key other than `db-password`, you can specify the key name via `externalDB.passwordKey`. + +The secret will be created automatically if the password is supplied via `values.yaml`. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: RELEASE-NAME-externaldb +type: Opaque +data: + db-password: "" +``` + +### Gitea Secrets + +Gitea requires a number of internal secret tokens, which can be supplied via an externally-created secret or via `values.yaml`. + +If they are not supplied, they will be auto-generated by the init container, and will change on upgrades. + +Gitea requires particular encoding for some of these so they should be generated using `gitea generate secret`. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: RELEASE-NAME +type: Opaque +data: + secret-key: "base64-encoded secret" + jwt-secret: "base64-encoded secret" + lfs-jwt-secret: "base64-encoded secret" + internal-token: "base64-encoded secret" +``` + +## Immutable Configuration + +If `config.immutableConfig` is `true`, the Gitea `app.ini` is regenerated each time the init container runs and is set as read-only. + +If it is `false`, then `app.ini` is generated only on first install and is editable by Gitea. ## Configuration @@ -171,11 +240,15 @@ The following table lists the configurable parameters of this chart and their de | `mariadb.persistence.enabled` | Enable or diable persistence | `true` | | `mariadb.persistence.accessMode` | What access mode to use | `ReadWriteOnce` | | `mariadb.persistence.size` | What size of database to use | `8Gi` | +| `externalDB.secretName` | Name of existing secret containing externalDB password | ` unset` | +| `externalDB.passwordKey` | Name of password entry in Secret | `db-password` | | `externalDB.dbUser` | external db user | ` unset` | | `externalDB.dbPassword` | external db password | ` unset` | | `externalDB.dbHost` | external db host | ` unset` | | `externalDB.dbPort` | external db port | ` unset` | | `externalDB.dbDatabase` | external db database name | ` unset` | +| `config.immutableConfig` | Set config as read-only and regenerate on every upgrade. | `false` | +| `config.secretName` | Name of existing secret containing Gitea internal tokens | ` unset` | | `config.disableInstaller` | Disable the installer | `false` | | `config.offlineMode` | Sets Gitea's Offline Mode. Values are `true` or `false`. | `false` | | `config.requireSignin` | Require Gitea user to be signed in to see any pages. Values are `true` or `false`. | `false` | diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index f662ef5..ce52b53 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -29,3 +29,35 @@ Return the appropriate apiVersion for ingress. {{- print "networking.k8s.io/v1beta1" -}} {{- end -}} {{- end -}} + +{{- define "gitea-secret-name" -}} +{{- if .Values.config.secretName -}} + {{ .Values.config.secretName }} +{{- else -}} + {{ template "fullname" . }} +{{- end -}} +{{- end -}} + +{{- define "db-secret-name" -}} +{{- if .Values.mariadb.enabled -}} + {{- if .Values.mariadb.existingSecret -}} + {{ .Values.mariadb.existingSecret }} + {{- else -}} + {{ template "mariadb.fullname" . }} + {{- end -}} +{{- else -}} + {{- if .Values.externalDB.secretName -}} + {{ .Values.externalDB.secretName }} + {{- else -}} + {{ printf "%s-externalDB" (include "fullname" .) }} + {{- end -}} +{{- end -}} +{{- end -}} + +{{- define "db-secret-key" -}} +{{- if .Values.mariadb.enabled -}} + {{- print "mariadb-password" -}} +{{- else -}} + {{ .Values.externalDB.passwordKey }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/templates/deployment.yaml b/templates/deployment.yaml index 4ff836e..b0557df 100644 --- a/templates/deployment.yaml +++ b/templates/deployment.yaml @@ -38,6 +38,15 @@ spec: - name: gitea-config configMap: name: {{ template "fullname" . }} + - name: database-secret + secret: + secretName: {{ template "db-secret-name" . }} + items: + - key: {{ template "db-secret-key" . }} + path: db-password + - name: gitea-secret + secret: + secretName: {{ template "gitea-secret-name" . }} {{- if .Values.imagePullSecrets }} imagePullSecrets: diff --git a/templates/gitea/_container.tpl b/templates/gitea/_container.tpl index 033e668..a907b96 100644 --- a/templates/gitea/_container.tpl +++ b/templates/gitea/_container.tpl @@ -5,17 +5,6 @@ Create helm partial for gitea server - name: gitea image: {{ .Values.images.gitea }} imagePullPolicy: {{ .Values.images.pullPolicy }} - env: - - name: DATABASE_PASSWORD - valueFrom: - secretKeyRef: - {{- if .Values.mariadb.enabled }} - name: {{ template "mariadb.fullname" . }} - key: mariadb-password - {{- else }} - name: {{ printf "%s-%s" .Release.Name "externaldb" }} - key: db-password - {{- end }} ports: - name: ssh containerPort: 22 diff --git a/templates/gitea/gitea-config.yaml b/templates/gitea/gitea-config.yaml index 61a21b9..5e27e58 100644 --- a/templates/gitea/gitea-config.yaml +++ b/templates/gitea/gitea-config.yaml @@ -248,7 +248,7 @@ data: ; Where your lfs files reside, default is data/lfs. LFS_CONTENT_PATH = data/lfs ; LFS authentication secret, change this yourself - LFS_JWT_SECRET = + LFS_JWT_SECRET = HELM_LFS_JWT_SECRET ; LFS authentication validity period (in time.Duration), pushes taking longer than this may fail. LFS_HTTP_AUTH_EXPIRY = 20m @@ -267,20 +267,15 @@ data: HOST = {{ .Values.externalDB.dbHost }}:{{ .Values.externalDB.dbPort }} NAME = {{ .Values.externalDB.dbDatabase }} USER = {{ .Values.externalDB.dbUser }} - PASSWD = {{ .Values.externalDB.dbPassword }} {{ else if .Values.mariadb.enabled }} ; Either "mysql", "postgres", "mssql" or "sqlite3", it's your choice DB_TYPE = mysql HOST = {{ template "mariadb.fullname" . }}:3306 NAME = {{ .Values.mariadb.db.name }} USER = {{ .Values.mariadb.db.user }} + {{ end }} ; Use PASSWD = `your password` for quoting if you use special characters in the password. - {{ if .Values.mariadb.password }} - PASSWD = {{ .Values.mariadb.db.password }} - {{ else }} - PASSWD = MARIADB_PASSWORD - {{ end }} - {{ end }} + PASSWD = HELM_DB_PASSWORD ; For "postgres" only, either "disable", "require" or "verify-full" SSL_MODE = disable ; For "sqlite3" and "tidb", use an absolute path when you start gitea as service @@ -308,11 +303,8 @@ data: ; Whether the installer is disabled INSTALL_LOCK = {{ .Values.config.disableInstaller }} ; !!CHANGE THIS TO KEEP YOUR USER DATA SAFE!! - {{ if .Values.config.secretKey }} - SECRET_KEY = {{ .Values.config.secretKey }} - {{ else }} - SECRET_KEY = {{ randAlphaNum 64 | quote }} - {{ end }} + SECRET_KEY = HELM_SECRET_KEY + INTERNAL_TOKEN_URI = file:/data/gitea/conf/internal-token ; How long to remember that an user is logged in before requiring relogin (in days) @@ -662,6 +654,20 @@ data: ; Max number of items in a page MAX_RESPONSE_ITEMS = 50 + [oauth2] + ; Enables OAuth2 provider + ENABLE = true + ; Lifetime of an OAuth2 access token in seconds + ACCESS_TOKEN_EXPIRATION_TIME=3600 + ; Lifetime of an OAuth2 access token in hours + REFRESH_TOKEN_EXPIRATION_TIME=730 + ; Check if refresh token got already used + INVALIDATE_REFRESH_TOKENS=false + ; OAuth2 authentication secret for access and refresh tokens, change this to a unique string. + JWT_SECRET=HELM_JWT_SECRET + ; Maximum length of oauth2 token/cookie stored on server + MAX_TOKEN_LENGTH=32767 + [i18n] LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,Українська,日本語,español,português do Brasil,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어 diff --git a/templates/init/_container.tpl b/templates/init/_container.tpl index 6a02e84..55eda65 100644 --- a/templates/init/_container.tpl +++ b/templates/init/_container.tpl @@ -6,26 +6,43 @@ Create helm partial for gitea server image: {{ .Values.images.gitea }} imagePullPolicy: {{ .Values.images.pullPolicy }} env: - - name: MARIADB_PASSWORD - valueFrom: - secretKeyRef: - {{- if .Values.mariadb.enabled }} - name: {{ template "mariadb.fullname" . }} - key: mariadb-password - {{- else }} - name: {{ printf "%s-%s" .Release.Name "externaldb" }} - key: db-password - {{- end }} - name: SCRIPT value: &script |- mkdir -p /datatmp/gitea/conf - if [ ! -f /datatmp/gitea/conf/app.ini ]; then - sed "s/MARIADB_PASSWORD/${MARIADB_PASSWORD}/g" < /etc/gitea/app.ini > /datatmp/gitea/conf/app.ini + + if [ -f /etc/gitea-secret/internal-token ]; then + cp /etc/gitea-secret/internal-token /datatmp/gitea/conf/internal-token fi + if [ ! -f /datatmp/gitea/conf/internal-token ]; then + gitea generate secret INTERNAL_TOKEN >/datatmp/gitea/conf/internal-token + fi + + {{- if not .Values.config.immutableConfig }} + if [ -f /datatmp/gitea/conf/app.ini ]; then + chmod u+w /datatmp/gitea/conf/app.ini + exit + fi + {{- end }} + + sed "s/HELM_DB_PASSWORD/$(cat /etc/database-secret/db-password)/g" < /etc/gitea/app.ini > /datatmp/gitea/conf/app.ini + sed -i "s/HELM_SECRET_KEY/$([ -f /etc/gitea-secret/secret-key ] && cat /etc/gitea-secret/secret-key || gitea generate secret SECRET_KEY)/g" /datatmp/gitea/conf/app.ini + sed -i "s/HELM_JWT_SECRET/$([ -f /etc/gitea-secret/jwt-secret ] && cat /etc/gitea-secret/jwt-secret || gitea generate secret JWT_SECRET)/g" /datatmp/gitea/conf/app.ini + sed -i "s/HELM_LFS_JWT_SECRET/$([ -f /etc/gitea-secret/lfs-jwt-secret ] && cat /etc/gitea-secret/lfs-jwt-secret || gitea generate secret LFS_JWT_SECRET)/g" /datatmp/gitea/conf/app.ini + + {{- if .Values.config.immutableConfig }} + chmod a-w /datatmp/gitea/conf/app.ini + {{- end }} command: ["/bin/sh",'-c', *script] volumeMounts: - name: gitea-data mountPath: /datatmp - name: gitea-config mountPath: /etc/gitea + readOnly: true + - name: database-secret + mountPath: /etc/database-secret + readOnly: true + - name: gitea-secret + mountPath: /etc/gitea-secret + readOnly: true {{- end }} diff --git a/templates/secrets/externaldb-secret.yaml b/templates/secrets/externaldb-secret.yaml new file mode 100644 index 0000000..60bb025 --- /dev/null +++ b/templates/secrets/externaldb-secret.yaml @@ -0,0 +1,16 @@ +{{- if .Values.externalDB -}} +{{- if (not .Values.externalDB.secretName) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "db-secret-name" . }} + labels: + app: {{ template "fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: {{ .Release.Name | quote }} + heritage: {{ .Release.Service | quote }} +type: Opaque +data: + {{ .Values.externalDB.passwordKey }}: {{ .Values.externalDB.dbPassword | b64enc }} +{{- end -}} +{{- end }} \ No newline at end of file diff --git a/templates/secrets/gitea-secret.yaml b/templates/secrets/gitea-secret.yaml new file mode 100644 index 0000000..895003f --- /dev/null +++ b/templates/secrets/gitea-secret.yaml @@ -0,0 +1,25 @@ +{{- if (not .Values.config.secretName) -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "gitea-secret-name" . }} + labels: + app: {{ template "fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: {{ .Release.Name | quote }} + heritage: {{ .Release.Service | quote }} +type: Opaque +data: + {{ if .Values.config.secretKey }} + secret-key: {{ .Values.config.secretKey | b64enc }} + {{ end }} + {{ if .Values.config.jwtSecret }} + jwt-secret: {{ .Values.config.jwtSecret | b64enc }} + {{ end }} + {{ if .Values.config.lfsJwtSecret }} + lfs-jwt-secret: {{ .Values.config.lfsJwtSecret | b64enc }} + {{ end }} + {{ if .Values.config.internalToken }} + internal-token: {{ .Values.config.internalToken | b64enc }} + {{ end }} +{{- end }} \ No newline at end of file diff --git a/values.yaml b/values.yaml index 2fdb2d4..98f3a86 100644 --- a/values.yaml +++ b/values.yaml @@ -187,6 +187,8 @@ mariadb: ## Connect to an external database instead # externalDB: +# #secretName: "" # Existing secret that contains the password; auto-created otherwise. +# passwordKey: "db-password" # Identifier in the Secret's dictionary. # dbUser: "postgres" # dbPassword: "" # dbHost: "service-name.namespace.svc.cluster.local" # or some external host @@ -197,13 +199,26 @@ mariadb: ## Actual Gitea configuration (modified the default .ini file for Gitea) ## This will skip the initial installation screen. You must have a secretKey already defined ## and disableInstaller set to True +## If secretName is set, it should point to an existing secret with the following keys: +## - internal-token +## - secret-key +## - jwt-secret +## - lfs-jwt-secret +## If secretName is unset, secret is created from values specified below. +## Secrets should be generated with 'gitea generate secret' +## Unspecified secrets are auto-generated by the init container config: -## secretKey: ThisIsMySuperSecretKeyThatsUsedInterally +## secretName: "" +## secretKey: "" # Generate with 'gitea generate secret SECRET_KEY' +## jwtSecret: "" # Generate with 'gitea generate secret JWT_SECRET' +## lfsJwtSecret: "" # Generate with 'gitea generate secret LFS_JWT_SECRET' +## internalToken: "" # Generate with 'gitea generate secret INTERNAL_TOKEN' disableInstaller: false offlineMode: false requireSignin: false disableRegistration: false openidSignin: true + immutableConfig: false # Regenerate config every time, and set as read-only ## Common helm annotations ## Node labels and tolerations for pod assignment