Setup SAML locally

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2025-12-12 18:46:17 +01:00
parent 949983d2e0
commit 82ae2f701b
4 changed files with 250 additions and 16 deletions

1
.gitignore vendored
View File

@@ -12,3 +12,4 @@ sbom-docker.json
*.pem
*.crt
*.key
compose/keycloak/probo-realm.json

View File

@@ -1,13 +1,17 @@
NPM?= npm
NPX?= npx
PRETTIER?= $(NPX) prettier
GO?= go
DOCKER?= docker
SYFT ?= syft
GRYPE ?= grype
CP ?= cp
MKDIR ?= mkdir -p
MKCERT ?= mkcert
CAT ?= cat
CP ?= cp
DOCKER ?= docker
GO ?= go
GRYPE ?= grype
MKCERT ?= mkcert
MKDIR ?= mkdir -p
NPM ?= npm
NPX ?= npx
OPENSSL ?= openssl
PRETTIER ?= $(NPX) prettier
SED ?= sed
SYFT ?= syft
TAIL ?= tail
DOCKER_BUILD_FLAGS?=
DOCKER_BUILD= DOCKER_BUILDKIT=1 $(DOCKER) build $(DOCKER_BUILD_FLAGS)
@@ -95,8 +99,8 @@ test-e2e-coverage: bin/probod-coverage ## Run e2e tests with coverage
.PHONY: coverage-combined
coverage-combined: coverage-report test-e2e-coverage ## Generate combined coverage report (unit + e2e)
@cat coverage.out > coverage-combined.out
@tail -n +2 coverage-e2e.out >> coverage-combined.out
@$(CAT) coverage.out > coverage-combined.out
@$(TAIL) -n +2 coverage-e2e.out >> coverage-combined.out
$(GO) tool cover -html=coverage-combined.out -o=coverage-combined.html
.PHONY: build
@@ -206,7 +210,7 @@ clean: ## Clean the project (node_modules and build artifacts)
$(RM) -rf coverage/
.PHONY: stack-up
stack-up: compose/pebble/certs/rootCA.pem ## Start the docker stack as a deamon
stack-up: compose/pebble/certs/rootCA.pem compose/keycloak/probo-realm.json ## Start the docker stack as a deamon
$(DOCKER_COMPOSE) up -d
.PHONY: stack-down
@@ -230,9 +234,19 @@ goreleaser-check: ## Check goreleaser configuration
goreleaser check
compose/pebble/certs/rootCA.pem:
@$(MKDIR) -p compose/pebble/certs
@$(MKDIR) compose/pebble/certs
$(MKCERT) -cert-file compose/pebble/certs/pebble.crt \
-key-file compose/pebble/certs/pebble.key \
localhost 127.0.0.1 ::1 pebble
$(CP) "$$($(MKCERT) -CAROOT)/rootCA.pem" compose/pebble/certs/rootCA.pem
$(CP) "$$($(MKCERT) -CAROOT)/rootCA-key.pem" compose/pebble/certs/rootCA-key.pem
compose/keycloak/certs/cert.pem:
$(MKDIR) ./compose/keycloak/certs
$(OPENSSL) req -x509 -newkey rsa:2048 -keyout compose/keycloak/certs/private-key.pem -out compose/keycloak/certs/cert.pem -days 3650 -nodes -subj "/CN=keycloak-saml-signing"
compose/keycloak/probo-realm.json: compose/keycloak/probo-realm.json.tmpl compose/keycloak/certs/cert.pem
$(SED) \
-e "s|CERTIFICATE_PLACEHOLDER|$$(awk 'NR==1 {printf "%s", $$0; next} {printf "\\\\n%s", $$0}' compose/keycloak/certs/cert.pem)|g" \
-e "s|PRIVATE_KEY_PLACEHOLDER|$$(awk 'NR==1 {printf "%s", $$0; next} {printf "\\\\n%s", $$0}' compose/keycloak/certs/private-key.pem)|g" \
$@.tmpl > $@

View File

@@ -19,7 +19,7 @@ services:
interval: 5s
timeout: 5s
retries: 5
minio:
image: "quay.io/minio/minio"
entrypoint: "sh"
@@ -33,7 +33,7 @@ services:
environment:
MINIO_ROOT_USER: "probod"
MINIO_ROOT_PASSWORD: "thisisnotasecret"
grafana:
image: "grafana/grafana:latest"
ports:
@@ -129,9 +129,34 @@ services:
- "8056:8056" # Management API
command: -dns01 ":8053" -http01 ":8055" -management ":8056"
keycloak:
image: quay.io/keycloak/keycloak:latest
user: root
environment:
KC_HOSTNAME: localhost
KC_HOSTNAME_PORT: 8082
KC_HOSTNAME_STRICT: false
KC_HOSTNAME_STRICT_HTTPS: false
KC_LOG_LEVEL: info
KC_METRICS_ENABLED: true
KC_HEALTH_ENABLED: true
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
command:
- "start-dev"
- "--import-realm"
- "--verbose"
ports:
- 8082:8080
volumes:
- "./compose/keycloak:/opt/keycloak/data/import:ro"
- "keycloak-data:/opt/keycloak/data/h2:rw"
volumes:
postgres-data:
minio-data:
grafana-data:
prometheus-data:
tempo-data:
keycloak-data:

