feat(oauth2-proxy) add oauth2-proxy module

This commit is contained in:
Masaki Yatsu
2025-09-13 00:15:31 +09:00
parent cf28e427c2
commit 45aa5bd20e
6 changed files with 292 additions and 0 deletions

View File

@@ -18,6 +18,7 @@ mod k8s
mod longhorn mod longhorn
mod metabase mod metabase
mod minio mod minio
mod oauth2-proxy
mod postgres mod postgres
mod utils mod utils
mod vault mod vault

130
oauth2-proxy/justfile Normal file
View File

@@ -0,0 +1,130 @@
set fallback := true
export OAUTH2_PROXY_NAMESPACE := env("OAUTH2_PROXY_NAMESPACE", "default")
export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "")
export KEYCLOAK_REALM := env("KEYCLOAK_REALM", "buunstack")
export OAUTH2_PROXY_HOST := env("OAUTH2_PROXY_HOST", "")
export EXTERNAL_SECRETS_NAMESPACE := env("EXTERNAL_SECRETS_NAMESPACE", "external-secrets")
[private]
default:
@just --list --unsorted --list-submodules
# Setup OAuth2 Proxy for an application
setup-for-app app_name app_host app_namespace="default" upstream_service="":
#!/bin/bash
set -euo pipefail
echo "Setting up OAuth2 Proxy for {{ app_name }}"
# Create Keycloak client
echo "Creating Keycloak client for {{ app_name }}..."
client_id="{{ app_name }}-oauth2-proxy"
redirect_url="https://{{ app_host }}/oauth2/callback"
# Generate client secret for confidential client
client_secret=$(just utils::random-password 32)
if ! just keycloak::create-client "${KEYCLOAK_REALM}" "${client_id}" "${redirect_url}" "${client_secret}"; then
echo "Failed to create Keycloak client"
exit 1
fi
# Add audience mapper to Keycloak client
echo "Adding audience mapper to Keycloak client..."
just keycloak::add-audience-mapper "${client_id}"
# Generate cookie secret
cookie_secret=$(just utils::random-password 32)
# Create namespace if it doesn't exist
kubectl get namespace {{ app_namespace }} &>/dev/null || \
kubectl create namespace {{ app_namespace }}
# Store secrets
if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then
echo "External Secrets Operator detected. Storing credentials in Vault..."
just vault::put "oauth2-proxy/{{ app_name }}" \
client_id="${client_id}" \
client_secret="${client_secret}" \
cookie_secret="${cookie_secret}"
# Create ExternalSecret
export APP_NAME="{{ app_name }}"
export APP_HOST="{{ app_host }}"
export APP_NAMESPACE="{{ app_namespace }}"
gomplate -f oauth2-proxy-external-secret.gomplate.yaml | kubectl apply -f -
echo "Waiting for ExternalSecret to sync..."
kubectl wait --for=condition=Ready externalsecret/oauth2-proxy-{{ app_name }}-config \
-n {{ app_namespace }} --timeout=60s
else
echo "Creating Kubernetes secret directly..."
kubectl create secret generic oauth2-proxy-{{ app_name }}-config \
-n {{ app_namespace }} \
--from-literal=client_id="${client_id}" \
--from-literal=client_secret="${client_secret}" \
--from-literal=cookie_secret="${cookie_secret}" \
--dry-run=client -o yaml | kubectl apply -f -
fi
# Set upstream service (default to static response if not provided)
if [ -z "{{ upstream_service }}" ]; then
upstream_service="static://202"
else
upstream_service="{{ upstream_service }}"
fi
# Deploy OAuth2 Proxy
export APP_NAME="{{ app_name }}"
export APP_HOST="{{ app_host }}"
export APP_NAMESPACE="{{ app_namespace }}"
export UPSTREAM_SERVICE="${upstream_service}"
gomplate -f oauth2-proxy-deployment.gomplate.yaml | kubectl apply -f -
gomplate -f oauth2-proxy-service.gomplate.yaml | kubectl apply -f -
gomplate -f oauth2-proxy-ingressroute.gomplate.yaml | kubectl apply -f -
echo "Waiting for OAuth2 Proxy to be ready..."
kubectl wait --for=condition=Available deployment/oauth2-proxy-{{ app_name }} \
-n {{ app_namespace }} --timeout=120s
echo "OAuth2 Proxy setup completed for {{ app_name }}"
echo "Access URL: https://{{ app_host }}/oauth2/sign_in"
# Remove OAuth2 Proxy for an application
remove-for-app app_name app_namespace="default":
#!/bin/bash
set -euo pipefail
echo "Removing OAuth2 Proxy for {{ app_name }}"
# Delete Kubernetes resources
kubectl delete ingressroute oauth2-proxy-{{ app_name }} -n {{ app_namespace }} --ignore-not-found
kubectl delete service oauth2-proxy-{{ app_name }} -n {{ app_namespace }} --ignore-not-found
kubectl delete deployment oauth2-proxy-{{ app_name }} -n {{ app_namespace }} --ignore-not-found
kubectl delete configmap oauth2-proxy-{{ app_name }}-config -n {{ app_namespace }} --ignore-not-found
kubectl delete secret oauth2-proxy-{{ app_name }}-config -n {{ app_namespace }} --ignore-not-found
kubectl delete externalsecret oauth2-proxy-{{ app_name }}-config -n {{ app_namespace }} --ignore-not-found
# Remove Vault secrets if External Secrets is available
if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then
just vault::delete "oauth2-proxy/{{ app_name }}"
fi
# Delete Keycloak client
client_id="{{ app_name }}-oauth2-proxy"
just keycloak::delete-client "${KEYCLOAK_REALM}" "${client_id}"
echo "OAuth2 Proxy removed for {{ app_name }}"
# List OAuth2 Proxy deployments
list:
@echo "OAuth2 Proxy deployments:"
@kubectl get deployments -A -l app.kubernetes.io/component=oauth2-proxy
# Show OAuth2 Proxy status for an application
status app_name app_namespace="default":
@echo "OAuth2 Proxy status for {{ app_name }}:"
@kubectl get deployment,service,ingressroute -n {{ app_namespace }} -l app={{ app_name }}-oauth2-proxy

