Add Authentik OIDC SSO as the default human login for OpenBao #147
@@ -0,0 +1,17 @@
|
||||
# Authentik (OIDC) human SSO. The mount path is the filename and MUST stay
|
||||
# "oidc": the Authentik provider registers strict redirect URIs containing
|
||||
# /ui/vault/auth/oidc/oidc/callback, which encode the mount path.
|
||||
#
|
||||
# identity.unkin.net serves the public LetsEncrypt wildcard, so the OpenBao VMs
|
||||
# validate discovery against the system roots with no CA bundle work. Do not
|
||||
# swap it for identity.k8s.syd1.au.unkin.net (internal CA).
|
||||
#
|
||||
# listing_visibility unauth makes OIDC the offered default on the UI login page.
|
||||
# client_id/client_secret are read from kv/service/authentik/oidc-vault.
|
||||
---
|
||||
oidc_discovery_url: "https://identity.unkin.net/application/o/vault/"
|
||||
default_role: "default"
|
||||
description: "Authentik SSO - default human login"
|
||||
listing_visibility: "unauth"
|
||||
default_lease_ttl: 24h
|
||||
max_lease_ttl: 168h
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
# this file doesnt need anything in it, so this data is just to make sure yamlencode reads some yaml data
|
||||
# The filename is the Authentik group name asserted in the ak_groups claim.
|
||||
# Only akP-* permission groups may appear here; akR-* roles are grouping-only.
|
||||
description: foo
|
||||
@@ -0,0 +1,25 @@
|
||||
# Default OIDC login role (the mount's default_role), used by both the web UI
|
||||
# and `bao login -method=oidc`.
|
||||
#
|
||||
# The role grants no policies of its own: authorization comes from the external
|
||||
# identity groups under config/auth_oidc_group, matched on the ak_groups claim.
|
||||
# ak_groups is Authentik's hierarchy-expanding claim (plain `groups` only carries
|
||||
# direct memberships), so nested akP-* permission groups resolve.
|
||||
#
|
||||
# allowed_redirect_uris must match the provider's strict URIs exactly.
|
||||
---
|
||||
user_claim: "email"
|
||||
groups_claim: "ak_groups"
|
||||
oidc_scopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- ak_groups
|
||||
bound_audiences:
|
||||
- vault
|
||||
allowed_redirect_uris:
|
||||
- "http://localhost:8250/oidc/callback"
|
||||
- "https://vault.k8s.syd1.au.unkin.net/ui/vault/auth/oidc/oidc/callback"
|
||||
- "https://vault.service.consul:8200/ui/vault/auth/oidc/oidc/callback"
|
||||
token_ttl: 3600
|
||||
token_max_ttl: 28800
|
||||
@@ -97,6 +97,27 @@ locals {
|
||||
})
|
||||
if startswith(file_path, "auth_ldap_group/")
|
||||
}
|
||||
auth_oidc_backend = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(basename(file_path), ".yaml") => content
|
||||
if startswith(file_path, "auth_oidc_backend/")
|
||||
}
|
||||
auth_oidc_role = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "auth_oidc_role/", ""), ".yaml") => merge(content, {
|
||||
role_name = trimsuffix(basename(file_path), ".yaml")
|
||||
backend = dirname(replace(file_path, "auth_oidc_role/", ""))
|
||||
})
|
||||
if startswith(file_path, "auth_oidc_role/")
|
||||
}
|
||||
auth_oidc_group = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "auth_oidc_group/", ""), ".yaml") => merge(content, {
|
||||
groupname = trimsuffix(basename(file_path), ".yaml")
|
||||
backend = split("/", replace(file_path, "auth_oidc_group/", ""))[0]
|
||||
})
|
||||
if startswith(file_path, "auth_oidc_group/")
|
||||
}
|
||||
auth_kubernetes_backend = {
|
||||
for file_path, content in local.all_configs :
|
||||
trimsuffix(replace(file_path, "auth_kubernetes_backend/", ""), ".yaml") => content
|
||||
|
||||
@@ -60,6 +60,9 @@ inputs = {
|
||||
auth_approle_role = local.config.auth_approle_role
|
||||
auth_ldap_backend = local.config.auth_ldap_backend
|
||||
auth_ldap_group = local.config.auth_ldap_group
|
||||
auth_oidc_backend = local.config.auth_oidc_backend
|
||||
auth_oidc_role = local.config.auth_oidc_role
|
||||
auth_oidc_group = local.config.auth_oidc_group
|
||||
auth_kubernetes_backend = local.config.auth_kubernetes_backend
|
||||
auth_kubernetes_role = local.config.auth_kubernetes_role
|
||||
kv_secret_backend = local.config.kv_secret_backend
|
||||
|
||||
@@ -64,6 +64,55 @@ module "auth_ldap_group" {
|
||||
depends_on = [module.auth_ldap_backend]
|
||||
}
|
||||
|
||||
module "auth_oidc_backend" {
|
||||
source = "./modules/auth_oidc_backend"
|
||||
|
||||
for_each = var.auth_oidc_backend
|
||||
|
||||
path = each.key
|
||||
description = each.value.description
|
||||
oidc_discovery_url = each.value.oidc_discovery_url
|
||||
client_secret_mount = each.value.client_secret_mount
|
||||
client_secret_path = each.value.client_secret_path
|
||||
default_role = each.value.default_role
|
||||
listing_visibility = each.value.listing_visibility
|
||||
default_lease_ttl = each.value.default_lease_ttl
|
||||
max_lease_ttl = each.value.max_lease_ttl
|
||||
}
|
||||
|
||||
module "auth_oidc_role" {
|
||||
source = "./modules/auth_oidc_role"
|
||||
|
||||
for_each = var.auth_oidc_role
|
||||
|
||||
backend = each.value.backend
|
||||
role_name = each.value.role_name
|
||||
user_claim = each.value.user_claim
|
||||
groups_claim = each.value.groups_claim
|
||||
oidc_scopes = each.value.oidc_scopes
|
||||
bound_audiences = each.value.bound_audiences
|
||||
allowed_redirect_uris = each.value.allowed_redirect_uris
|
||||
token_ttl = each.value.token_ttl
|
||||
token_max_ttl = each.value.token_max_ttl
|
||||
# Human authorization comes from the external identity groups below, so a
|
||||
# login role carrying no policies is the normal case.
|
||||
token_policies = try(var.policy_auth_map[each.value.backend][each.value.role_name], [])
|
||||
|
||||
depends_on = [module.auth_oidc_backend]
|
||||
}
|
||||
|
||||
module "auth_oidc_group" {
|
||||
source = "./modules/auth_oidc_group"
|
||||
|
||||
for_each = var.auth_oidc_group
|
||||
|
||||
groupname = each.value.groupname
|
||||
policies = var.policy_auth_map[each.value.backend][each.value.groupname]
|
||||
mount_accessor = module.auth_oidc_backend[each.value.backend].accessor
|
||||
|
||||
depends_on = [module.auth_oidc_backend]
|
||||
}
|
||||
|
||||
module "auth_kubernetes_backend" {
|
||||
source = "./modules/auth_kubernetes_backend"
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Expected keys in KV secret: client_id, client_secret (generated and written by
|
||||
# terraform-authentik's oauth2 provider module, never seeded by hand).
|
||||
data "vault_kv_secret_v2" "oidc_client" {
|
||||
mount = var.client_secret_mount
|
||||
name = var.client_secret_path
|
||||
}
|
||||
|
||||
resource "vault_jwt_auth_backend" "oidc" {
|
||||
path = var.path
|
||||
type = "oidc"
|
||||
description = var.description
|
||||
oidc_discovery_url = var.oidc_discovery_url
|
||||
oidc_client_id = data.vault_kv_secret_v2.oidc_client.data["client_id"]
|
||||
oidc_client_secret = data.vault_kv_secret_v2.oidc_client.data["client_secret"]
|
||||
default_role = var.default_role
|
||||
|
||||
tune {
|
||||
default_lease_ttl = var.default_lease_ttl
|
||||
max_lease_ttl = var.max_lease_ttl
|
||||
listing_visibility = var.listing_visibility
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
output "accessor" {
|
||||
description = "Accessor of the OIDC auth mount, used to bind identity group aliases"
|
||||
value = vault_jwt_auth_backend.oidc.accessor
|
||||
}
|
||||
|
||||
output "path" {
|
||||
description = "Mount path of the OIDC auth backend"
|
||||
value = vault_jwt_auth_backend.oidc.path
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
variable "path" {
|
||||
description = "Mount path of the OIDC auth backend"
|
||||
type = string
|
||||
default = "oidc"
|
||||
}
|
||||
|
||||
variable "description" {
|
||||
description = "Human-readable description of the auth mount"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "oidc_discovery_url" {
|
||||
description = "OIDC issuer discovery URL of the identity provider"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "client_secret_mount" {
|
||||
description = "KV-v2 mount holding the OIDC client credentials"
|
||||
type = string
|
||||
default = "kv"
|
||||
}
|
||||
|
||||
variable "client_secret_path" {
|
||||
description = "Path within the KV-v2 mount holding client_id and client_secret"
|
||||
type = string
|
||||
default = "service/authentik/oidc-vault"
|
||||
}
|
||||
|
||||
variable "default_role" {
|
||||
description = "Role used when none is supplied at login (the UI/CLI default)"
|
||||
type = string
|
||||
default = "default"
|
||||
}
|
||||
|
||||
variable "listing_visibility" {
|
||||
description = "Specifies whether to show this mount in the UI-specific listing endpoint. Valid values are 'unauth' or 'hidden'"
|
||||
type = string
|
||||
default = null
|
||||
validation {
|
||||
condition = var.listing_visibility == null || contains(["unauth", "hidden"], var.listing_visibility)
|
||||
error_message = "listing_visibility must be either 'unauth' or 'hidden'."
|
||||
}
|
||||
}
|
||||
|
||||
variable "default_lease_ttl" {
|
||||
description = "Specifies the default time-to-live. If set, this overrides the global default. Must be a valid duration string"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "max_lease_ttl" {
|
||||
description = "Specifies the maximum time-to-live. If set, this overrides the global default. Must be a valid duration string"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
# External identity group: membership is asserted by the IdP through the role's
|
||||
# groups_claim, matched by the alias name below.
|
||||
resource "vault_identity_group" "group" {
|
||||
name = var.groupname
|
||||
type = "external"
|
||||
policies = var.policies
|
||||
}
|
||||
|
||||
resource "vault_identity_group_alias" "alias" {
|
||||
name = var.groupname
|
||||
mount_accessor = var.mount_accessor
|
||||
canonical_id = vault_identity_group.group.id
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
variable "groupname" {
|
||||
description = "Name of the IdP group, as it appears in the groups claim"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "policies" {
|
||||
description = "List of policies to assign to the identity group"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "mount_accessor" {
|
||||
description = "Accessor of the OIDC auth mount the alias is bound to"
|
||||
type = string
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
resource "vault_jwt_auth_backend_role" "role" {
|
||||
backend = var.backend
|
||||
role_name = var.role_name
|
||||
role_type = "oidc"
|
||||
user_claim = var.user_claim
|
||||
groups_claim = var.groups_claim
|
||||
oidc_scopes = var.oidc_scopes
|
||||
bound_audiences = var.bound_audiences
|
||||
allowed_redirect_uris = var.allowed_redirect_uris
|
||||
token_ttl = var.token_ttl
|
||||
token_max_ttl = var.token_max_ttl
|
||||
token_policies = var.token_policies
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
version = "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
variable "backend" {
|
||||
description = "The unique path of the OIDC auth backend to configure"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "role_name" {
|
||||
description = "The name of the role"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "user_claim" {
|
||||
description = "Claim used as the entity alias name (the Vault identity of the human)"
|
||||
type = string
|
||||
default = "email"
|
||||
}
|
||||
|
||||
variable "groups_claim" {
|
||||
description = "Claim holding the caller's group memberships, matched against identity group aliases"
|
||||
type = string
|
||||
default = "ak_groups"
|
||||
}
|
||||
|
||||
variable "oidc_scopes" {
|
||||
description = "Scopes requested from the identity provider during the authorization request"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "bound_audiences" {
|
||||
description = "List of audiences (aud claim) accepted in the ID token"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "allowed_redirect_uris" {
|
||||
description = "Redirect URIs accepted for this role. Must match the provider's registered URIs exactly"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "token_ttl" {
|
||||
description = "The TTL period of tokens issued using this role, in seconds"
|
||||
type = number
|
||||
default = 3600
|
||||
}
|
||||
|
||||
variable "token_max_ttl" {
|
||||
description = "The maximum lifetime for generated tokens in number of seconds. Its current value will be referenced at renewal time."
|
||||
type = number
|
||||
default = 28800
|
||||
}
|
||||
|
||||
variable "token_policies" {
|
||||
description = "List of policies to assign to the role (passed from policy_auth_map). Human authorization normally comes from external identity groups instead"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
@@ -62,6 +62,46 @@ variable "auth_ldap_group" {
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "auth_oidc_backend" {
|
||||
description = "Map of OIDC (JWT) auth backends to create"
|
||||
type = map(object({
|
||||
oidc_discovery_url = string
|
||||
description = optional(string)
|
||||
client_secret_mount = optional(string, "kv")
|
||||
client_secret_path = optional(string, "service/authentik/oidc-vault")
|
||||
default_role = optional(string, "default")
|
||||
listing_visibility = optional(string)
|
||||
default_lease_ttl = optional(string)
|
||||
max_lease_ttl = optional(string)
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "auth_oidc_role" {
|
||||
description = "Map of OIDC auth roles to create"
|
||||
type = map(object({
|
||||
role_name = string
|
||||
backend = string
|
||||
allowed_redirect_uris = list(string)
|
||||
user_claim = optional(string, "email")
|
||||
groups_claim = optional(string, "ak_groups")
|
||||
oidc_scopes = optional(list(string), [])
|
||||
bound_audiences = optional(list(string), [])
|
||||
token_ttl = optional(number, 3600)
|
||||
token_max_ttl = optional(number, 28800)
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "auth_oidc_group" {
|
||||
description = "Map of external identity groups bound to an OIDC auth mount"
|
||||
type = map(object({
|
||||
groupname = string
|
||||
backend = string
|
||||
}))
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "auth_kubernetes_backend" {
|
||||
description = "Map of Kubernetes auth backends to create"
|
||||
type = map(object({
|
||||
|
||||
@@ -10,6 +10,12 @@ rules:
|
||||
- list
|
||||
- sudo
|
||||
|
||||
# The oidc entry is an Authentik akP-* permission group, not an LDAP group name:
|
||||
# it names the external identity group under config/auth_oidc_group, so a human
|
||||
# who logs in via Authentik SSO lands on exactly the policy the LDAP vault_admin
|
||||
# group already carries. akR-* roles are grouping-only and never named here.
|
||||
auth:
|
||||
ldap:
|
||||
- vault_admin
|
||||
oidc:
|
||||
- akP-vault-admin
|
||||
|
||||
Reference in New Issue
Block a user