1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ sbom-docker.json
|
|||||||
*.pem
|
*.pem
|
||||||
*.crt
|
*.crt
|
||||||
*.key
|
*.key
|
||||||
|
compose/keycloak/probo-realm.json
|
||||||
|
|||||||
42
GNUmakefile
42
GNUmakefile
@@ -1,13 +1,17 @@
|
|||||||
NPM?= npm
|
CAT ?= cat
|
||||||
NPX?= npx
|
CP ?= cp
|
||||||
PRETTIER?= $(NPX) prettier
|
DOCKER ?= docker
|
||||||
GO?= go
|
GO ?= go
|
||||||
DOCKER?= docker
|
GRYPE ?= grype
|
||||||
SYFT ?= syft
|
MKCERT ?= mkcert
|
||||||
GRYPE ?= grype
|
MKDIR ?= mkdir -p
|
||||||
CP ?= cp
|
NPM ?= npm
|
||||||
MKDIR ?= mkdir -p
|
NPX ?= npx
|
||||||
MKCERT ?= mkcert
|
OPENSSL ?= openssl
|
||||||
|
PRETTIER ?= $(NPX) prettier
|
||||||
|
SED ?= sed
|
||||||
|
SYFT ?= syft
|
||||||
|
TAIL ?= tail
|
||||||
|
|
||||||
DOCKER_BUILD_FLAGS?=
|
DOCKER_BUILD_FLAGS?=
|
||||||
DOCKER_BUILD= DOCKER_BUILDKIT=1 $(DOCKER) build $(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
|
.PHONY: coverage-combined
|
||||||
coverage-combined: coverage-report test-e2e-coverage ## Generate combined coverage report (unit + e2e)
|
coverage-combined: coverage-report test-e2e-coverage ## Generate combined coverage report (unit + e2e)
|
||||||
@cat coverage.out > coverage-combined.out
|
@$(CAT) coverage.out > coverage-combined.out
|
||||||
@tail -n +2 coverage-e2e.out >> coverage-combined.out
|
@$(TAIL) -n +2 coverage-e2e.out >> coverage-combined.out
|
||||||
$(GO) tool cover -html=coverage-combined.out -o=coverage-combined.html
|
$(GO) tool cover -html=coverage-combined.out -o=coverage-combined.html
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
@@ -206,7 +210,7 @@ clean: ## Clean the project (node_modules and build artifacts)
|
|||||||
$(RM) -rf coverage/
|
$(RM) -rf coverage/
|
||||||
|
|
||||||
.PHONY: stack-up
|
.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
|
$(DOCKER_COMPOSE) up -d
|
||||||
|
|
||||||
.PHONY: stack-down
|
.PHONY: stack-down
|
||||||
@@ -230,9 +234,19 @@ goreleaser-check: ## Check goreleaser configuration
|
|||||||
goreleaser check
|
goreleaser check
|
||||||
|
|
||||||
compose/pebble/certs/rootCA.pem:
|
compose/pebble/certs/rootCA.pem:
|
||||||
@$(MKDIR) -p compose/pebble/certs
|
@$(MKDIR) compose/pebble/certs
|
||||||
$(MKCERT) -cert-file compose/pebble/certs/pebble.crt \
|
$(MKCERT) -cert-file compose/pebble/certs/pebble.crt \
|
||||||
-key-file compose/pebble/certs/pebble.key \
|
-key-file compose/pebble/certs/pebble.key \
|
||||||
localhost 127.0.0.1 ::1 pebble
|
localhost 127.0.0.1 ::1 pebble
|
||||||
$(CP) "$$($(MKCERT) -CAROOT)/rootCA.pem" compose/pebble/certs/rootCA.pem
|
$(CP) "$$($(MKCERT) -CAROOT)/rootCA.pem" compose/pebble/certs/rootCA.pem
|
||||||
$(CP) "$$($(MKCERT) -CAROOT)/rootCA-key.pem" compose/pebble/certs/rootCA-key.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 > $@
|
||||||
|
|||||||
29
compose.yaml
29
compose.yaml
@@ -19,7 +19,7 @@ services:
|
|||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
minio:
|
minio:
|
||||||
image: "quay.io/minio/minio"
|
image: "quay.io/minio/minio"
|
||||||
entrypoint: "sh"
|
entrypoint: "sh"
|
||||||
@@ -33,7 +33,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
MINIO_ROOT_USER: "probod"
|
MINIO_ROOT_USER: "probod"
|
||||||
MINIO_ROOT_PASSWORD: "thisisnotasecret"
|
MINIO_ROOT_PASSWORD: "thisisnotasecret"
|
||||||
|
|
||||||
grafana:
|
grafana:
|
||||||
image: "grafana/grafana:latest"
|
image: "grafana/grafana:latest"
|
||||||
ports:
|
ports:
|
||||||
@@ -129,9 +129,34 @@ services:
|
|||||||
- "8056:8056" # Management API
|
- "8056:8056" # Management API
|
||||||
command: -dns01 ":8053" -http01 ":8055" -management ":8056"
|
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:
|
volumes:
|
||||||
postgres-data:
|
postgres-data:
|
||||||
minio-data:
|
minio-data:
|
||||||
grafana-data:
|
grafana-data:
|
||||||
prometheus-data:
|
prometheus-data:
|
||||||
tempo-data:
|
tempo-data:
|
||||||
|
keycloak-data:
|
||||||
|
|||||||
194
compose/keycloak/probo-realm.json.tmpl
Normal file
194
compose/keycloak/probo-realm.json.tmpl
Normal 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"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user