This commit is contained in:
commit
6eacafe945
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
src/__pycache__
|
||||||
|
src/ENV
|
||||||
|
src/.venv
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
deployment/secrets.txt
|
||||||
|
src/.venv
|
||||||
|
src/__pycache__
|
||||||
|
src/ENV
|
||||||
|
|
32
.woodpecker.yml
Normal file
32
.woodpecker.yml
Normal file
@ -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
|
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@ -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"
|
||||||
|
|
43
deployment/decrypt-secrets.sh
Executable file
43
deployment/decrypt-secrets.sh
Executable file
@ -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
|
||||||
|
|
||||||
|
|
70
deployment/deploy-yml.tmpl
Normal file
70
deployment/deploy-yml.tmpl
Normal file
@ -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
|
||||||
|
|
41
deployment/deploy.sh
Executable file
41
deployment/deploy.sh
Executable file
@ -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
|
||||||
|
|
27
deployment/encrypt-secrets.sh
Executable file
27
deployment/encrypt-secrets.sh
Executable file
@ -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
|
||||||
|
|
13
deployment/oidc-config.json
Normal file
13
deployment/oidc-config.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
5
deployment/secrets.enc
Normal file
5
deployment/secrets.enc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
U2FsdGVkX1+uD433QQ1ZrBZgwtJsOHAXSLLhEoqQKwnt6IMztghrgnFihkUxkPo8
|
||||||
|
LMjaH2aDwvEFh6BgzHgf+AAsbdQc0eUj1TJnIK9nd5e/5JZTRY9oQW5sMFjx7HDB
|
||||||
|
VZ/YLX4/hsPMIsfG1cYXkE0dcpYPGgj2Y/YT33mNVcyU0VQ+DL7qRsMZXwohGQHE
|
||||||
|
7R3sRXcUNLAnfdQWXOzkPtOg8UGE01Rki/DLEoDJgsLye6skqyIjrCQ7siaW5fER
|
||||||
|
GoJCSzckwUUnzCrVa6b1tg==
|
74
src/app.py
Normal file
74
src/app.py
Normal file
@ -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, <a href="/private">See private</a> '
|
||||||
|
'<a href="/logout">Log out</a>') % \
|
||||||
|
oidc.user_getfield('preferred_username')
|
||||||
|
else:
|
||||||
|
return 'Welcome anonymous, <a href="/private">Log in</a>'
|
||||||
|
|
||||||
|
|
||||||
|
@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!
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">Home</a></li>
|
||||||
|
<li><a href="/logout">Logout</a></li>
|
||||||
|
</ul>""" %
|
||||||
|
(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! <a href="/">Return</a>'
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(port=8080)
|
19
src/requirements.txt
Normal file
19
src/requirements.txt
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user