configure vault oidc login and cicd jwt login

This commit is contained in:
2024-10-07 17:24:25 +02:00
parent 5beaee60ac
commit 50399328dc
17 changed files with 271 additions and 28 deletions

View File

@@ -8,9 +8,25 @@
- name: gitea_admin_password
prompt: Enter gitea admin password
unsafe: true # password can contain uncommon chars such as '{'
roles:
- arcodange.factory.gitea_token
tasks:
- name: Setup Hashicorp Vault
include_role:
name: hashicorp_vault
name: hashicorp_vault
- name: share VAULT CA
block:
- name: read traefik CA
include_role:
name: arcodange.factory.traefik_certs
post_tasks:
- include_role:
name: arcodange.factory.gitea_token
vars:
gitea_token_delete: true

View File

@@ -25,6 +25,10 @@ variable "gitea_app" {
})
}
# kubectl -n kube-system exec $(kubectl -n kube-system get pod -l app.kubernetes.io/name=traefik -o jsonpath="{.items[0]['.metadata.name']}") -- cat /data/acme.json | jq '(.letsencrypt.Certificates | map(select(.domain.main=="arcodange.duckdns.org")))[0]' | jq '.certificate' -r | base64 -d | openssl x509
variable "ca_pem" {
type = string
}
terraform {
required_providers {
vault = {
@@ -45,9 +49,11 @@ resource "vault_jwt_auth_backend" "gitea" {
path = "gitea"
type = "oidc"
oidc_discovery_url = var.gitea_app.url
oidc_discovery_ca_pem = var.ca_pem
oidc_client_id = var.gitea_app.id
oidc_client_secret = var.gitea_app.secret
bound_issuer = var.gitea_app.url
tune {
allowed_response_headers = []
audit_non_hmac_request_keys = []
@@ -65,13 +71,50 @@ resource "vault_jwt_auth_backend_role" "gitea" {
role_name = "default"
token_policies = ["default"]
user_claim = "email"
role_type = "oidc"
allowed_redirect_uris = [
"http://localhost:8250/oidc/callback", # for command line login
"${var.vault_address}/ui/vault/auth/gitea/oidc/callback",
"https://webapp.arcodange.duckdns.org/oauth-callback",
]
}
resource "vault_jwt_auth_backend" "gitea_jwt" {
description = var.gitea_app.description
default_role = "gitea_cicd"
path = "gitea_jwt"
type = "jwt"
oidc_discovery_url = var.gitea_app.url
oidc_discovery_ca_pem = var.ca_pem
bound_issuer = var.gitea_app.url
tune {
allowed_response_headers = []
audit_non_hmac_request_keys = []
audit_non_hmac_response_keys = []
default_lease_ttl = "15m"
listing_visibility = "hidden"
max_lease_ttl = "15m"
passthrough_request_headers = []
token_type = "default-batch"
}
}
resource "vault_jwt_auth_backend_role" "gitea_jwt_cicd" {
backend = vault_jwt_auth_backend.gitea_jwt.path
role_name = "gitea_cicd"
token_policies = ["default"]
bound_audiences = [
var.gitea_app.id,
]
user_claim = "email"
role_type = "jwt"
}
data "vault_policy_document" "admin" {
rule {
path = "*"
@@ -87,8 +130,29 @@ resource "vault_identity_entity" "admin" {
name = var.admin_email
policies = [vault_policy.admin.name]
}
resource "vault_identity_entity_alias" "admin" {
resource "vault_identity_entity_alias" "admin_gitea" {
name = var.admin_email
mount_accessor = vault_jwt_auth_backend.gitea.accessor
canonical_id = vault_identity_entity.admin.id
}
resource "vault_identity_entity_alias" "admin_gitea_jwt" {
name = var.admin_email
mount_accessor = vault_jwt_auth_backend.gitea_jwt.accessor
canonical_id = vault_identity_entity.admin.id
}
resource "vault_mount" "kvv1" {
path = "kvv1"
type = "kv"
options = { version = "1" }
description = "KV Version 1 secret engine mount"
}
resource "vault_kv_secret" "google_credential" {
path = "${vault_mount.kvv1.path}/google/credentials"
data_json = jsonencode(
{
credentials = file("~/.config/gcloud/application_default_credentials.json")
}
)
}

View File

@@ -72,7 +72,9 @@ async function setupApp() {
console.warn('app not found');
await applicationsPanel.locator('input[name="application_name"]').fill(appName);
await applicationsPanel.locator('textarea[name="redirect_uris"]').fill([
`${vaultAddress}/ui/vault/auth/gitea/oidc/callback`
'http://localhost:8250/oidc/callback', // for command line login
`${vaultAddress}/ui/vault/auth/gitea/oidc/callback`,
'https://webapp.arcodange.duckdns.org/oauth-callback',
].join('\n'));
await applicationsPanel.locator('form[action="/admin/applications/oauth2"] > button').dblclick()

View File

@@ -15,8 +15,11 @@
- include_role:
name: arcodange.factory.playwright
- include_role:
name: arcodange.factory.traefik_certs
- set_fact:
gite_app: '{{ playwright_job.stdout | from_json }}'
gitea_app: '{{ playwright_job.stdout | from_json }}'
volume_name: tofu-{{ ansible_date_time.iso8601.replace(':','-') }}
@@ -31,15 +34,35 @@
--entrypoint=''
ghcr.io/opentofu/opentofu:latest
{{ command }}
register: last_tofu_command
loop:
- tofu init -no-color
- >-
tofu apply -auto-approve -no-color
-var='gitea_app={{ gite_app | to_json }}'
-var='gitea_app={{ gitea_app | to_json }}'
-var='vault_address={{ vault_address }}'
-var='vault_token={{ vault_root_token }}'
-var='ca_pem={{ traefik_cert_pem }}'
loop_control:
loop_var: command
extended: true
label: tofu command n°{{ ansible_loop.index0 }}
always:
- shell: docker volume rm {{ volume_name }}
- shell: docker volume rm {{ volume_name }}
- #when: last_tofu_command.stderr | length > 0
debug:
var: last_tofu_command
# msg: '{{ last_tofu_command.stderr }}'
- include_role:
name: arcodange.factory.gitea_secret
vars:
gitea_secret_name: vault_oauth__sh_b64
gitea_secret_value: >-
{{ lookup('ansible.builtin.template', 'oidc_jwt_token.sh.j2', template_vars = {
'GITEA_BASE_URL': 'https://gitea.arcodange.duckdns.org',
'OIDC_CLIENT_ID': gitea_app.id,
'OIDC_CLIENT_SECRET': gitea_app.secret,
}) | b64encode }}
gitea_owner_type: 'org' # value != 'user'

View File

@@ -0,0 +1,107 @@
#!/bin/bash
set -eu
# Variables à ajuster selon ta configuration
CLIENT_ID="{{ OIDC_CLIENT_ID }}"
CLIENT_SECRET="{{ OIDC_CLIENT_SECRET }}"
REDIRECT_URI="{{ OIDC_CLIENT_CALLBACK | default('https://webapp.arcodange.duckdns.org/oauth-callback') }}" # Redirige ici après l'authentification
AUTH_URL="{{ GITEA_BASE_URL | default('https://gitea.arcodange.duckdns.org') }}/login/oauth/authorize"
TOKEN_URL="{{ GITEA_BASE_URL | default('https://gitea.arcodange.duckdns.org') }}/login/oauth/access_token"
ISSUER="https://gitea.arcodange.duckdns.org/"
# SCOPE="openid email profile groups" # Scope que tu souhaites obtenir - profile groups
SCOPE="email openid read:user" # Scope que tu souhaites obtenir - profile groups
set +u
STATE="$RANDOM-$RANDOM-$RANDOM-$RANDOM-$RANDOM-$RANDOM" # Anti-CSRF (valeur aléatoire)
set -u
CODE=""
MAX_ATTEMPTS=100 # Nombre maximum de tentatives
INTERVAL=5 # Intervalle en secondes entre chaque tentative
# Fonction pour effectuer le polling
poll_state() {
local attempt=1
while [ $attempt -le $MAX_ATTEMPTS ]; do
#echo "Tentative $attempt/$MAX_ATTEMPTS: Requête à l'endpoint /retrieve pour state=$STATE..."
# Effectuer la requête GET
RESPONSE=$(curl -s -w "%{http_code}" -o /tmp/response_body "https://webapp.arcodange.duckdns.org/retrieve?state=$STATE")
HTTP_CODE=$(tail -n1 <<< "$RESPONSE")
if [ "$HTTP_CODE" == "200" ]; then
CODE=$(cat /tmp/response_body)
echo ''
return 0
elif [ "$HTTP_CODE" == "404" ]; then
echo -n "."
else
echo "Erreur lors de la requête (HTTP $HTTP_CODE)."
return 1
fi
# Attendre avant de refaire une requête
sleep $INTERVAL
attempt=$((attempt + 1))
done
echo "Échec après $MAX_ATTEMPTS tentatives, code non trouvé."
return 1
}
# 1. Rediriger l'utilisateur vers l'URL d'authentification
echo "Ouvrez le lien suivant dans votre navigateur pour vous authentifier dans Gitea:"
echo "$AUTH_URL?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=$(sed 's/ /%20/g' <<<$SCOPE)&state=$STATE"
# Device Flow simulation (non implémenté par Gitea)
if [ -n ${GITHUB_ACTOR:-} ]; then
>&2 echo "hello $GITHUB_ACTOR :)"
poll_state
else
# 2. L'utilisateur doit coller le code ici
# stty -echo
printf "Collez le code d'autorisation (code+state) ici après authentification: "
read CODE_STATE
# stty echo
CODE=`awk '{print $2}' <<< $CODE_STATE`
p_STATE=`awk '{print $4}' <<< $CODE_STATE`
if [ $STATE != $p_STATE ]; then
>&2 echo 'mauvais csrf (state)'
exit 1
fi
fi
# 3. Échanger le code d'autorisation contre un access token
RESPONSE=$(curl -s -X POST $TOKEN_URL \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
-d "grant_type=authorization_code" \
-d "scope=$SCOPE" \
-d "code=$CODE" \
-d "redirect_uri=$REDIRECT_URI")
# 4. Extraire l'access token de la réponse
ACCESS_TOKEN=$(echo $RESPONSE | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')
ID_TOKEN=$(echo $RESPONSE | sed -n 's/.*"id_token":"\([^"]*\)".*/\1/p')
# 5. Vérifier si le token a été obtenu et l'afficher
if [ -z "$ACCESS_TOKEN" ]; then
echo "Erreur: Impossible d'obtenir un token d'accès."
echo "Réponse de l'API: $RESPONSE"
exit 1
fi
if [ -n ${GITHUB_ACTOR:-} ]; then
>&2 echo "using `cut -d '.' -f 2 <<< $ID_TOKEN | base64 -d 2>/dev/null | jq '.email'` account"
echo "::add-mask::$ACCESS_TOKEN"
echo "::add-mask::$ID_TOKEN"
echo "id_token=$ID_TOKEN" >> $GITHUB_OUTPUT
echo "access_token=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
else
echo -n "$ACCESS_TOKEN" > .access_token
echo -n "$ID_TOKEN" > .id_token
fi