From 942e81775f88349e7fd4f459f4c223e39c58df2e Mon Sep 17 00:00:00 2001
From: Maarten de Waard <maarten@greenhost.nl>
Date: Thu, 23 Jun 2022 09:27:54 +0000
Subject: [PATCH] Docker based proxy

---
 README.md           | 117 +++++++++-----------------------------------
 docker-compose.yml  |  75 ++++++++++++++++++++++++++++
 proxy/default.conf  |  26 ++++++++++
 run_app.sh          |  65 +++++++++++-------------
 set-port-forward.sh |  18 -------
 5 files changed, 152 insertions(+), 149 deletions(-)
 create mode 100644 docker-compose.yml
 create mode 100644 proxy/default.conf
 delete mode 100755 set-port-forward.sh

diff --git a/README.md b/README.md
index 3ba154df..6c1a6fe8 100644
--- a/README.md
+++ b/README.md
@@ -50,9 +50,9 @@ Follow the instructions [in the dashboard-dev-overrides
 repository](https://open.greenhost.net/stackspin/dashboard-dev-overrides#dashboard-dev-overrides)
 in order to set up a development-capable cluster.
 The end-points for the Dashboard,
-as well as Kratos and Hydra, will point to `localhost` in that cluster.
-As a result, you can run those components locally, and still log into Stackspin
-applications that run on the cluster.
+as well as Kratos and Hydra, will point to `http://stackspin_proxy:8081` in that cluster.
+As a result, you can run components using the `docker-compose` file in
+this repository, and still log into Stackspin applications that run on the cluster.
 
 
 ## Setting up the local development environment
@@ -63,115 +63,42 @@ After this process is finished, the following will run locally:
 - The
   [dashboard-backend](https://open.greenhost.net/stackspin/dashboard-backend)
 
-The following will be available on localhost through a proxy and port-forwards:
+The following will be available locally through a proxy and port-forwards:
 
-- Hydra
-- Kratos
+- Hydra admin
+- Kratos admin and public
 - The MariaDB database connections
 
 These need to be available locally, because Kratos wants to run on the same
 domain as the front-end that serves the login interface.
 
 
-### 1. Setup port forwards
+### 1. Setup hosts file
 
-To be able to work on the dashboard,
-we have to configure our development system to access all the remote services and endpoints.
-
-A helper script is available in this directory
-to setup and redirect the relevant ports locally.
-It will open ports 8000, 8080, 4445, 3306 to get access to all APIs.
-To use it, you'll need `kubectl` access to the cluster:
-
-1. Install `kubectl` (available through `snap` on Linux)
-2. Download the kubeconfig: `scp root@stackspin.example.com:/etc/rancher/k3s/k3s.yaml kube_config_stackspin.example.com.yaml`
-3. Set `kubectl` to use the kubeconfig: `export KUBECONFIG=$PWD/kube_config_stackspin.example.com.yaml`.
-4. Test if it works:
-
-   ```
-   kubectl get ingress -n stackspin
-   ```
-
-   Should return something like:
-
-   ```
-   NAME                                 CLASS    HOSTS                             ADDRESS         PORTS     AGE
-   hydra-public                         <none>   sso.stackspin.example.com            213.108.110.5   80, 443   39d
-   dashboard                            <none>   dashboard.stackspin.example.com      213.108.110.5   80, 443   150d
-   kube-prometheus-stack-grafana        <none>   grafana.stackspin.example.com        213.108.110.5   80, 443   108d
-   kube-prometheus-stack-alertmanager   <none>   alertmanager.stackspin.example.com   213.108.110.5   80, 443   108d
-   kube-prometheus-stack-prometheus     <none>   prometheus.stackspin.example.com     213.108.110.5   80, 443   108d
-   ```
-5. Run the script to forward ports of the services to your local setup:
-
-   ```
-   ./set-port-forward.sh
-   ```
-
-   As long as the script runs, your connection stays open.
-   End the script by pressing `ctrl + c` and your port-forwards will end as well.
-
-### 2. Configure a local proxy
-
-Because of strict CORS headers,
-we have to map the public Kratos API and login app,
-which we will run locally with a local proxy.
-
-This can be done with any proxy server, here we use `nginx`.
-Be sure you have NGINX installed and listening on port 80 locally:
-`sudo apt-get install nginx` should be enough.
-
-Now configure NGINX with this configuration in `/etc/nginx/sites-enabled/default`
+The application will run on `http://stackspin_proxy`. Add the following line to
+`/etc/hosts` to be able to access that from your browser:
 
 ```
-server {
-    listen 80 default_server;
-    listen [::]:80 default_server;
-
-    root /var/www/html;
-
-    index index.html;
-
-    server_name _;
-
-    # Flask app and dashboard-backend
-    location / {
-        proxy_pass http://127.0.0.1:5000/;
-        proxy_redirect     default;
-        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
-    }
-
-    # Kratos APIs
-    location /kratos/  {
-        proxy_pass http://127.0.0.1:8080/;
-        proxy_redirect     default;
-        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
-    }
-}
+127.0.0.1	stackspin_proxy
 ```
 
-Reload your NGINX:
+### 2. Kubernetes access
 
-```
-sudo systemctl reload nginx.service
-```
+The script needs you to have access to the Kubernetes cluster that runs
+Stackspin. Point the `KUBECONFIG` environment variable to a kubectl config. That
+kubeconfig will be mounted inside docker containers, so also make sure your
+Docker user can read it.
 
-### 3. Run FLASK app
+### 3. Run it all
 
-Now it is time to start the flask app.
-Please make sure you are using Python 3 in your environment.
-And install the required dependencies:
+Now, run this script that sets a few environment variables based on what is in
+your cluster secrets, and starts `docker-compose` to start a reverse proxy as
+well as the flask application in this repository.
 
 ```
-pip3 install -r requirements.txt
+./run_app.sh
 ```
 
-Then copy `run_app.sh` to `run_app.local.sh` and change the secrets defined in it.
-
-You can now start the app by running
-
-```
-./run_app.local.sh
-```
+### 4. Front-end developmenet
 
-Lastly, start the [dashboard front-end app](https://open.greenhost.net/stackspin/dashboard/#yarn-start)
+Start the [dashboard front-end app](https://open.greenhost.net/stackspin/dashboard/#yarn-start).
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..34de6fdd
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,75 @@
+version: '3'
+services:
+  stackspin_proxy:
+    image: nginx:1.22.0
+    ports:
+      - "8081:8081"
+    volumes:
+      - ./proxy/default.conf:/etc/nginx/conf.d/default.conf
+    depends_on:
+      - kube_port_kratos_public
+      - flask_app
+  flask_app:
+    build: .
+    environment:
+      - FLASK_APP=app.py
+      - FLASK_ENV=development
+      - HYDRA_CLIENT_ID=dashboard-local
+
+      # Domain-specific URL settings
+      - HYDRA_AUTHORIZATION_BASE_URL=https://sso.$DOMAIN/oauth2/auth
+      - TOKEN_URL=https://sso.$DOMAIN/oauth2/token
+      - HYDRA_PUBLIC_URL=https://sso.$DOMAIN
+
+      # Local path overrides
+      - KRATOS_PUBLIC_URL=http://stackspin_proxy:8081/kratos
+      - KRATOS_ADMIN_URL=http://kube_port_kratos_admin:8000
+      - HYDRA_ADMIN_URL=http://kube_port_hydra_admin:4445
+      - LOGIN_PANEL_URL=http://stackspin_proxy:8081/web/
+      - DATABASE_URL=mysql+pymysql://stackspin:$DATABASE_PASSWORD@kube_port_mysql/stackspin
+
+      # ENV variables that are deployment-specific
+      - SECRET_KEY=$FLASK_SECRET_KEY
+      - HYDRA_CLIENT_SECRET=$HYDRA_CLIENT_SECRET
+      # - OAUTHLIB_INSECURE_TRANSPORT=1
+    ports:
+      - "5000:5000"
+    volumes:
+      - .:/app
+    depends_on:
+      - kube_port_mysql
+    entrypoint: ["bash", "-c", "flask run --host $$(hostname -i)"]
+  kube_port_kratos_admin:
+    image: bitnami/kubectl:1.24.1
+    user: "${KUBECTL_UID}:${KUBECTL_GID}"
+    expose:
+      - 8000
+    volumes:
+      - "$KUBECONFIG:/.kube/config"
+    entrypoint: ["bash", "-c", "kubectl -n stackspin port-forward --address $$(hostname -i) service/kratos-admin 8000:80"]
+  kube_port_hydra_admin:
+    image: bitnami/kubectl:1.24.1
+    user: "${KUBECTL_UID}:${KUBECTL_GID}"
+    expose:
+      - 4445
+    volumes:
+      - "$KUBECONFIG:/.kube/config"
+    entrypoint: ["bash", "-c", "kubectl -n stackspin port-forward --address $$(hostname -i) service/hydra-admin 4445:4445"]
+  kube_port_kratos_public:
+    image: bitnami/kubectl:1.24.1
+    user: "${KUBECTL_UID}:${KUBECTL_GID}"
+    ports:
+      - "8080:8080"
+    expose:
+      - 8080
+    volumes:
+      - "$KUBECONFIG:/.kube/config"
+    entrypoint: ["bash", "-c", "kubectl -n stackspin port-forward --address $$(hostname -i) service/kratos-public 8080:80"]
+  kube_port_mysql:
+    image: bitnami/kubectl:1.24.1
+    user: "${KUBECTL_UID}:${KUBECTL_GID}"
+    expose:
+      - 3306
+    volumes:
+      - "$KUBECONFIG:/.kube/config"
+    entrypoint: ["bash", "-c", "kubectl -n stackspin port-forward --address $$(hostname -i) service/single-sign-on-database-mariadb 3306:3306"]
diff --git a/proxy/default.conf b/proxy/default.conf
new file mode 100644
index 00000000..42a6fdfd
--- /dev/null
+++ b/proxy/default.conf
@@ -0,0 +1,26 @@
+# Default server configuration
+#
+server {
+    listen 8081 default_server;
+    listen [::]:8081 default_server;
+
+    root /var/www/html;
+
+    index index.html;
+
+    server_name _;
+
+    # Flask app
+    location / {
+        proxy_pass http://flask_app:5000/;
+        proxy_redirect     default;
+        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
+    }
+
+    # Kratos Public
+    location /kratos/  {
+        proxy_pass http://kube_port_kratos_public:8080/;
+        proxy_redirect     default;
+        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
+    }
+}
diff --git a/run_app.sh b/run_app.sh
index ff58aa08..b4d203d9 100755
--- a/run_app.sh
+++ b/run_app.sh
@@ -1,39 +1,32 @@
+#!/usr/bin/env bash
 
-GREEN='\033[0;32m'
-RED='\033[1;31m'
-NC='\033[0m' # No Color
-
-# Check if kratos port is open
-if nc -z localhost 8000;
-then
-    echo -e "${GREEN}Great! It looks like the Kratos Admin port is available${NC}"
-else
-    echo -e "${RED}**********************************************************${NC}"
-    echo -e "${RED}WARNING! It looks like the Kratos Admin port NOT available${NC}"
-    echo -e "${RED}please run in a seperate terminal:                        ${NC}"
-    echo -e "${RED}                                                          ${NC}"
-    echo -e "${RED}./set-port-forward.sh                                     ${NC}"
-    echo -e "${RED}                                                          ${NC}"
-    echo -e "${RED}We will continue to start the app after 5 seconds.        ${NC}"
-    echo -e "${RED}**********************************************************${NC}"
-    sleep 5
+set -euo pipefail
+
+export DATABASE_PASSWORD=$(kubectl get secret -n flux-system stackspin-single-sign-on-variables -o jsonpath --template '{.data.dashboard_database_password}' | base64 -d)
+export DOMAIN=$(kubectl get secret -n flux-system stackspin-cluster-variables -o jsonpath --template '{.data.domain}' | base64 -d)
+export HYDRA_CLIENT_SECRET=$(kubectl get secret -n flux-system stackspin-dashboard-local-oauth-variables -o jsonpath --template '{.data.client_secret}' | base64 -d)
+export FLASK_SECRET_KEY=$(kubectl get secret -n flux-system stackspin-dashboard-variables -o jsonpath --template '{.data.backend_secret_key}' | base64 -d)
+
+
+if [[ -z "$DATABASE_PASSWORD" ]]; then
+    echo "Could not find database password in stackspin-single-sign-on-variables secret"
+    exit 1
+fi
+
+if [[ -z "$DOMAIN" ]]; then
+    echo "Could not find domain name in stackspin-cluster-variables secret"
+    exit 1
+fi
+
+if [[ -z "$FLASK_SECRET_KEY" ]]; then
+    echo "Could not find backend_secret_key in stackspin-dashboard-variables secret"
+    exit 1
+fi
+
+if [[ -z "$HYDRA_CLIENT_SECRET" ]]; then
+    echo "Could not find client_secret in stackspin-dashboard-local-oauth-variables secret"
+    echo "make sure you add this secret following instructions in the dashboard-dev-overrides repository"
+    exit 1
 fi
 
-export FLASK_APP=app.py
-export FLASK_ENV=development
-export SECRET_KEY="e38hq!@0n64g@qe6)5csk41t=ljo2vllog(%k7njnm4b@kh42c"
-export HYDRA_CLIENT_ID="dashboard-local"
-export HYDRA_CLIENT_SECRET="gDSEuakxzybHBHJocnmtDOLMwlWWEvPh"
-export HYDRA_AUTHORIZATION_BASE_URL="https://sso.init.stackspin.net/oauth2/auth"
-export TOKEN_URL="https://sso.init.stackspin.net/oauth2/token"
-
-# Login facilitator paths
-export KRATOS_PUBLIC_URL=http://localhost/kratos
-export KRATOS_ADMIN_URL=http://localhost:8000
-export HYDRA_PUBLIC_URL="https://sso.init.stackspin.net"
-export HYDRA_ADMIN_URL=http://localhost:4445
-export LOGIN_PANEL_URL=http://localhost/web/
-#export DATABASE_URL="mysql+pymysql://stackspin:stackspin@localhost/stackspin?charset=utf8mb4"
-export DATABASE_URL="mysql+pymysql://stackspin:OZBSDkMdbdvEIOomnwpOqLdaiHDKbzWY@localhost/stackspin"
-
-flask run
+KUBECTL_UID=${UID:-1001} KUBECTL_GID=${GID:-0} docker compose up
diff --git a/set-port-forward.sh b/set-port-forward.sh
deleted file mode 100755
index 2577a334..00000000
--- a/set-port-forward.sh
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/bin/bash
-
-echo "
-kratos admin port will be at localhost: 8000
-kratos public port will be at localhost: 8080
-hydra admin port will be at localhost: 4445
-mysql port will be at localhost: 3306
-"
-
-# Kill all processes when this script ends
-trap "exit" INT TERM ERR
-trap "kill 0" EXIT
-
-# Add forwarded ports for all processes
-kubectl port-forward -n stackspin service/kratos-admin 8000:80 &
-kubectl port-forward -n stackspin service/kratos-public 8080:80 &
-kubectl port-forward -n stackspin service/hydra-admin 4445:4445 &
-kubectl port-forward -n stackspin service/single-sign-on-database-mariadb 3306:3306
-- 
GitLab