From 6eacafe94518ee66908b635d707806101104560c Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth Date: Tue, 30 Jan 2024 10:53:37 +0100 Subject: [PATCH] initial --- .dockerignore | 3 ++ .gitignore | 5 +++ .woodpecker.yml | 32 +++++++++++++++ Dockerfile | 15 +++++++ deployment/decrypt-secrets.sh | 43 ++++++++++++++++++++ deployment/deploy-yml.tmpl | 70 +++++++++++++++++++++++++++++++++ deployment/deploy.sh | 41 +++++++++++++++++++ deployment/encrypt-secrets.sh | 27 +++++++++++++ deployment/oidc-config.json | 13 ++++++ deployment/secrets.enc | 5 +++ src/app.py | 74 +++++++++++++++++++++++++++++++++++ src/requirements.txt | 19 +++++++++ start.sh | 4 ++ 13 files changed, 351 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .woodpecker.yml create mode 100644 Dockerfile create mode 100755 deployment/decrypt-secrets.sh create mode 100644 deployment/deploy-yml.tmpl create mode 100755 deployment/deploy.sh create mode 100755 deployment/encrypt-secrets.sh create mode 100644 deployment/oidc-config.json create mode 100644 deployment/secrets.enc create mode 100644 src/app.py create mode 100644 src/requirements.txt create mode 100755 start.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3add265 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +src/__pycache__ +src/ENV +src/.venv diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe97e28 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +deployment/secrets.txt +src/.venv +src/__pycache__ +src/ENV + diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..442ec46 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,32 @@ +steps: + build: + image: plugins/kaniko + settings: + repo: gitea.hottis.de/wn/oidc-python-example + registry: + from_secret: container_registry + tags: latest,${CI_COMMIT_SHA},${CI_COMMIT_TAG} + username: + from_secret: container_registry_username + password: + from_secret: container_registry_password + dockerfile: Dockerfile + when: + - event: [push, tag] + + deploy: + image: portainer/kubectl-shell:latest + secrets: + - source: kube_config + target: KUBE_CONFIG_CONTENT + - source: encryption_key + target: ENCRYPTION_KEY + - source: secrets_checksum + target: MD5_CHECKSUM + commands: + - export IMAGE_TAG=$CI_COMMIT_TAG + - printf "$KUBE_CONFIG_CONTENT" > /tmp/kubeconfig + - export KUBECONFIG=/tmp/kubeconfig + - ./deployment/deploy.sh + when: + - event: tag diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5879c03 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.12-alpine3.19 + +ARG APP_DIR="/opt/app" + +COPY ./src/* ${APP_DIR}/ +COPY start.sh ${APP_DIR}/ + +WORKDIR ${APP_DIR} + +RUN pip install -r requirements.txt + +EXPOSE 8080 + +CMD "./start.sh" + diff --git a/deployment/decrypt-secrets.sh b/deployment/decrypt-secrets.sh new file mode 100755 index 0000000..d971ca7 --- /dev/null +++ b/deployment/decrypt-secrets.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +if [ "$ENCRYPTION_KEY" = "" ]; then + echo "ENCRYPTION_KEY not set" + exit 1 +fi + +if [ "$MD5_CHECKSUM" = "" ]; then + echo "No checksum given" + exit 1 +fi + +SECRETS_CIPHERTEXT_FILE=secrets.enc +SECRETS_PLAINTEXT_FILE=/tmp/secrets +TMP_FILE=`mktemp` +POD_NAME_SUFFIX=`date +%s` + +cat $SECRETS_CIPHERTEXT_FILE | \ + kubectl run openssl-$POD_NAME_SUFFIX \ + --rm \ + --image bitnami/debian-base-buildpack:latest \ + --env KEY=$ENCRYPTION_KEY \ + -i \ + -q \ + -- \ + /bin/sh -c "openssl enc -aes-256-cbc -salt -pass env:KEY -a -d" > \ + $TMP_FILE + +if [ `uname` = "Darwin" ]; then + CALCULATED_CHECKSUM=`cat $TMP_FILE | md5` +elif [ `uname` = "Linux" ]; then + CALCULATED_CHECKSUM=`cat $TMP_FILE | md5sum - | awk '{print $1}'` +fi + +if [ "$MD5_CHECKSUM" != "$CALCULATED_CHECKSUM" ]; then + echo "Invalid checksum" + exit 1 +fi + +# cat $TMP_FILE +mv $TMP_FILE $SECRETS_PLAINTEXT_FILE + + diff --git a/deployment/deploy-yml.tmpl b/deployment/deploy-yml.tmpl new file mode 100644 index 0000000..0a0e81e --- /dev/null +++ b/deployment/deploy-yml.tmpl @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: locsrv + namespace: homea + labels: + app: locsrv +spec: + replicas: 1 + selector: + matchLabels: + app: locsrv + template: + metadata: + labels: + app: locsrv + spec: + containers: + - name: locsrv + image: %IMAGE% + envFrom: + - secretRef: + name: locsrv-db-cred + env: + - name: MQTT_BROKER + value: mqtt://emqx01-anonymous-cluster-internal.broker.svc.cluster.local:1883 + - name: MQTT_TOPIC + value: locative/event + - name: GIN_MODE + value: release + ports: + - containerPort: 8080 + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + name: locative +spec: + type: ClusterIP + selector: + app: locsrv + ports: + - name: http + targetPort: 8080 + port: 80 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: locative + annotations: + cert-manager.io/cluster-issuer: letsencrypt-production-http +spec: + tls: + - hosts: + - locative.hottis.de + secretName: locative-cert + rules: + - host: locative.hottis.de + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: locative + port: + number: 80 + diff --git a/deployment/deploy.sh b/deployment/deploy.sh new file mode 100755 index 0000000..f2c3396 --- /dev/null +++ b/deployment/deploy.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +if [ "$IMAGE_TAG" == "" ]; then + echo "Make sure IMAGE_TAG is set" + exit 1 +fi + + +IMAGE_NAME=gitea.hottis.de/wn/oidc-python-example +NAMESPACE=oidc-python-example +DEPLOYMENT_DIR=$PWD/deployment + +pushd $DEPLOYMENT_DIR > /dev/null +./decrypt-secrets.sh || exit 1 +. /tmp/secrets +rm /tmp/secrets + +kubectl create namespace $NAMESPACE \ + --dry-run=client \ + -o yaml | \ + kubectl -f - apply + +kubectl create secret generic secrets \ + --dry-run=client \ + -o yaml \ + --save-config \ + --from-literal=SECRET="$SECRET" \ + --from-literal=CLIENT_SECRETS="$CLIENT_SECRETS" \ + --from-literal=PGUSER="$PGUSER" \ + --from-literal=PGPASSWORD="$PGPASSWORD" \ + --from-literal=PGDATABASE="$PGDATABASE" \ + --from-literal=PGHOST="timescaledb.database.svc.cluster.local" \ + --from-literal=PGSSLMODE="require" | \ + kubectl apply -f - -n $NAMESPACE + +cat $DEPLOYMENT_DIR/deploy-yml.tmpl | \ + sed -e 's,%IMAGE%,'$IMAGE_NAME':'$IMAGE_TAG','g | \ + kubectl apply -f - -n $NAMESPACE + +popd > /dev/null + diff --git a/deployment/encrypt-secrets.sh b/deployment/encrypt-secrets.sh new file mode 100755 index 0000000..38a7b6d --- /dev/null +++ b/deployment/encrypt-secrets.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +ENCRYPTION_KEY=`openssl rand -hex 32` +echo $ENCRYPTION_KEY + +SECRETS_PLAINTEXT_FILE=secrets.txt +SECRETS_CIPHERTEXT_FILE=secrets.enc + +if [ `uname` = "Darwin" ]; then + cat $SECRETS_PLAINTEXT_FILE | md5 +elif [ `uname` = "Linux" ]; then + cat $SECRETS_PLAINTEXT_FILE | md5sum - | awk '{print $1}' +fi + +POD_NAME_SUFFIX=`date +%s` + +cat $SECRETS_PLAINTEXT_FILE | \ + kubectl run openssl-$POD_NAME_SUFFIX \ + --rm \ + --image bitnami/debian-base-buildpack:latest \ + --env KEY=$ENCRYPTION_KEY \ + -i \ + -q \ + -- \ + /bin/sh -c "openssl enc -aes-256-cbc -salt -pass env:KEY -a" > \ + $SECRETS_CIPHERTEXT_FILE + diff --git a/deployment/oidc-config.json b/deployment/oidc-config.json new file mode 100644 index 0000000..3ed7417 --- /dev/null +++ b/deployment/oidc-config.json @@ -0,0 +1,13 @@ +{ + "web": { + "issuer": "https://auth2.hottis.de/realms/hottis", + "auth_uri": "https://auth2.hottis.de/ealms/hottis/protocol/openid-connect/auth", + "client_id": "oidc-python-example", + "client_secret": "%CLIENT_SECRET%", + "redirect_uris": [ + "http://localhost:8080/*" + ], + "userinfo_uri": "https://auth2.hottis.de/realms/hottis/protocol/openid-connect/userinfo", + "token_uri": "https://auth2.hottis.de/realms/hottis/protocol/openid-connect/token" + } +} diff --git a/deployment/secrets.enc b/deployment/secrets.enc new file mode 100644 index 0000000..48c34cf --- /dev/null +++ b/deployment/secrets.enc @@ -0,0 +1,5 @@ +U2FsdGVkX1+uD433QQ1ZrBZgwtJsOHAXSLLhEoqQKwnt6IMztghrgnFihkUxkPo8 +LMjaH2aDwvEFh6BgzHgf+AAsbdQc0eUj1TJnIK9nd5e/5JZTRY9oQW5sMFjx7HDB +VZ/YLX4/hsPMIsfG1cYXkE0dcpYPGgj2Y/YT33mNVcyU0VQ+DL7qRsMZXwohGQHE +7R3sRXcUNLAnfdQWXOzkPtOg8UGE01Rki/DLEoDJgsLye6skqyIjrCQ7siaW5fER +GoJCSzckwUUnzCrVa6b1tg== diff --git a/src/app.py b/src/app.py new file mode 100644 index 0000000..9be57a1 --- /dev/null +++ b/src/app.py @@ -0,0 +1,74 @@ +import json +import logging +import os + +from flask import Flask, g +from flask_oidc import OpenIDConnect +import requests + +logging.basicConfig(level=logging.DEBUG) + +app = Flask(__name__) +app.config.update({ + 'SECRET_KEY': os.environ['SECRET'], + 'DEBUG': False, + 'OIDC_CLIENT_SECRETS': json.loads(os.environ['CLIENT_SECRETS']), + 'OIDC_ID_TOKEN_COOKIE_SECURE': False, + 'OIDC_USER_INFO_ENABLED': True, + 'OIDC_OPENID_REALM': 'hottis', + 'OIDC_SCOPES': ['openid', 'email', 'profile'] +}) + +oidc = OpenIDConnect(app) + + +@app.route('/') +def hello_world(): + if oidc.user_loggedin: + return ('Hello, %s, See private ' + 'Log out') % \ + oidc.user_getfield('preferred_username') + else: + return 'Welcome anonymous, Log in' + + +@app.route('/private') +@oidc.require_login +def hello_me(): + """Example for protected endpoint that extracts private information from the OpenID Connect id_token. + Uses the accompanied access_token to access a backend service. + """ + + info = oidc.user_getinfo(['preferred_username', 'email', 'sub', 'roles']) + + username = info.get('preferred_username') + email = info.get('email') + user_id = info.get('sub') + roles = info.get('roles') + + return ("""%s, %s, %s, %s! + """ % + (username, email, user_id, roles)) + + +# @app.route('/api', methods=['POST']) +# @oidc.accept_token(require_token=True, scopes_required=['openid']) +# def hello_api(): +# """OAuth 2.0 protected API endpoint accessible via AccessToken""" +# +# return json.dumps({'hello': 'Welcome %s' % g.oidc_token_info['sub']}) + + +@app.route('/logout') +def logout(): + """Performs local logout by removing the session cookie.""" + + oidc.logout() + return 'Hi, you have been logged out! Return' + + +if __name__ == '__main__': + app.run(port=8080) diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..e0ceaba --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1,19 @@ +Authlib==1.3.0 +blinker==1.7.0 +certifi==2023.11.17 +cffi==1.16.0 +charset-normalizer==3.3.2 +click==8.1.7 +cryptography==42.0.1 +Flask==3.0.1 +flask-oidc==2.1.1 +gunicorn==21.2.0 +idna==3.6 +itsdangerous==2.1.2 +Jinja2==3.1.3 +MarkupSafe==2.1.4 +packaging==23.2 +pycparser==2.21 +requests==2.31.0 +urllib3==2.1.0 +Werkzeug==3.0.1 diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..f937bc8 --- /dev/null +++ b/start.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +gunicorn 'app:app' --bind 0.0.0.0:8080 --log-level=info --workers=4 +