From 9393715d301aeff3aa698b0324f96b2a77a44d91 Mon Sep 17 00:00:00 2001 From: Sven Trieflinger Date: Wed, 18 Dec 2024 07:07:02 +0100 Subject: [PATCH] feat(chart): add user email verification (#35) Signed-off-by: Sven Trieflinger Signed-off-by: Sebastian Becker Co-authored-by: Sebastian Becker --- charts/thymus/scripts/create-user.sh | 88 ++++++++++++++++++++++++ charts/thymus/templates/mailslurper.yaml | 81 ++++++++++++++++++++++ charts/thymus/templates/users.yaml | 18 +++-- charts/thymus/values.yaml | 40 ++++++++--- 4 files changed, 213 insertions(+), 14 deletions(-) create mode 100644 charts/thymus/scripts/create-user.sh create mode 100644 charts/thymus/templates/mailslurper.yaml diff --git a/charts/thymus/scripts/create-user.sh b/charts/thymus/scripts/create-user.sh new file mode 100644 index 0000000..d930cfa --- /dev/null +++ b/charts/thymus/scripts/create-user.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +# +# Copyright (c) 2024 - for information on the respective copyright owner +# see the NOTICE file and/or the repository https://github.com/carbynestack/thymus. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# This script creates a user in Kratos and verifies the user's email address. +# +# The script expects the following environment variables to be set: +# - MAILSLURPER_ADDRESS: The address of the MailSlurper instance. +# - KRATOS_ADMIN_SERVICE_ADDRESS: The address of the Kratos admin service instance. +# - KRATOS_PUBLIC_SERVICE_ADDRESS: The address of the Kratos public service instance. +# - RETRY_PERIOD: The time to wait between retries when polling for the verification code. Default is 1 second. +# - RETRIES: The number of times to retry getting the verification code. Default is 10. + +RETRY_PERIOD=${RETRY_PERIOD:-1} +RETRIES=${RETRIES:-10} + +if [ -z "${MAILSLURPER_ADDRESS}" ]; then + echo "Error: MAILSLURPER_ADDRESS environment variable is not set." + exit 1 +fi + +if [ -z "${KRATOS_ADMIN_SERVICE_ADDRESS}" ]; then + echo "Error: KRATOS_ADMIN_SERVICE_ADDRESS environment variable is not set." + exit 1 +fi + +if [ -z "${KRATOS_PUBLIC_SERVICE_ADDRESS}" ]; then + echo "Error: KRATOS_PUBLIC_SERVICE_ADDRESS environment variable is not set." + exit 1 +fi + +# Checks if the last command failed and exits the script if it did. +exitOnError() { + STATUS_CODE=$? + if [ $STATUS_CODE -ne 0 ]; then + echo "$1 Exit Code: $STATUS_CODE" + exit 1 + fi +} + +# Gets the verification code from the email sent by Kratos. +getVerificationCode() { + email=$1 + for i in $(seq 1 "${RETRIES}"); do + code=$(curl -X GET -sf -H 'Content-Type: application/JSON' \ + "http://${MAILSLURPER_ADDRESS}/mail?to=${email}\&order=desc" | \ + jq -r '.mailItems[0].body | capture("code: (?\\w+)").code') + exitOnError "Failed to get verification code for user: $email." + if [[ -n "$code" ]]; then + echo "$code" + return + fi + sleep "$((i*RETRY_PERIOD))" + done + echo "Failed to get verification code for user: $email." + exit 1 +} + +# Extracts the email from the user credentials file. +email=$(jq -r '.traits.email' < /user-credentials/data.json) + +echo "Creating user: ${email}" +curl -X POST -vf -H 'Content-Type: application/json' -d @/user-credentials/data.json "http://${KRATOS_ADMIN_SERVICE_ADDRESS}/admin/identities" +exitOnError "Failed to create user: ${email}." + +echo "Verifying user: ${email}" +flowID=$(curl -X GET -sf -H 'Content-Type: application/JSON' \ + "http://${KRATOS_PUBLIC_SERVICE_ADDRESS}/self-service/verification/api" | \ + jq -r '.id') +exitOnError "Failed to create verification flow for user: ${email}." + +curl -X POST -sf -H 'Content-Type: application/JSON' \ + "http://${KRATOS_PUBLIC_SERVICE_ADDRESS}/self-service/verification?flow=${flowID}" \ + --data "{\"method\": \"code\", \"email\": \"${email}\"}" +exitOnError "Failed to initiate verification flow for user: ${email}." + +code=$(getVerificationCode "${email}") +curl -X POST -sf -H 'Content-Type: application/JSON' \ + "http://${KRATOS_PUBLIC_SERVICE_ADDRESS}/self-service/verification?flow=${flowID}" \ + --data "{\"method\": \"code\", \"code\": \"$code\"}" +exitOnError "Failed finalize verification for user: ${email}." + +echo "User ${email} successfully created" diff --git a/charts/thymus/templates/mailslurper.yaml b/charts/thymus/templates/mailslurper.yaml new file mode 100644 index 0000000..59900fe --- /dev/null +++ b/charts/thymus/templates/mailslurper.yaml @@ -0,0 +1,81 @@ +# +# Copyright (c) 2024 - for information on the respective copyright owner +# see the NOTICE file and/or the repository https://github.com/carbynestack/thymus. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Deployment for mailslurper +{{- if .Values.thymus.users.enabled }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "thymus.fullname" . }}-mailslurper + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "thymus.name" . }}-mailslurper + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.AppVersion }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + replicas: {{ .Values.thymus.policyCatalogue.replicaCount }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "thymus.name" . }}-mailslurper + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "thymus.name" . }}-mailslurper + app.kubernetes.io/instance: {{ .Release.Name }} + spec: + {{- if .Values.mailslurper.image.pullSecrets }} + imagePullSecrets: + {{- range .Values.mailslurper.image.pullSecrets }} + - name: {{ . }} + {{- end}} + {{- end}} + containers: + - name: "{{ .Chart.Name }}-mailslurper" + image: "{{ .Values.mailslurper.image.registry }}/{{ .Values.mailslurper.image.repository }}:{{ .Values.mailslurper.image.tag }}" + imagePullPolicy: {{ .Values.mailslurper.image.pullPolicy }} + ports: + - name: ui + containerPort: 4436 + protocol: TCP + - name: api + containerPort: 4437 + protocol: TCP + - name: smtp + containerPort: 1025 + protocol: TCP +--- +# Service for exposing mailslurper +apiVersion: v1 +kind: Service +metadata: + name: {{ include "thymus.fullname" . }}-mailslurper + namespace: {{ .Release.Namespace }} + {{- if .Values.mailslurper.service.annotations }} + annotations: +{{ .Values.mailslurper.service.annotations | toYaml | trim | indent 4 }} + {{- end}} +spec: + selector: + app.kubernetes.io/name: {{ include "thymus.name" . }}-mailslurper + app.kubernetes.io/instance: {{ .Release.Name }} + ports: + - name: ui + protocol: TCP + port: {{ .Values.mailslurper.service.uiPort }} + targetPort: ui + - name: api + protocol: TCP + port: {{ .Values.mailslurper.service.apiPort }} + targetPort: api + - name: smtp + protocol: TCP + port: {{ .Values.mailslurper.service.smtpPort }} + targetPort: smtp +{{- end }} diff --git a/charts/thymus/templates/users.yaml b/charts/thymus/templates/users.yaml index a90807d..abc83b6 100644 --- a/charts/thymus/templates/users.yaml +++ b/charts/thymus/templates/users.yaml @@ -7,8 +7,6 @@ # Create demo users {{- if .Values.thymus.users.enabled }} -{{- $command := printf "curl -X POST -sf -H 'Content-Type: application/json' -d @/user-credentials/data.json http://%s-kratos-admin:80/admin/identities" - (include "thymus.fullname" . ) -}} {{- range $index, $value := .Values.thymus.users.data }} --- apiVersion: v1 @@ -18,7 +16,7 @@ metadata: data: data.json: | { - "traits": { + "traits": { "email": "{{ $value.email }}" }, "credentials": { @@ -35,7 +33,7 @@ kind: Job metadata: name: "create-user-{{ $index }}" spec: - backoffLimit: 10 + backoffLimit: 5 template: spec: volumes: @@ -44,11 +42,19 @@ spec: name: "user-credentials-{{ $index }}" containers: - name: add-user - image: curlimages/curl:8.8.0 + image: pnnlmiscscripts/curl-jq + {{- $files := $.Files }} command: - "sh" - "-c" - - {{ $command | quote }} + - {{ $files.Get "scripts/create-user.sh" | quote | indent 14 }} + env: + - name: MAILSLURPER_ADDRESS + value: "{{ include "thymus.fullname" $ }}-mailslurper:{{ $.Values.mailslurper.service.apiPort }}" + - name: KRATOS_ADMIN_SERVICE_ADDRESS + value: {{ printf "%s-kratos-admin:%d" (include "thymus.fullname" $) (int $.Values.kratos.service.admin.port) }} + - name: KRATOS_PUBLIC_SERVICE_ADDRESS + value: {{ printf "%s-kratos-public:%d" (include "thymus.fullname" $) (int $.Values.kratos.service.public.port) }} volumeMounts: - name: user-credentials-volume mountPath: /user-credentials diff --git a/charts/thymus/values.yaml b/charts/thymus/values.yaml index fe555a1..3b39416 100644 --- a/charts/thymus/values.yaml +++ b/charts/thymus/values.yaml @@ -33,22 +33,22 @@ thymus: replicaCount: 1 service: port: 8080 - annotations: [] + annotations: [ ] image: registry: ghcr.io repository: carbynestack/thymus/policy-catalogue tag: latest pullPolicy: "IfNotPresent" - pullSecrets: [] + pullSecrets: [ ] probes: liveness: - period: 10 - initialDelay: 10 - failureThreshold: 3 + period: 10 + initialDelay: 10 + failureThreshold: 3 readiness: - period: 10 - initialDelay: 10 - failureThreshold: 3 + period: 10 + initialDelay: 10 + failureThreshold: 3 # Overrides for the Kratos subchart kratos: @@ -123,6 +123,13 @@ kratos: hooks: - hook: session + verification: + enabled: true + ui_url: http://172.18.1.128.sslip.io/iam/ui/verification + use: code + after: + default_browser_return_url: http://172.18.1.128.sslip.io/iam/ui/ + # Logging system configuration log: # If set will leak sensitive values (e.g. emails) in the logs. @@ -174,6 +181,9 @@ kratos: "password": { "identifier": true } + }, + "verification": { + "via": "email" } } } @@ -401,3 +411,17 @@ kratos-selfservice-ui-node: config: csrfCookieName: cookie_name + +# Mailslurper configuration +mailslurper: + image: + registry: docker.io + repository: oryd/mailslurper + tag: latest-smtps + pullPolicy: "IfNotPresent" + pullSecrets: [ ] + service: + annotations: [ ] + uiPort: 4436 + apiPort: 4437 + smtpPort: 1025