308 lines
9.4 KiB
Terraform
308 lines
9.4 KiB
Terraform
# =============================================================================
|
|
# Realm
|
|
# =============================================================================
|
|
|
|
resource "keycloak_realm" "hexor" {
|
|
realm = "hexor"
|
|
enabled = true
|
|
|
|
display_name = "Hexor"
|
|
|
|
login_theme = "keycloak.v2"
|
|
account_theme = "keycloak.v3"
|
|
|
|
registration_allowed = false
|
|
reset_password_allowed = true
|
|
remember_me = true
|
|
verify_email = false
|
|
login_with_email_allowed = true
|
|
duplicate_emails_allowed = false
|
|
|
|
ssl_required = "external"
|
|
|
|
web_authn_passwordless_policy {
|
|
relying_party_entity_name = "Hexor"
|
|
relying_party_id = "hexor.cy"
|
|
signature_algorithms = ["ES256", "RS256"]
|
|
user_verification_requirement = "required"
|
|
require_resident_key = "Yes"
|
|
attestation_conveyance_preference = "none"
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# Passkey (WebAuthn Passwordless) — required action
|
|
# =============================================================================
|
|
|
|
resource "keycloak_required_action" "webauthn_register_passwordless" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
alias = "webauthn-register-passwordless"
|
|
name = "Webauthn Register Passwordless"
|
|
enabled = true
|
|
default_action = false
|
|
}
|
|
|
|
# =============================================================================
|
|
# Browser flow with Passkey support
|
|
# =============================================================================
|
|
|
|
resource "keycloak_authentication_flow" "browser_with_passkey" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
alias = "browser-with-passkey"
|
|
description = "Browser flow with passkey/password alternatives"
|
|
provider_id = "basic-flow"
|
|
}
|
|
|
|
# --- Cookie (re-use existing session) ---
|
|
|
|
resource "keycloak_authentication_execution" "cookie" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
parent_flow_alias = keycloak_authentication_flow.browser_with_passkey.alias
|
|
authenticator = "auth-cookie"
|
|
requirement = "ALTERNATIVE"
|
|
priority = 10
|
|
}
|
|
|
|
# --- Identity Provider Redirector ---
|
|
|
|
resource "keycloak_authentication_execution" "idp_redirector" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
parent_flow_alias = keycloak_authentication_flow.browser_with_passkey.alias
|
|
authenticator = "identity-provider-redirector"
|
|
requirement = "ALTERNATIVE"
|
|
priority = 20
|
|
}
|
|
|
|
# --- Passkey (WebAuthn Passwordless) ---
|
|
|
|
resource "keycloak_authentication_execution" "passkey" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
parent_flow_alias = keycloak_authentication_flow.browser_with_passkey.alias
|
|
authenticator = "webauthn-authenticator-passwordless"
|
|
requirement = "ALTERNATIVE"
|
|
priority = 30
|
|
}
|
|
|
|
# --- Username/Password + optional 2FA subflow ---
|
|
|
|
resource "keycloak_authentication_subflow" "forms" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
parent_flow_alias = keycloak_authentication_flow.browser_with_passkey.alias
|
|
alias = "browser-with-passkey-forms"
|
|
provider_id = "basic-flow"
|
|
requirement = "ALTERNATIVE"
|
|
priority = 40
|
|
}
|
|
|
|
resource "keycloak_authentication_execution" "username_password" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
parent_flow_alias = keycloak_authentication_subflow.forms.alias
|
|
authenticator = "auth-username-password-form"
|
|
requirement = "REQUIRED"
|
|
priority = 10
|
|
}
|
|
|
|
resource "keycloak_authentication_subflow" "conditional_2fa" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
parent_flow_alias = keycloak_authentication_subflow.forms.alias
|
|
alias = "browser-with-passkey-conditional-2fa"
|
|
provider_id = "basic-flow"
|
|
requirement = "CONDITIONAL"
|
|
priority = 20
|
|
}
|
|
|
|
resource "keycloak_authentication_execution" "condition_user_configured" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
parent_flow_alias = keycloak_authentication_subflow.conditional_2fa.alias
|
|
authenticator = "conditional-user-configured"
|
|
requirement = "REQUIRED"
|
|
priority = 10
|
|
}
|
|
|
|
resource "keycloak_authentication_execution" "otp_form" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
parent_flow_alias = keycloak_authentication_subflow.conditional_2fa.alias
|
|
authenticator = "auth-otp-form"
|
|
requirement = "ALTERNATIVE"
|
|
priority = 20
|
|
}
|
|
|
|
# --- Bind the new flow as the browser flow ---
|
|
|
|
resource "keycloak_authentication_bindings" "browser" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
browser_flow = keycloak_authentication_flow.browser_with_passkey.alias
|
|
}
|
|
|
|
# =============================================================================
|
|
# Google Identity Provider
|
|
# =============================================================================
|
|
|
|
resource "keycloak_oidc_google_identity_provider" "google" {
|
|
realm = keycloak_realm.hexor.id
|
|
client_id = var.google_client_id
|
|
client_secret = var.google_client_secret
|
|
|
|
trust_email = true
|
|
sync_mode = "IMPORT"
|
|
}
|
|
|
|
# =============================================================================
|
|
# Standalone groups
|
|
# =============================================================================
|
|
|
|
resource "keycloak_group" "standalone" {
|
|
for_each = toset(var.groups)
|
|
|
|
realm_id = keycloak_realm.hexor.id
|
|
name = each.value
|
|
}
|
|
|
|
resource "keycloak_default_groups" "default" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
group_ids = [for g in keycloak_group.standalone : g.id if g.name == "users"]
|
|
}
|
|
|
|
# =============================================================================
|
|
# rsauth2-proxy client (production)
|
|
# =============================================================================
|
|
|
|
resource "keycloak_openid_client" "rsauth2_proxy" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
client_id = "rsauth2-proxy"
|
|
|
|
name = "rsauth2-proxy"
|
|
enabled = true
|
|
access_type = "CONFIDENTIAL"
|
|
standard_flow_enabled = true
|
|
direct_access_grants_enabled = false
|
|
|
|
valid_redirect_uris = [
|
|
"https://oauth.hexor.cy/callback",
|
|
]
|
|
|
|
web_origins = [
|
|
"https://oauth.hexor.cy",
|
|
]
|
|
}
|
|
|
|
resource "keycloak_openid_group_membership_protocol_mapper" "rsauth2_proxy_groups" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
client_id = keycloak_openid_client.rsauth2_proxy.id
|
|
name = "groups"
|
|
claim_name = "groups"
|
|
full_path = false
|
|
}
|
|
|
|
resource "keycloak_openid_client_default_scopes" "rsauth2_proxy" {
|
|
realm_id = keycloak_realm.hexor.id
|
|
client_id = keycloak_openid_client.rsauth2_proxy.id
|
|
|
|
default_scopes = [
|
|
"profile",
|
|
"email",
|
|
]
|
|
}
|
|
|
|
# =============================================================================
|
|
# Proxy applications — auto-created groups + routes ConfigMap
|
|
# =============================================================================
|
|
|
|
resource "keycloak_group" "app" {
|
|
for_each = var.proxy_applications
|
|
|
|
realm_id = keycloak_realm.hexor.id
|
|
name = "app-${each.key}"
|
|
}
|
|
|
|
locals {
|
|
app_allowed_groups = {
|
|
for k, v in var.proxy_applications : k => concat(
|
|
["app-${k}"],
|
|
v.allowed_groups
|
|
)
|
|
}
|
|
}
|
|
|
|
# =============================================================================
|
|
# OAuth2 applications — full OIDC clients for apps that handle auth themselves
|
|
# =============================================================================
|
|
|
|
resource "keycloak_openid_client" "oauth2_app" {
|
|
for_each = var.oauth2_applications
|
|
|
|
realm_id = keycloak_realm.hexor.id
|
|
client_id = each.key
|
|
|
|
name = each.key
|
|
enabled = true
|
|
access_type = "CONFIDENTIAL"
|
|
standard_flow_enabled = true
|
|
direct_access_grants_enabled = false
|
|
|
|
valid_redirect_uris = each.value.redirect_uris
|
|
valid_post_logout_redirect_uris = each.value.post_logout_redirect_uris
|
|
web_origins = each.value.web_origins
|
|
}
|
|
|
|
resource "keycloak_openid_group_membership_protocol_mapper" "oauth2_app_groups" {
|
|
for_each = var.oauth2_applications
|
|
|
|
realm_id = keycloak_realm.hexor.id
|
|
client_id = keycloak_openid_client.oauth2_app[each.key].id
|
|
name = "groups"
|
|
claim_name = "groups"
|
|
full_path = false
|
|
}
|
|
|
|
resource "keycloak_openid_client_default_scopes" "oauth2_app" {
|
|
for_each = var.oauth2_applications
|
|
|
|
realm_id = keycloak_realm.hexor.id
|
|
client_id = keycloak_openid_client.oauth2_app[each.key].id
|
|
|
|
default_scopes = concat(
|
|
["profile", "email"],
|
|
each.value.extra_default_scopes
|
|
)
|
|
}
|
|
|
|
resource "keycloak_openid_client_optional_scopes" "oauth2_app" {
|
|
for_each = {
|
|
for k, v in var.oauth2_applications : k => v if length(v.extra_optional_scopes) > 0
|
|
}
|
|
|
|
realm_id = keycloak_realm.hexor.id
|
|
client_id = keycloak_openid_client.oauth2_app[each.key].id
|
|
|
|
optional_scopes = each.value.extra_optional_scopes
|
|
}
|
|
|
|
resource "keycloak_group" "oauth2_app" {
|
|
for_each = var.oauth2_applications
|
|
|
|
realm_id = keycloak_realm.hexor.id
|
|
name = "app-${each.key}"
|
|
}
|
|
|
|
# =============================================================================
|
|
# Proxy applications — routes ConfigMap
|
|
# =============================================================================
|
|
|
|
resource "kubernetes_config_map_v1" "auth_proxy_routes" {
|
|
metadata {
|
|
name = "auth-proxy-routes"
|
|
namespace = "auth-proxy"
|
|
}
|
|
|
|
data = {
|
|
"routes.yaml" = yamlencode({
|
|
routes = {
|
|
for k, v in var.proxy_applications : v.domain => {
|
|
allowed_groups = local.app_allowed_groups[k]
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|