View File

@@ -0,0 +1,78 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: oauth2-proxy-{{ .Env.APP_NAME }}-config
namespace: {{ .Env.APP_NAMESPACE }}
data:
config.cfg: |
http_address = "0.0.0.0:4180"
provider = "keycloak-oidc"
oidc_issuer_url = "https://{{ .Env.KEYCLOAK_HOST }}/realms/{{ .Env.KEYCLOAK_REALM }}"
redirect_url = "https://{{ .Env.APP_HOST }}/oauth2/callback"
email_domains = "*"
reverse_proxy = true
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: oauth2-proxy-{{ .Env.APP_NAME }}
namespace: {{ .Env.APP_NAMESPACE }}
labels:
app: {{ .Env.APP_NAME }}-oauth2-proxy
app.kubernetes.io/component: oauth2-proxy
spec:
replicas: 1
selector:
matchLabels:
app: {{ .Env.APP_NAME }}-oauth2-proxy
template:
metadata:
labels:
app: {{ .Env.APP_NAME }}-oauth2-proxy
app.kubernetes.io/component: oauth2-proxy
spec:
containers:
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.6.0
args:
- --config=/etc/oauth2-proxy/config.cfg
- --upstream=http://{{ .Env.UPSTREAM_SERVICE }}
env:
- name: OAUTH2_PROXY_CLIENT_ID
valueFrom:
secretKeyRef:
name: oauth2-proxy-{{ .Env.APP_NAME }}-config
key: client_id
- name: OAUTH2_PROXY_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: oauth2-proxy-{{ .Env.APP_NAME }}-config
key: client_secret
- name: OAUTH2_PROXY_COOKIE_SECRET
valueFrom:
secretKeyRef:
name: oauth2-proxy-{{ .Env.APP_NAME }}-config
key: cookie_secret
ports:
- containerPort: 4180
name: http
volumeMounts:
- name: config
mountPath: /etc/oauth2-proxy/
readinessProbe:
httpGet:
path: /ping
port: 4180
initialDelaySeconds: 3
timeoutSeconds: 1
livenessProbe:
httpGet:
path: /ping
port: 4180
initialDelaySeconds: 3
timeoutSeconds: 1
volumes:
- name: config
configMap:
name: oauth2-proxy-{{ .Env.APP_NAME }}-config

View File

@@ -0,0 +1,32 @@
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: oauth2-proxy-{{ .Env.APP_NAME }}-config
namespace: {{ .Env.APP_NAMESPACE }}
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-secret-store
kind: ClusterSecretStore
target:
name: oauth2-proxy-{{ .Env.APP_NAME }}-config
creationPolicy: Owner
template:
type: Opaque
data:
client_id: "{{ `{{ .client_id }}` }}"
client_secret: "{{ `{{ .client_secret }}` }}"
cookie_secret: "{{ `{{ .cookie_secret }}` }}"
data:
- secretKey: client_id
remoteRef:
key: oauth2-proxy/{{ .Env.APP_NAME }}
property: client_id
- secretKey: client_secret
remoteRef:
key: oauth2-proxy/{{ .Env.APP_NAME }}
property: client_secret
- secretKey: cookie_secret
remoteRef:
key: oauth2-proxy/{{ .Env.APP_NAME }}
property: cookie_secret

View File

@@ -0,0 +1,36 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: {{ .Env.APP_NAME }}-auth-headers
namespace: {{ .Env.APP_NAMESPACE }}
spec:
headers:
sslRedirect: true
stsSeconds: 315360000
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
sslHost: {{ .Env.APP_HOST }}
stsIncludeSubdomains: true
stsPreload: true
frameDeny: true
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: oauth2-proxy-{{ .Env.APP_NAME }}
namespace: {{ .Env.APP_NAMESPACE }}
labels:
app: {{ .Env.APP_NAME }}-oauth2-proxy
spec:
entryPoints:
- websecure
routes:
- match: "Host(`{{ .Env.APP_HOST }}`)"
kind: Rule
services:
- name: oauth2-proxy-{{ .Env.APP_NAME }}
port: 80
middlewares:
- name: {{ .Env.APP_NAME }}-auth-headers

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: oauth2-proxy-{{ .Env.APP_NAME }}
namespace: {{ .Env.APP_NAMESPACE }}
labels:
app: {{ .Env.APP_NAME }}-oauth2-proxy
spec:
ports:
- port: 80
targetPort: 4180
protocol: TCP
name: http
selector:
app: {{ .Env.APP_NAME }}-oauth2-proxy