View File

@@ -0,0 +1,194 @@
{
"id": "probo",
"realm": "probo",
"displayName": "Probo",
"enabled": true,
"sslRequired": "none",
"registrationAllowed": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"editUsernameAllowed": false,
"bruteForceProtected": true,
"roles": {
"realm": [
{
"name": "admin",
"description": "Administrator role"
},
{
"name": "member",
"description": "Regular member role"
},
{
"name": "viewer",
"description": "Read-only viewer role"
}
]
},
"defaultRoles": ["member"],
"clients": [
{
"clientId": "http://localhost:5173/connect/saml/metadata",
"name": "Probo Console",
"description": "Probo GRC Platform - SAML Service Provider",
"enabled": true,
"protocol": "saml",
"fullScopeAllowed": true,
"frontchannelLogout": true,
"attributes": {
"saml.assertion.signature": "true",
"saml.force.post.binding": "true",
"saml.multivalued.roles": "false",
"saml.encrypt": "false",
"saml.server.signature": "true",
"saml.server.signature.keyinfo.ext": "false",
"saml.signature.algorithm": "RSA_SHA256",
"saml_force_name_id_format": "true",
"saml_name_id_format": "email",
"saml.client.signature": "false",
"saml.authnstatement": "true",
"saml_single_logout_service_url_post": "http://localhost:5173/connect/saml/logout",
"saml_single_logout_service_url_redirect": "http://localhost:5173/connect/saml/logout",
"saml.onetimeuse.condition": "false"
},
"rootUrl": "http://localhost:5173",
"baseUrl": "/",
"adminUrl": "http://localhost:5173/connect/saml/consume",
"redirectUris": ["http://localhost:5173/*"],
"webOrigins": ["http://localhost:5173"],
"protocolMappers": [
{
"name": "email",
"protocol": "saml",
"protocolMapper": "saml-user-property-mapper",
"consentRequired": false,
"config": {
"attribute.nameformat": "Basic",
"user.attribute": "email",
"friendly.name": "email",
"attribute.name": "email"
}
},
{
"name": "firstName",
"protocol": "saml",
"protocolMapper": "saml-user-property-mapper",
"consentRequired": false,
"config": {
"attribute.nameformat": "Basic",
"user.attribute": "firstName",
"friendly.name": "firstName",
"attribute.name": "firstName"
}
},
{
"name": "lastName",
"protocol": "saml",
"protocolMapper": "saml-user-property-mapper",
"consentRequired": false,
"config": {
"attribute.nameformat": "Basic",
"user.attribute": "lastName",
"friendly.name": "lastName",
"attribute.name": "lastName"
}
},
{
"name": "role",
"protocol": "saml",
"protocolMapper": "saml-role-list-mapper",
"consentRequired": false,
"config": {
"single": "true",
"attribute.nameformat": "Basic",
"friendly.name": "role",
"attribute.name": "role"
}
}
]
}
],
"components": {
"org.keycloak.keys.KeyProvider": [
{
"name": "custom-saml-signing",
"providerId": "rsa",
"config": {
"priority": ["200"],
"enabled": ["true"],
"active": ["true"],
"certificate": ["CERTIFICATE_PLACEHOLDER"],
"privateKey": ["PRIVATE_KEY_PLACEHOLDER"]
}
}
]
},
"users": [
{
"username": "owner@example.com",
"email": "owner@example.com",
"emailVerified": true,
"enabled": true,
"firstName": "Owner",
"lastName": "User",
"credentials": [
{
"type": "password",
"value": "owner@example.com",
"temporary": false
}
],
"realmRoles": ["owner"]
},
{
"username": "admin@example.com",
"email": "admin@example.com",
"emailVerified": true,
"enabled": true,
"firstName": "Admin",
"lastName": "User",
"credentials": [
{
"type": "password",
"value": "admin@example.com",
"temporary": false
}
],
"realmRoles": ["admin"]
},
{
"username": "member@example.com",
"email": "member@example.com",
"emailVerified": true,
"enabled": true,
"firstName": "Member",
"lastName": "User",
"credentials": [
{
"type": "password",
"value": "member123",
"temporary": false
}
],
"realmRoles": ["member"]
},
{
"username": "viewer@example.com",
"email": "viewer@example.com",
"emailVerified": true,
"enabled": true,
"firstName": "Viewer",
"lastName": "User",
"credentials": [
{
"type": "password",
"value": "viewer@example.com",
"temporary": false
}
],
"realmRoles": ["viewer"]
}
]
}