diff --git a/demos/gaia-x-security/superset-assets.zip b/demos/gaia-x-security/superset-assets.zip new file mode 100644 index 00000000..cd090b69 Binary files /dev/null and b/demos/gaia-x-security/superset-assets.zip differ diff --git a/stacks/gaia-x-security/aas.yaml b/stacks/gaia-x-security/aas.yaml new file mode 100644 index 00000000..18d7d8b7 --- /dev/null +++ b/stacks/gaia-x-security/aas.yaml @@ -0,0 +1,51 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: auth-server + labels: + app: aas +spec: + ports: + - name: http + port: 9000 + targetPort: 9000 + selector: + app: aas + type: NodePort +--- +apiVersion: v1 +kind: Pod +metadata: + name: aas + labels: + app: aas +spec: + containers: + - name: aas + image: oci.stackable.tech/sandbox/xfsc-aas:latest + ports: + - name: web + containerPort: 9000 + protocol: TCP + env: + - name: SPRING_PROFILES_ACTIVE + value: prod + - name: SPRING_DATASOURCE_URL + value: jdbc:postgresql://postgresql-aas:5432/aas + - name: SPRING_DATASOURCE_USERNAME + value: aas + - name: SPRING_DATASOURCE_PASSWORD + value: aas + - name: AAS_IAM_BASEURI + value: http://key-server:8080 + - name: AAS_TSA_URL + value: http://tsa:5000 + - name: AAS_OIDC_ISSUER + value: http://auth-server:9000 + - name: AAS_CACHE_TTL + value: 5m + - name: LOGGING_LEVEL_EU_XFSC_AAS + value: DEBUG + - name: AAS_INVITATION_URI + value: http://localhost:5000/requests? diff --git a/stacks/gaia-x-security/gaia-x-realm.yaml b/stacks/gaia-x-security/gaia-x-realm.yaml new file mode 100644 index 00000000..48643d0f --- /dev/null +++ b/stacks/gaia-x-security/gaia-x-realm.yaml @@ -0,0 +1,2596 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: keycloak-gaia-x-realm +data: + gaia-x-realm.json: |- + { + "id": "gaia-x", + "realm": "gaia-x", + "displayName": "GAIA-X", + "notBefore": 1654094377, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "none", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "114aff6f-9cd0-43e4-b253-ecc8051b78be", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "gaia-x", + "attributes": {} + }, + { + "id": "f43e5019-48a3-4f69-9867-2ab167a22c44", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "gaia-x", + "attributes": {} + }, + { + "id": "8ee7d6f7-f168-4b62-ae34-1c18ffbf631a", + "name": "default-roles-gaia-x", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ] + }, + "clientRole": false, + "containerId": "gaia-x", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "5b9f2d0f-42a7-4840-b0d3-1a721ce966a2", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "e7b13600-8203-4f4a-b9ae-bb1ef1b3ffd0", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "b907922d-e3a9-4d2e-bc0a-98658e807412", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "78cab9dd-0779-4c56-aa81-4a2afda2bf29", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "40904ff5-ed33-4c11-ad98-84eeb6e983f9", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "03ec9856-cc25-42f8-9c73-4a3bd2a7e361", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "e78ea104-4b06-4958-ad5d-218b123c5a7d", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "d8ec8ec3-1537-4612-8f23-c60fca6a2216", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "956c9acd-018e-4c70-b4a0-7204333b3bb8", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "b94967f5-7929-4d6f-ade7-ef2d7db43cd7", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "8071f8be-e915-465c-ba7f-a84feda1769e", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "6fd195b9-bd1e-4b17-b37b-3557b66ce2b7", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "68ca7431-d90d-482a-8901-4bc35cbe6179", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "1f5f2ec2-8d41-4d5d-8dd4-f5f17bf914e9", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-users", + "query-groups" + ] + } + }, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "6ad820d9-8e67-4902-a91b-54a61d6204a9", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "7eaf080d-36ca-4c69-8f86-cb61b9bcbe5d", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "453d293d-ce02-42c8-9703-ed8a99c33214", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "create-client", + "impersonation", + "manage-realm", + "manage-events", + "view-clients", + "view-authorization", + "query-users", + "view-identity-providers", + "query-groups", + "manage-users", + "manage-authorization", + "view-realm", + "view-users", + "manage-clients", + "query-clients", + "query-realms", + "view-events", + "manage-identity-providers" + ] + } + }, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "2642b524-c1b7-4852-b37b-1a09678cb9bc", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + }, + { + "id": "74c6912a-b4d9-4897-9c66-c00f820791a2", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "aas-app": [ + { + "id": "dc9e6728-aceb-41a1-8088-132a9d792143", + "name": "uma_protection", + "composite": false, + "clientRole": true, + "containerId": "ed510a04-4e30-4ef5-baf3-30b42f0d58d5", + "attributes": {} + } + ], + "demo-app": [], + "account-console": [], + "cip-app": [ + { + "id": "8fa23743-5f3f-4cf1-865f-e7b732c6daca", + "name": "uma_protection", + "composite": false, + "clientRole": true, + "containerId": "e92a965c-9236-4ea7-953d-81d7bc021f9f", + "attributes": {} + } + ], + "broker": [], + "account": [ + { + "id": "26a2da99-679c-40e4-a29e-84dc0e87ce47", + "name": "manage-account", + "composite": false, + "clientRole": true, + "containerId": "98919336-c907-4361-9872-6466ca1bae7e", + "attributes": {} + }, + { + "id": "d4d32ec8-dff0-4a6c-8d77-2af81604a5d1", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "98919336-c907-4361-9872-6466ca1bae7e", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "8ee7d6f7-f168-4b62-ae34-1c18ffbf631a", + "name": "default-roles-gaia-x", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "gaia-x" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "users": [ + { + "id": "b98712d1-7fca-4b73-bf20-f6b08315431b", + "createdTimestamp": 1647375741271, + "username": "service-account-aas-app", + "enabled": true, + "totp": false, + "emailVerified": false, + "serviceAccountClientId": "aas-app", + "disableableCredentialTypes": [], + "requiredActions": [], + "clientRoles": { + "aas-app": [ + "uma_protection" + ] + }, + "notBefore": 0, + "groups": [] + }, + { + "id": "86d1f3f9-d9f8-45da-b50d-eb4c9cabe49a", + "createdTimestamp": 1654093531829, + "username": "service-account-cip-app", + "enabled": true, + "totp": false, + "emailVerified": false, + "serviceAccountClientId": "cip-app", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "default-roles-gaia-x" + ], + "clientRoles": { + "cip-app": [ + "uma_protection" + ] + }, + "notBefore": 0, + "groups": [] + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account" + ] + } + ] + }, + "clients": [ + { + "id": "ed510a04-4e30-4ef5-baf3-30b42f0d58d5", + "clientId": "aas-app", + "name": "Authentication & Authorization Service", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "use.refresh.tokens": "true", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "false", + "client_credentials.use_refresh_token": "false", + "require.pushed.authorization.requests": "false", + "saml.client.signature": "false", + "id.token.as.detached.signature": "false", + "saml.assertion.signature": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "exclude.session.state.from.auth.response": "false", + "saml.artifact.binding": "false", + "saml_force_name_id_format": "false", + "acr.loa.map": "{}", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "59cbe9c0-83d8-4ac2-8607-b36c5164e214", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "431f91ae-519a-49e1-a4b2-9716af0be72b", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + }, + { + "id": "6b6e7198-4e9e-4dc7-a8f6-94af77d93652", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "98919336-c907-4361-9872-6466ca1bae7e", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/gaia-x/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/gaia-x/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "bd308877-0534-4ad0-a136-1e4a4bdaac6b", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/gaia-x/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/gaia-x/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "1ff4a2da-164b-4b21-9e10-f0600207f2e7", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "d0eb7727-06f1-491c-9d9a-aecabefdd4a9", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "096c1a58-a545-4089-8273-b320c016f076", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "e92a965c-9236-4ea7-953d-81d7bc021f9f", + "clientId": "cip-app", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "http://test-server:8180/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "oauth2.device.authorization.grant.enabled": "true", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "use.refresh.tokens": "true", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "false", + "client_credentials.use_refresh_token": "false", + "require.pushed.authorization.requests": "false", + "saml.client.signature": "false", + "id.token.as.detached.signature": "false", + "saml.assertion.signature": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "exclude.session.state.from.auth.response": "false", + "saml.artifact.binding": "false", + "saml_force_name_id_format": "false", + "acr.loa.map": "{}", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "30569955-7c45-46cb-b072-b5fb58731e74", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "b123576c-0777-42e8-acd9-aedc14abbc38", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "fa468176-d583-4fb4-80a5-a24a0a4345cb", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "roles" + ], + "optionalClientScopes": [ + "address", + "phone", + "profile", + "offline_access", + "microprofile-jwt", + "email" + ], + "authorizationSettings": { + "allowRemoteResourceManagement": false, + "policyEnforcementMode": "ENFORCING", + "resources": [ + { + "name": "Read Resource", + "ownerManagedAccess": false, + "displayName": "Read Resource", + "attributes": {}, + "_id": "c8f3e615-b1d1-453f-bcb0-b77e33ad4985", + "uris": [ + "/demo/read" + ] + }, + { + "name": "Write Resource", + "ownerManagedAccess": false, + "displayName": "Write Resource", + "attributes": {}, + "_id": "ebbf51b3-36ba-4bde-8c22-1cde8f32cdd5", + "uris": [ + "/demo/write" + ] + }, + { + "name": "Demo Resource", + "ownerManagedAccess": false, + "displayName": "Demo Resource", + "attributes": {}, + "_id": "95d2d0f4-87d3-4c03-b5cb-1a52394551c9", + "uris": [ + "/demo" + ] + } + ], + "policies": [ + { + "id": "13986fff-5832-4456-9171-0fdb425a77e8", + "name": "Default Permission", + "description": "A permission that applies to the default resource type", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "defaultResourceType": "urn:cip-app:resources:default", + "applyPolicies": "[]" + } + }, + { + "id": "bd546d36-fa89-4eda-a837-d9d0608533cf", + "name": "Read Permission", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "resources": "[\"Read Resource\"]", + "applyPolicies": "[]" + } + } + ], + "scopes": [], + "decisionStrategy": "UNANIMOUS" + } + }, + { + "id": "52c9c697-c860-4edd-bde4-5e03904fdb44", + "clientId": "demo-app", + "name": "SSI Demo Application", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "http://test-server:8990/*", + "http://key-server:8080/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.force.post.binding": "false", + "saml.multivalued.roles": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "use.refresh.tokens": "true", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "false", + "client_credentials.use_refresh_token": "false", + "require.pushed.authorization.requests": "false", + "saml.client.signature": "false", + "id.token.as.detached.signature": "false", + "saml.assertion.signature": "false", + "saml.encrypt": "false", + "saml.server.signature": "false", + "exclude.session.state.from.auth.response": "false", + "saml.artifact.binding": "false", + "saml_force_name_id_format": "false", + "acr.loa.map": "{}", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "roles" + ], + "optionalClientScopes": [ + "address", + "phone", + "profile", + "offline_access", + "microprofile-jwt", + "email" + ] + }, + { + "id": "af3a57c9-da9c-47b6-81a0-f131b7637d08", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "12604a51-9ed5-4ec6-967d-373b185f15ec", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/gaia-x/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/gaia-x/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "7a55bf05-4bfa-4904-aa57-91e639f38e9b", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "8cdc0c1b-9349-4792-b8ec-f4d9bcf98d09", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "b0e96b4c-e790-43bb-bc4b-2bef7a9e1f6e", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "0eb18a29-bc6f-49ae-b83f-baa724db43b0", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "8fa06769-45df-4a8f-9826-28404e62bdf7", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "876ac006-7cde-4a7c-ab72-64e078c20b8b", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "ca02a41f-3128-41f5-9a53-b3b25b2adf7f", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "9a0da62f-6824-416e-8eb9-47afcc4fdda3", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "bb79af2b-a17d-4f11-8a94-dfeca9974d3e", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "e8ec0746-2d7f-4bc4-bec2-acf9eeda7493", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "af13725e-1468-4e72-a365-7f7c16f42fc7", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "b125d27b-4038-45d5-a40f-5c5c03507567", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "464f9c67-e95a-4136-ad93-22f5190e304d", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "752a4e76-b88b-4645-ac4e-1cc7133917a8", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "7872f01b-38e7-415a-874b-9004c71a3e88", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "853a6314-4305-4db4-94eb-22c994fe72e4", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "dc5d9dcd-0269-46e6-a289-615e8e9bd1e6", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "12de7cd4-1376-437d-9e6b-08d16c738f95", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "b4cdcbc2-f20b-42d6-923c-b438866d4403", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "8336ad4e-7183-4929-acec-ef1bfcbee46a", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "0aa713da-c832-47f4-bfa5-0395a9eed7e4", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "d653d614-21ea-43ec-bf52-7023064f02f6", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "0c83fef9-fc12-4c8d-912b-0c33705a5e51", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "8a765bf0-4b3c-4f3b-9dd9-7393c43302bb", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "f5dcdd7b-7855-4c8c-9552-43657eb39d49", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + }, + { + "id": "c17817a3-570f-4aa5-8e77-5efb2aa3cc28", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "b8602ae1-9446-4c5a-a22b-06451e0e279e", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "85f9fe13-2366-463f-a67c-28cc7fcb0fe3", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "6a73807f-99d2-4d80-a249-b5f29384bdc6", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "f7585dfa-94f5-4792-aedf-a9f33cd66d3a", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "a2839b81-af82-434d-9ccb-31bc7f716d4d", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "3a0829e0-9a56-4af8-8561-995bfc4eaba2", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "110f237a-c47a-4116-b040-114f66026672", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "e5520b36-30d4-4187-915e-4a4e9f40d5de", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "115b221c-e4ad-4039-89be-71a23f458fbe", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "0f0d05d6-d959-4123-9efa-8fd6493b0b47", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "roles", + "web-origins" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt", + "email", + "profile" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "loginTheme": "gaia-x", + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [ + { + "alias": "ssi-oidc", + "displayName": "SSI OIDC Broker", + "internalId": "532c5037-eb4b-4f73-99df-cbe236279bb2", + "providerId": "oidc", + "enabled": true, + "updateProfileFirstLoginMode": "on", + "trustEmail": true, + "storeToken": false, + "addReadTokenRoleOnCreate": false, + "authenticateByDefault": false, + "linkOnly": false, + "firstBrokerLoginFlowAlias": "first broker login", + "config": { + "userInfoUrl": "http://auth-server:9000/userinfo", + "clientId": "aas-app-oidc", + "tokenUrl": "http://auth-server:9000/oauth2/token", + "uiLocales": "false", + "issuer": "http://auth-server:9000", + "useJwksUrl": "true", + "loginHint": "false", + "authorizationUrl": "http://auth-server:9000/oauth2/authorize", + "clientAuthMethod": "client_secret_post", + "syncMode": "IMPORT", + "clientSecret": "secret", + "defaultScope": "openid profile email" + } + }, + { + "alias": "ssi-siop", + "displayName": "SSI SIOP Broker", + "internalId": "a6fc3365-616b-4432-8b9e-e8beb7a63324", + "providerId": "oidc", + "enabled": true, + "updateProfileFirstLoginMode": "on", + "trustEmail": true, + "storeToken": false, + "addReadTokenRoleOnCreate": false, + "authenticateByDefault": false, + "linkOnly": false, + "firstBrokerLoginFlowAlias": "first broker login", + "config": { + "loginHint": "false", + "userInfoUrl": "http://auth-server:9000/userinfo", + "clientId": "aas-app-siop", + "tokenUrl": "http://auth-server:9000/oauth2/token", + "authorizationUrl": "http://auth-server:9000/oauth2/authorize", + "clientAuthMethod": "client_secret_basic", + "syncMode": "IMPORT", + "clientSecret": "secret2", + "defaultScope": "openid profile email", + "issuer": "http://auth-server:9000", + "useJwksUrl": "true" + } + } + ], + "identityProviderMappers": [ + { + "id": "0a72e8ca-1fdb-4d48-8ca9-62f8e56a45e1", + "name": "last name", + "identityProviderAlias": "ssi-oidc", + "identityProviderMapper": "oidc-user-attribute-idp-mapper", + "config": { + "syncMode": "INHERIT", + "claim": "family_name", + "user.attribute": "lastName" + } + }, + { + "id": "8cdc7471-8a9f-4886-8618-af1034c37f70", + "name": "user name", + "identityProviderAlias": "ssi-siop", + "identityProviderMapper": "oidc-user-attribute-idp-mapper", + "config": { + "syncMode": "INHERIT", + "claim": "preferred_username", + "user.attribute": "username" + } + }, + { + "id": "9d6787c8-468c-4d17-b64b-9dad58289730", + "name": "email", + "identityProviderAlias": "ssi-oidc", + "identityProviderMapper": "oidc-user-attribute-idp-mapper", + "config": { + "syncMode": "INHERIT", + "claim": "email", + "user.attribute": "email" + } + }, + { + "id": "bc81f4b8-e19a-4ed0-b509-799f3f2bf138", + "name": "last name", + "identityProviderAlias": "ssi-siop", + "identityProviderMapper": "oidc-user-attribute-idp-mapper", + "config": { + "syncMode": "INHERIT", + "claim": "family_name", + "user.attribute": "lastName" + } + }, + { + "id": "17b28568-7b84-431d-b27e-acfc09ddcc87", + "name": "first name", + "identityProviderAlias": "ssi-oidc", + "identityProviderMapper": "oidc-user-attribute-idp-mapper", + "config": { + "syncMode": "INHERIT", + "claim": "name", + "user.attribute": "firstName" + } + }, + { + "id": "ef7007c4-a38f-4e2d-90da-c904961b1788", + "name": "first name", + "identityProviderAlias": "ssi-siop", + "identityProviderMapper": "oidc-user-attribute-idp-mapper", + "config": { + "syncMode": "INHERIT", + "claim": "name", + "user.attribute": "firstName" + } + }, + { + "id": "5f10f9a8-0ac4-4ef1-99f8-799f9ef11749", + "name": "user name", + "identityProviderAlias": "ssi-oidc", + "identityProviderMapper": "oidc-user-attribute-idp-mapper", + "config": { + "syncMode": "INHERIT", + "claim": "preferred_username", + "user.attribute": "username" + } + }, + { + "id": "5cc712cd-af22-45dc-b394-4188f3e745bf", + "name": "email", + "identityProviderAlias": "ssi-siop", + "identityProviderMapper": "oidc-user-attribute-idp-mapper", + "config": { + "syncMode": "INHERIT", + "claim": "email", + "user.attribute": "email" + } + } + ], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "b0d0db1f-6ae7-4856-8ce2-7b770d1bc61f", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "9329cb81-3e46-47d6-b350-5d8087e87cf5", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "ec1b1ee9-270a-498f-b9ef-feff690aba87", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-property-mapper", + "saml-role-list-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-property-mapper" + ] + } + }, + { + "id": "f78e90a8-7c92-4ddc-8c6b-4f2363ab1b1e", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "b5d35367-a334-43b3-94d4-6633861c12df", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-property-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper" + ] + } + }, + { + "id": "b69bd178-95a4-488a-ab0b-df7e032b9666", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "2188e132-b7cc-41bc-b967-06b954761f2a", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "a8f92145-2d2e-43e4-9f12-963c0579cc8c", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "eccc20ef-ccc4-4e54-b385-245cd6239bc3", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "4dbacd4a-e98d-4479-86d5-e17bcff5e14f", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "445b8478-0710-49b0-9462-b07a5844bbe6", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "8a29592d-3aaf-48d5-b520-5a9a7d5ae630", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "d9b0da75-ef91-4d12-ab9b-a5078b157865", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "f6df262e-b866-4b74-bde0-0dd2a8936cbf", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "1ddba671-9a1e-47ff-aea0-9dac2a4e4b68", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "52a63bb0-6747-4f22-8014-c7322be546ad", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "c7f1ca27-8e9f-425c-9c05-003416222cdd", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "a4e67553-8bed-49d1-a546-cbd901d56471", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "d376b86b-b1ee-4272-ad93-e66743bf7259", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "2b2131b4-2834-44c5-bd04-c426ddfda894", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "19a88868-3f6e-47ed-9a7c-e5199ae0e0c2", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "c10f1f1b-9548-4f38-afd3-3819561b38bc", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "3d2f1527-a794-4890-b8b7-8bcb31535b6b", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "86503421-8411-4cd0-a251-bbe4926d7a41", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "a1463c49-24b7-4fae-98d6-d9d2bd2d3a98", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "99ae0da5-f4ce-477a-a621-ca043ff32f8c", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "e170404d-8bd3-4153-bcd0-ae84e720fef1", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "67b18d58-6567-48ba-8994-aee92bbfb79b", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "6c8fb550-3983-4558-8998-65433730be96", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Authentication Options", + "userSetupAllowed": false + } + ] + }, + { + "id": "756b055c-15e7-4e07-961c-352bf1db5cf0", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "003b074b-cc91-4aa5-b8d4-eba39940fb57", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "d9692df4-0f49-4d26-9dc0-a0dc0d3d4e1e", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "75864193-c008-4490-abcb-92b1189a396b", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "b2eb64da-3f09-4e63-826e-206aa807f19a", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "814e8014-03e6-4a52-83dd-e50caae6a78b", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "clientOfflineSessionMaxLifespan": "0", + "oauth2DevicePollingInterval": "5", + "clientSessionIdleTimeout": "0", + "userProfileEnabled": "false", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5" + }, + "keycloakVersion": "17.0.0", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } + } diff --git a/stacks/gaia-x-security/keycloak-authenticationclass.yaml b/stacks/gaia-x-security/keycloak-authenticationclass.yaml new file mode 100644 index 00000000..1eb1003d --- /dev/null +++ b/stacks/gaia-x-security/keycloak-authenticationclass.yaml @@ -0,0 +1,12 @@ +apiVersion: authentication.stackable.tech/v1alpha1 +kind: AuthenticationClass +metadata: + name: keycloak +spec: + provider: + oidc: + hostname: key-server + port: 8080 + rootPath: /realms/gaia-x + scopes: [openid profile email] + principalClaim: preferred_username diff --git a/stacks/gaia-x-security/keycloak.yaml b/stacks/gaia-x-security/keycloak.yaml new file mode 100644 index 00000000..de96a75b --- /dev/null +++ b/stacks/gaia-x-security/keycloak.yaml @@ -0,0 +1,68 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: keycloak + labels: + app: keycloak +spec: + replicas: 1 + selector: + matchLabels: + app: keycloak + template: + metadata: + labels: + app: keycloak + spec: + volumes: + - name: keycloak-import-dir + configMap: + name: keycloak-gaia-x-realm + containers: + - name: keycloak + image: quay.io/keycloak/keycloak:22.0.3 + # Keycloak is running in development mode: https://www.keycloak.org/server/configuration#_starting_keycloak + # production mode disables HTTP and requires a TLS configuration, which is currently very difficult to configure + # given that we're running on a NodePort + args: ["start-dev", "--import-realm", "--hostname-url=http://key-server:8080"] + env: + - name: KEYCLOAK_ADMIN + value: admin + - name: KEYCLOAK_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: keycloak-admin-credentials + key: admin + volumeMounts: + - name: keycloak-import-dir + mountPath: /opt/keycloak/data/import + ports: + - name: http + containerPort: 8080 + readinessProbe: + httpGet: + path: /realms/master + port: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: key-server + labels: + app: keycloak +spec: + type: NodePort + selector: + app: keycloak + ports: + - name: http + port: 8080 + targetPort: 8080 +--- +apiVersion: v1 +kind: Secret +metadata: + name: keycloak-admin-credentials +stringData: + admin: "{{ keycloakAdminPassword }}" diff --git a/stacks/gaia-x-security/opa.yaml b/stacks/gaia-x-security/opa.yaml new file mode 100644 index 00000000..6a1c6e90 --- /dev/null +++ b/stacks/gaia-x-security/opa.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: opa.stackable.tech/v1alpha1 +kind: OpaCluster +metadata: + name: opa +spec: + image: + productVersion: 0.61.0 + clusterConfig: + userInfo: + backend: + experimentalXfscAas: + hostname: tsa + port: 5000 + servers: + roleGroups: + default: {} diff --git a/stacks/gaia-x-security/postgres-aas.yaml b/stacks/gaia-x-security/postgres-aas.yaml new file mode 100644 index 00000000..79864fb9 --- /dev/null +++ b/stacks/gaia-x-security/postgres-aas.yaml @@ -0,0 +1,11 @@ +releaseName: postgresql-aas +name: postgresql +repo: + name: bitnami + url: https://charts.bitnami.com/bitnami/ +version: 12.6.6 +options: + auth: + username: aas + password: aas + database: aas diff --git a/stacks/gaia-x-security/release.yaml b/stacks/gaia-x-security/release.yaml new file mode 100644 index 00000000..613dcf46 --- /dev/null +++ b/stacks/gaia-x-security/release.yaml @@ -0,0 +1,17 @@ +releases: + gaia-x-security-release: + releaseDate: 1970-01-01 + description: Intermediate release until everything is released in (hopefully) 24.3 + products: + commons: + operatorVersion: 0.0.0-dev # Needed for OIDC support in AuthClass + listener: + operatorVersion: 23.11.0 + opa: + operatorVersion: 0.0.0-dev # Needed for UIF and OPA 0.61.0 + secret: + operatorVersion: 23.11.0 + superset: + operatorVersion: 0.0.0-dev # Needed for OIDC support + trino: + operatorVersion: 0.0.0-dev # Needed for OIDC support diff --git a/stacks/gaia-x-security/serviceaccount.yaml b/stacks/gaia-x-security/serviceaccount.yaml new file mode 100644 index 00000000..b29d205b --- /dev/null +++ b/stacks/gaia-x-security/serviceaccount.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: demo-serviceaccount + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: demo-clusterrolebinding +subjects: + - kind: ServiceAccount + name: demo-serviceaccount + namespace: default +roleRef: + kind: ClusterRole + name: demo-clusterrole + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: demo-clusterrole +rules: + - apiGroups: + - "" + resources: + - nodes + - services + - endpoints + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - patch diff --git a/stacks/gaia-x-security/setup-keycloak.yaml b/stacks/gaia-x-security/setup-keycloak.yaml new file mode 100644 index 00000000..26fa5900 --- /dev/null +++ b/stacks/gaia-x-security/setup-keycloak.yaml @@ -0,0 +1,83 @@ +apiVersion: v1 +kind: Secret +metadata: + name: keycloak-client-secrets +stringData: + superset: "{{ keycloakSupersetClientSecret }}" + trino: "{{ keycloakTrinoClientSecret }}" +--- +# This job creates users and clients in Keycloak. +# It also creates a ConfigMap with user IDs used by OPA +apiVersion: batch/v1 +kind: Job +metadata: + name: setup-keycloak +spec: + template: + spec: + containers: + - name: setup-keycloak + image: docker.stackable.tech/stackable/testing-tools:0.2.0-stackable0.0.0-dev # We need 0.0.0-dev, so we get kubectl + env: + - name: KEYCLOAK_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: keycloak-admin-credentials + key: admin + - name: SUPERSET_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: keycloak-client-secrets + key: superset + - name: TRINO_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: keycloak-client-secrets + key: trino + command: + - bash + - -x + - -euo + - pipefail + - -c + - | + echo "Download keycloak" + curl -LO https://github.com/keycloak/keycloak/releases/download/22.0.3/keycloak-22.0.3.zip + unzip keycloak-22.0.3.zip + cd keycloak-22.0.3/bin + ./kcadm.sh config credentials --config kcadm.conf --server http://key-server:8080/ --realm master --user admin --password "$KEYCLOAK_ADMIN_PASSWORD" + ./kcadm.sh create clients -r gaia-x --config kcadm.conf -f - << EOF || true + { + "clientId": "trino", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "$TRINO_CLIENT_SECRET", + "redirectUris": [ + "*" + ], + "webOrigins": [ + "*" + ], + "standardFlowEnabled": true, + "protocol": "openid-connect" + } + EOF + ./kcadm.sh create clients -r gaia-x --config kcadm.conf -f - << EOF || true + { + "clientId": "superset", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "$SUPERSET_CLIENT_SECRET", + "redirectUris": [ + "*" + ], + "webOrigins": [ + "*" + ], + "standardFlowEnabled": true, + "protocol": "openid-connect" + } + EOF + serviceAccountName: demo-serviceaccount + restartPolicy: OnFailure + backoffLimit: 20 # give some time for the Keycloak to be available diff --git a/stacks/gaia-x-security/setup-superset.yaml b/stacks/gaia-x-security/setup-superset.yaml new file mode 100644 index 00000000..a044868d --- /dev/null +++ b/stacks/gaia-x-security/setup-superset.yaml @@ -0,0 +1,131 @@ +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: setup-superset +spec: + template: + spec: + containers: + - name: setup-superset + image: docker.stackable.tech/stackable/testing-tools:0.1.0-stackable0.1.0 + command: ["bash", "-c", "curl -o superset-assets.zip https://raw.githubusercontent.com/stackabletech/demos/aas-demo/demos/gaia-x-security/superset-assets.zip && python -u /tmp/script/script.py"] + volumeMounts: + - name: script + mountPath: /tmp/script + - name: trino-superset-user + mountPath: /trino-superset-user + - name: superset-credentials + mountPath: /superset-credentials + volumes: + - name: script + configMap: + name: setup-superset-script + - name: superset-credentials + secret: + secretName: superset-credentials + - name: trino-superset-user + secret: + secretName: trino-superset-user + restartPolicy: OnFailure + backoffLimit: 50 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: setup-superset-script +data: + script.py: | + #!/usr/bin/env python + import logging + import requests + import json + + base_url = "http://superset-external:8088" + superset_username = open("/superset-credentials/adminUser.username").read() + superset_password = open("/superset-credentials/adminUser.password").read() + trino_connection_password = open("/trino-superset-user/superset").read() + + logging.basicConfig(level=logging.INFO) + logging.info("Starting setup of Superset") + + logging.info("Getting access token from /api/v1/security/login") + session = requests.session() + access_token = session.post(f"{base_url}/api/v1/security/login", json={"username": superset_username, "password": superset_password, "provider": "db", "refresh": True}).json()['access_token'] + # print(f"access_token: {access_token}") + + logging.info("Getting csrf token from /api/v1/security/csrf_token") + csrf_token = session.get(f"{base_url}/api/v1/security/csrf_token", headers={"Authorization": f"Bearer {access_token}"}).json()["result"] + # print(f"csrf_token: {csrf_token}") + + headers = { + "accept": "application/json", + "Authorization": f"Bearer {access_token}", + "X-CSRFToken": csrf_token, + } + + # # To retrieve all of the assets (datasources, datasets, charts and dashboards) run the following commands + # logging.info("Exporting all assets") + # result = session.get(f"{base_url}/api/v1/assets/export", headers=headers) + # assert result.status_code == 200 + # with open("superset-assets.zip", "wb") as f: + # f.write(result.content) + # exit(0) + + # import the zip file into superset + logging.info("Importing all assets") + files = { + "bundle": ("superset-assets.zip", open("superset-assets.zip", "rb")), + } + data = { + "passwords": '{"databases/Trino_TPCDS.yaml": "' + trino_connection_password + '"}' + } + + result = session.post(f"{base_url}/api/v1/assets/import", headers=headers, files=files, data=data) + assert result.status_code == 200, f"{result.status_code}: {result.content}" + + + # Now we must hit the API to set the DB Connection to `impersonate_user: true`, because the export and import doesn't allow for it + # See the incomplete PR: https://github.com/apache/superset/pull/14718 + + headers = headers | {"content-type": "application/json"} + + # todo: make this next part iterable so other demos can use the same script + db_connection_name = "Trino TPCDS" + query = { + "columns": ["id","database_name", "impersonate_user"], + "filters": [ + { + "col": "database_name", + "opr": "eq", + "value": db_connection_name + } + ], + "keys": ["none"] + } + + # Lookup the DB Connection to get the ID + result = session.get(f"{base_url}/api/v1/database", params={"q": json.dumps(query)}, headers=headers) + assert result.status_code == 200, f"{result.status_code}: {result.content}" + result = result.json() + assert result['count'] == 1, f"there should only be one result returned, but got {result['count']}" + result = result['result'][0] + assert result['database_name'] == db_connection_name, f"the superset search filter appears to be invalid, expected: db_connection_name, got: result['database_name']" # extra check to ensure we are looking at the right DB, since the API returns all if the filter has an invalid `opr` value. + trino_tpcds_db_id = result["id"] + logging.info(f"Got database connection id for '{db_connection_name}': {trino_tpcds_db_id}") + + # lookup result from above, since the search doesn't return `impersonate_user` + result = session.get(f"{base_url}/api/v1/database/{trino_tpcds_db_id}", headers=headers) + assert result.status_code == 200, f"{result.status_code}: {result.content}" + result = result.json() + impersonate_user = result["result"]["impersonate_user"] + logging.info(f"The value of impersonate_user for '{db_connection_name}' was set to {impersonate_user}") + + # Enable impersonation (this is done as the superset admin user) + result = session.put(f"{base_url}/api/v1/database/{trino_tpcds_db_id}", headers=headers, data=json.dumps({"impersonate_user": True})) + assert result.status_code == 200, f"{result.status_code}: {result.content}" + result = result.json() + impersonate_user = result["result"]["impersonate_user"] + logging.info(f"The value of impersonate_user for '{db_connection_name}' is now {impersonate_user}") + + logging.info("Finished setup of Superset") diff --git a/stacks/gaia-x-security/superset.yaml b/stacks/gaia-x-security/superset.yaml new file mode 100644 index 00000000..9e39ba68 --- /dev/null +++ b/stacks/gaia-x-security/superset.yaml @@ -0,0 +1,96 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: superset-credentials +type: Opaque +stringData: + adminUser.username: admin + adminUser.firstname: Superset + adminUser.lastname: Admin + adminUser.email: admin@superset.com + adminUser.password: will-not-be-used # as we use auth/oidc + connections.secretKey: {{ supersetSecretKey }} + connections.sqlalchemyDatabaseUri: postgresql://superset:superset@postgresql-superset/superset +--- +apiVersion: superset.stackable.tech/v1alpha1 +kind: SupersetCluster +metadata: + name: superset +spec: + image: + productVersion: 3.1.0 + stackableVersion: 0.0.0-dev + clusterConfig: + credentialsSecret: superset-credentials + listenerClass: external-unstable + authentication: + - authenticationClass: keycloak + oidc: + clientCredentialsSecret: superset-keycloak-client + userRegistrationRole: Alpha_extended + nodes: + roleGroups: + default: + replicas: 1 + podOverrides: + spec: + containers: + - name: create-default-role + image: docker.stackable.tech/stackable/superset:3.1.0-stackable0.0.0-dev + env: + - name: SECRET_KEY + valueFrom: + secretKeyRef: + key: connections.secretKey + name: superset-credentials + - name: SQLALCHEMY_DATABASE_URI + valueFrom: + secretKeyRef: + key: connections.sqlalchemyDatabaseUri + name: superset-credentials + volumeMounts: + - mountPath: /stackable/config + name: config + command: + - /bin/bash + - -c + - | + mkdir --parents /stackable/app/pythonpath && cp /stackable/config/* /stackable/app/pythonpath + python - << EOF + import socket + import time + + while not socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex(('localhost', 8088)) == 0: + print("Superset not up yet, sleeping ...") + time.sleep(1) + EOF + superset fab export-roles --path data.json + python - << EOF + import json + + with open('data.json') as f: + data = json.load(f) + + alpha_permissions = [obj['permissions'] for obj in data if obj['name'] == 'Alpha'][0] + sql_lab_permissions = [obj['permissions'] for obj in data if obj['name'] == 'sql_lab'][0] + + alpha_extended = [{ + 'name': 'Alpha_extended', + 'permissions': alpha_permissions + sql_lab_permissions + }] + + with open('alpha_extended.json', 'w') as f: + json.dump(alpha_extended, f) + EOF + superset fab import-roles --path alpha_extended.json + echo "Role imported!" + sleep infinity +--- +apiVersion: v1 +kind: Secret +metadata: + name: superset-keycloak-client +stringData: + clientId: superset + clientSecret: "{{ keycloakSupersetClientSecret }}" diff --git a/stacks/gaia-x-security/trino-policies.yaml b/stacks/gaia-x-security/trino-policies.yaml new file mode 100644 index 00000000..b1cc6f47 --- /dev/null +++ b/stacks/gaia-x-security/trino-policies.yaml @@ -0,0 +1,154 @@ +{% raw %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: trino-policies + labels: + opa.stackable.tech/bundle: true +data: + trino_policies.rego: | + package trino_policies + + import rego.v1 + + policies := { + "catalogs": [ + { + "user": "admin", + "allow": "all", + }, + { + "group": "Onlineshop24", + "allow": "all", + }, + { + "group": "AwesomeAnalytics|SwiftShip", + "catalog": "tpcds", + "allow": "read-only", + }, + ], + "queries": [ + { + "user": "admin", + "allow": ["execute"], + }, + { + "group": "Onlineshop24", + "allow": ["execute", "kill", "view"], + }, + { + "group": "AwesomeAnalytics|SwiftShip", + "allow": ["execute", "view"], + }, + ], + "schemas": [ + { + "group": "Onlineshop24", + "owner": true, + }, + { + "group": "AwesomeAnalytics|SwiftShip", + "catalog": "tpcds", + "schema": "sf1", + }, + ], + "tables": [ + { + "group": "Onlineshop24", + "privileges": [ + "SELECT", + "INSERT", + "DELETE", + "UPDATE", + "OWNERSHIP", + "GRANT_SELECT", + ], + }, + { + "group": "AwesomeAnalytics", + "catalog": "tpcds", + "schema": "sf1", + "table": "household_demographics|customer_demographics|promotion|web_sales|catalog_sales|store_sales", + "privileges": [ + "SELECT" + ], + }, + { + "group": "SwiftShip", + "catalog": "tpcds", + "schema": "sf1", + "table": "customer_address|warehouse|web_sales|ship_mode", + "privileges": [ + "SELECT" + ], + }, + ], + "system_information": [ + { + "group": "Onlineshop24", + "allow": ["read", "write"], + }, + ], + "catalog_session_properties": [ + { + "group": "Onlineshop24", + "allow": true, + }, + ], + "system_session_properties": [ + { + "group": "Onlineshop24", + "allow": true, + }, + ], + "impersonation": [ + { + "original_user": "superset", + "new_user": ".*", + "allow": true, + }, + ], + "authorization": [{ + "original_user": "superset", + "new_user": ".*", + "allow": true, + }], + "functions": [{ + "group": "Onlineshop24", + "catalog": ".*", + "schema": ".*", + "function": ".*", + "privileges": [ + "EXECUTE", + "GRANT_EXECUTE", + "OWNERSHIP", + ], + }], + "procedures": [{ + "group": "Onlineshop24", + "catalog": ".*", + "schema": ".*", + "procedure": ".*", + "privileges": [ + "EXECUTE", + "GRANT_EXECUTE", + ], + }], + } + + # we model company association as an extra group + extra_groups := groups if { + request := { + "method": "POST", + "url": "http://127.0.0.1:9476/user", + "headers": {"Content-Type": "application/json"}, + "body": {"id": input.context.identity.user}, + } + response := http.send(request) + + response.status_code == 200 + + groups := [response.body.customAttributes.company] + } +{% endraw %} diff --git a/stacks/gaia-x-security/trino-regorules.yaml b/stacks/gaia-x-security/trino-regorules.yaml new file mode 100644 index 00000000..373ad027 --- /dev/null +++ b/stacks/gaia-x-security/trino-regorules.yaml @@ -0,0 +1,1513 @@ +{% raw %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: trino-regorules + labels: + opa.stackable.tech/bundle: true +data: + util.rego: | + package util + + import rego.v1 + + match_entire(pattern, value) if { + # Add the anchors ^ and $ + pattern_with_anchors := concat("", ["^", pattern, "$"]) + + regex.match(pattern_with_anchors, value) + } + + verification.rego: | + # METADATA + # schemas: + # - input: schema.input + # - data.trino_policies.policies: schema.policies + package trino + + import rego.v1 + + # METADATA + # description: Comparison of requested and actual permissions + # entrypoint: true + default allow := false + + allow if { + # Fail if the requested permissions for the given operation are not + # implemented yet + # + # The following operations are intentionally not supported: + # - CreateCatalog + # - DropCatalog + requested_permissions + + every requested_permission in requested_authorization_permissions { + permission := authorization_permission(requested_permission.granteeName) + requested_permission.allow == permission + } + every requested_permission in requested_catalog_permissions { + access := catalog_access(requested_permission.catalogName) + requested_permission.allow in access + } + every requested_permission in requested_catalog_session_properties_permissions { + access := catalog_session_properties_access( + requested_permission.catalogName, + requested_permission.propertyName, + ) + requested_permission.allow == access + } + every requested_permission in requested_catalog_visibility_permissions { + catalog_visibility(requested_permission.catalogName) + } + every requested_permission in requested_column_permissions { + access := column_access( + requested_permission.catalogName, + requested_permission.schemaName, + requested_permission.tableName, + requested_permission.columnName, + ) + requested_permission.allow == access + } + every requested_permission in requested_function_permissions { + privileges := function_privileges( + requested_permission.catalogName, + requested_permission.schemaName, + requested_permission.functionName, + ) + object.subset(privileges, requested_permission.privileges) + } + every requested_permission in requested_impersonation_permissions { + access := impersonation_access(requested_permission.user) + requested_permission.allow == access + } + every requested_permission in requested_procedure_permissions { + privileges := procedure_privileges( + requested_permission.catalogName, + requested_permission.schemaName, + requested_permission.functionName, + ) + object.subset(privileges, requested_permission.privileges) + } + every requested_permission in requested_query_permissions { + object.subset(query_access, requested_permission.allow) + } + every requested_permission in requested_query_owned_by_permissions { + object.subset( + query_owned_by_access(requested_permission.user), + requested_permission.allow, + ) + } + every requested_permission in requested_schema_permissions { + schema_owner( + requested_permission.catalogName, + requested_permission.schemaName, + ) == requested_permission.owner + } + every requested_permission in requested_schema_visibility_permissions { + schema_visibility( + requested_permission.catalogName, + requested_permission.schemaName, + ) + } + every requested_permission in requested_table_permissions { + privileges := table_privileges( + requested_permission.catalogName, + requested_permission.schemaName, + requested_permission.tableName, + ) + all_of_requested := object.get( + requested_permission.privileges, + "allOf", + set(), + ) + any_of_requested := object.get( + requested_permission.privileges, + "anyOf", + privileges, + ) + object.subset(privileges, all_of_requested) + privileges & any_of_requested != set() + } + every requested_permission in requested_system_information_permissions { + object.subset( + system_information_access, + requested_permission.allow, + ) + } + every requested_permission in requested_system_session_properties_permissions { + access := system_session_properties_access(requested_permission.propertyName) + requested_permission.allow == access + } + } + + # METADATA + # description: Comparison of requested and actual permissions + # entrypoint: true + batch contains index if { + input.action.operation != "FilterColumns" + + some index, resource in input.action.filterResources + allow with input.action.resource as resource + } + + batch contains index if { + input.action.operation == "FilterColumns" + + table := input.action.filterResources[0].table + some index, column_name in table.columns + + allow with input.action.resource as {"table": { + "catalogName": table.catalogName, + "schemaName": table.schemaName, + "tableName": table.tableName, + "columnName": column_name, + }} + } + + # METADATA + # description: Column mask for a given column + # entrypoint: true + columnMask := column_mask if { + column := column_constraints( + requested_column_mask.catalogName, + requested_column_mask.schemaName, + requested_column_mask.tableName, + requested_column_mask.columnName, + ) + + is_string(column.mask) + is_string(column.mask_environment.user) + + column_mask := { + "expression": column.mask, + "identity": column.mask_environment.user, + } + } + + columnMask := column_mask if { + column := column_constraints( + requested_column_mask.catalogName, + requested_column_mask.schemaName, + requested_column_mask.tableName, + requested_column_mask.columnName, + ) + + is_string(column.mask) + is_null(column.mask_environment.user) + + column_mask := {"expression": column.mask} + } + + # METADATA + # description: Row filters for a given table + # entrypoint: true + rowFilters := row_filter if { + rule := first_matching_table_rule( + requested_row_filters.catalogName, + requested_row_filters.schemaName, + requested_row_filters.tableName, + ) + + is_string(rule.filter) + is_string(rule.filter_environment.user) + + row_filter := { + "expression": rule.filter, + "identity": rule.filter_environment.user, + } + } + + rowFilters := row_filter if { + rule := first_matching_table_rule( + requested_row_filters.catalogName, + requested_row_filters.schemaName, + requested_row_filters.tableName, + ) + + is_string(rule.filter) + is_null(rule.filter_environment.user) + + row_filter := {"expression": rule.filter} + } + + requested_permissions.rego: | + package trino + + import rego.v1 + + action := input.action + + operation := action.operation + + requested_permissions := permissions if { + operation == "AccessCatalog" + permissions := {{ + "resource": "catalog", + "catalogName": action.resource.catalog.name, + "allow": "read-only", + }} + } + + requested_permissions := permissions if { + operation in { + "CreateSchema", + "DropSchema", + "ShowCreateSchema", + } + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.schema.catalogName, + "allow": "all", + }, + { + "resource": "schema", + "catalogName": action.resource.schema.catalogName, + "schemaName": action.resource.schema.schemaName, + "owner": true, + }, + } + } + + requested_permissions := permissions if { + operation in { + "AddColumn", + "AlterColumn", + "CreateMaterializedView", + "CreateTable", + "CreateView", + "CreateViewWithSelectFromColumns", + "DropColumn", + "DropMaterializedView", + "DropTable", + "DropView", + "RenameColumn", + "SetColumnComment", + "SetMaterializedViewProperties", + "SetTableComment", + "SetTableProperties", + "SetViewComment", + "ShowCreateTable", + } + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.table.catalogName, + "allow": "all", + }, + { + "resource": "table", + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + "privileges": {"allOf": {"OWNERSHIP"}}, + }, + } + } + + requested_permissions := permissions if { + operation in { + "RefreshMaterializedView", + "UpdateTableColumns", + } + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.table.catalogName, + "allow": "all", + }, + { + "resource": "table", + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + "privileges": {"allOf": {"UPDATE"}}, + }, + } + } + + requested_permissions := permissions if { + operation in { + "DeleteFromTable", + "TruncateTable", + } + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.table.catalogName, + "allow": "all", + }, + { + "resource": "table", + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + "privileges": {"allOf": {"DELETE"}}, + }, + } + } + + requested_permissions := permissions if { + operation == "ExecuteQuery" + permissions := {{ + "resource": "query", + "allow": {"execute"}, + }} + } + + requested_permissions := permissions if { + operation == "ExecuteTableProcedure" + + # Executing table procedures is always allowed + permissions := set() + } + + requested_permissions := permissions if { + operation == "FilterColumns" + permissions := { + { + "resource": "table", + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + "privileges": {"anyOf": { + "SELECT", + "INSERT", + "DELETE", + "UPDATE", + "OWNERSHIP", + "GRANT_SELECT", + }}, + }, + { + "resource": "column", + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + "columnName": action.resource.table.columnName, + "allow": true, + }, + } + } + + requested_permissions := permissions if { + operation == "KillQueryOwnedBy" + permissions := {{ + "resource": "query_owned_by", + "user": action.resource.user.user, + "groups": action.resource.user.groups, + "allow": {"kill"}, + }} + } + + requested_permissions := permissions if { + operation in { + "FilterViewQueryOwnedBy", + "ViewQueryOwnedBy", + } + permissions := {{ + "resource": "query_owned_by", + "user": action.resource.user.user, + "groups": action.resource.user.groups, + "allow": {"view"}, + }} + } + + requested_permissions := permissions if { + operation == "FilterTables" + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.table.catalogName, + "allow": "read-only", + }, + { + "resource": "table", + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + "privileges": {"anyOf": { + "SELECT", + "INSERT", + "DELETE", + "UPDATE", + "OWNERSHIP", + "GRANT_SELECT", + }}, + }, + } + } + + requested_permissions := permissions if { + operation in { + "CreateFunction", + "DropFunction", + } + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.function.catalogName, + "allow": "all", + }, + { + "resource": "function", + "catalogName": action.resource.function.catalogName, + "schemaName": action.resource.function.schemaName, + "functionName": action.resource.function.functionName, + "privileges": {"OWNERSHIP"}, + }, + } + } + + requested_permissions := permissions if { + operation in { + "ExecuteFunction", + "FilterFunctions", + } + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.function.catalogName, + "allow": "read-only", + }, + { + "resource": "function", + "catalogName": action.resource.function.catalogName, + "schemaName": action.resource.function.schemaName, + "functionName": action.resource.function.functionName, + "privileges": {"EXECUTE"}, + }, + } + } + + requested_permissions := permissions if { + operation == "ExecuteProcedure" + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.function.catalogName, + "allow": "read-only", + }, + { + "resource": "procedure", + "catalogName": action.resource.function.catalogName, + "schemaName": action.resource.function.schemaName, + "functionName": action.resource.function.functionName, + "privileges": {"EXECUTE"}, + }, + } + } + + requested_permissions := permissions if { + operation == "CreateViewWithExecuteFunction" + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.function.catalogName, + "allow": "read-only", + }, + { + "resource": "function", + "catalogName": action.resource.function.catalogName, + "schemaName": action.resource.function.schemaName, + "functionName": action.resource.function.functionName, + "privileges": {"GRANT_EXECUTE"}, + }, + } + } + + requested_permissions := permissions if { + operation == "ImpersonateUser" + permissions := {{ + "resource": "impersonation", + "user": action.resource.user.user, + "allow": true, + }} + } + + requested_permissions := permissions if { + operation == "InsertIntoTable" + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.table.catalogName, + "allow": "all", + }, + { + "resource": "table", + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + "privileges": {"allOf": {"INSERT"}}, + }, + } + } + + requested_permissions := permissions if { + operation == "ReadSystemInformation" + permissions := {{ + "resource": "system_information", + "allow": {"read"}, + }} + } + + requested_permissions := permissions if { + operation == "RenameSchema" + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.schema.catalogName, + "allow": "all", + }, + { + "resource": "catalog", + "catalogName": action.targetResource.schema.catalogName, + "allow": "all", + }, + { + "resource": "schema", + "catalogName": action.resource.schema.catalogName, + "schemaName": action.resource.schema.schemaName, + "owner": true, + }, + { + "resource": "schema", + "catalogName": action.targetResource.schema.catalogName, + "schemaName": action.targetResource.schema.schemaName, + "owner": true, + }, + } + } + + requested_permissions := permissions if { + operation in { + "RenameMaterializedView", + "RenameTable", + "RenameView", + } + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.table.catalogName, + "allow": "all", + }, + { + "resource": "catalog", + "catalogName": action.targetResource.table.catalogName, + "allow": "all", + }, + { + "resource": "table", + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + "privileges": {"allOf": {"OWNERSHIP"}}, + }, + { + "resource": "table", + "catalogName": action.targetResource.table.catalogName, + "schemaName": action.targetResource.table.schemaName, + "tableName": action.targetResource.table.tableName, + "privileges": {"allOf": {"OWNERSHIP"}}, + }, + } + } + + requested_permissions := permissions if { + operation == "SelectFromColumns" + column_permissions := { + { + "resource": "column", + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + "columnName": column_name, + "allow": true, + } | + some column_name in action.resource.table.columns + } + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.table.catalogName, + "allow": "read-only", + }, + { + "resource": "table", + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + "privileges": {"allOf": {"SELECT"}}, + }, + } | column_permissions + } + + requested_permissions := permissions if { + operation == "SetSchemaAuthorization" + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.schema.catalogName, + "allow": "all", + }, + { + "resource": "schema", + "catalogName": action.resource.schema.catalogName, + "schemaName": action.resource.schema.schemaName, + "owner": true, + }, + { + "resource": "authorization", + "granteeName": action.grantee.name, + "granteeType": action.grantee.type, + "allow": true, + }, + } + } + + requested_permissions := permissions if { + operation in { + "SetTableAuthorization", + "SetViewAuthorization", + } + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.table.catalogName, + "allow": "all", + }, + { + "resource": "table", + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + "privileges": {"allOf": {"OWNERSHIP"}}, + }, + { + "resource": "authorization", + "granteeName": action.grantee.name, + "granteeType": action.grantee.type, + "allow": true, + }, + } + } + + requested_permissions := permissions if { + operation == "ShowColumns" + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.table.catalogName, + "allow": "read-only", + }, + { + "resource": "table", + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + "privileges": {"anyOf": { + "SELECT", + "INSERT", + "DELETE", + "UPDATE", + "OWNERSHIP", + "GRANT_SELECT", + }}, + }, + } + } + + requested_permissions := permissions if { + operation in { + "FilterCatalogs", + "ShowSchemas", + } + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.catalog.name, + "allow": "read-only", + }, + { + "resource": "catalog_visibility", + "catalogName": action.resource.catalog.name, + }, + } + } + + requested_permissions := permissions if { + operation in { + "FilterSchemas", + "ShowFunctions", + "ShowTables", + } + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.schema.catalogName, + "allow": "read-only", + }, + { + "resource": "schema_visibility", + "catalogName": action.resource.schema.catalogName, + "schemaName": action.resource.schema.schemaName, + }, + } + } + + requested_permissions := permissions if { + operation == "SetCatalogSessionProperty" + permissions := { + { + "resource": "catalog", + "catalogName": action.resource.catalogSessionProperty.catalogName, + "allow": "read-only", + }, + { + "resource": "catalog_session_properties", + "catalogName": action.resource.catalogSessionProperty.catalogName, + "propertyName": action.resource.catalogSessionProperty.propertyName, + "allow": true, + }, + } + } + + requested_permissions := permissions if { + operation == "SetSystemSessionProperty" + permissions := {{ + "resource": "system_session_properties", + "propertyName": action.resource.systemSessionProperty.name, + "allow": true, + }} + } + + requested_permissions := permissions if { + operation == "WriteSystemInformation" + permissions := {{ + "resource": "system_information", + "allow": {"write"}, + }} + } + + requested_authorization_permissions contains permission if { + some permission in requested_permissions + permission.resource == "authorization" + } + + requested_catalog_permissions contains permission if { + some permission in requested_permissions + permission.resource == "catalog" + } + + requested_catalog_session_properties_permissions contains permission if { + some permission in requested_permissions + permission.resource == "catalog_session_properties" + } + + requested_catalog_visibility_permissions contains permission if { + some permission in requested_permissions + permission.resource == "catalog_visibility" + } + + requested_column_permissions contains permission if { + some permission in requested_permissions + permission.resource == "column" + } + + requested_function_permissions contains permission if { + some permission in requested_permissions + permission.resource == "function" + } + + requested_impersonation_permissions contains permission if { + some permission in requested_permissions + permission.resource == "impersonation" + } + + requested_procedure_permissions contains permission if { + some permission in requested_permissions + permission.resource == "procedure" + } + + requested_query_permissions contains permission if { + some permission in requested_permissions + permission.resource == "query" + } + + requested_query_owned_by_permissions contains permission if { + some permission in requested_permissions + permission.resource == "query_owned_by" + } + + requested_schema_permissions contains permission if { + some permission in requested_permissions + permission.resource == "schema" + } + + requested_schema_visibility_permissions contains permission if { + some permission in requested_permissions + permission.resource == "schema_visibility" + } + + requested_table_permissions contains permission if { + some permission in requested_permissions + permission.resource == "table" + } + + requested_system_information_permissions contains permission if { + some permission in requested_permissions + permission.resource == "system_information" + } + + requested_system_session_properties_permissions contains permission if { + some permission in requested_permissions + permission.resource == "system_session_properties" + } + + requested_column_mask := request if { + operation == "GetColumnMask" + request := { + "catalogName": action.resource.column.catalogName, + "schemaName": action.resource.column.schemaName, + "tableName": action.resource.column.tableName, + "columnName": action.resource.column.columnName, + } + } + + requested_row_filters := request if { + operation == "GetRowFilters" + request := { + "catalogName": action.resource.table.catalogName, + "schemaName": action.resource.table.schemaName, + "tableName": action.resource.table.tableName, + } + } + + actual_permissions.rego: | + package trino + + import data.util + import rego.v1 + + # These rules replicate the file-based access control + # (https://trino.io/docs/current/security/file-system-access-control.html#table-rules). + # + # But there are differences: + # * Only `user` and `group` are matched but not `role`. + # * The visibility is not checked. + + identity := input.context.identity + + policies := data.trino_policies.policies + + default extra_groups := [] + + extra_groups := data.trino_policies.extra_groups + + # Add an empty dummy group because the default pattern ".*" should match + # even if the user is not a member of a group. + groups := array.concat( + array.concat(identity.groups, extra_groups), + [""], + ) + + default match_any_group(_) := false + + match_any_group(group_pattern) if { + some group in groups + util.match_entire(group_pattern, group) + } + + default match_user_group(_) := false + + match_user_group(rule) if { + user_pattern := object.get(rule, "user", ".*") + group_pattern := object.get(rule, "group", ".*") + + util.match_entire(user_pattern, identity.user) + match_any_group(group_pattern) + } + + default match_original_user_group(_) := false + + match_original_user_group(rule) if { + user_pattern := object.get(rule, "original_user", ".*") + group_pattern := object.get(rule, "original_group", ".*") + + util.match_entire(user_pattern, identity.user) + match_any_group(group_pattern) + } + + default authorization_rules := [] + + authorization_rules := policies.authorization + + first_matching_authorization_rule(grantee_name) := rule if { + rules := [rule | + some rule in authorization_rules + + match_original_user_group(rule) + + new_user_pattern := object.get(rule, "new_user", ".*") + + util.match_entire(new_user_pattern, grantee_name) + ] + rule := object.union( + {"allow": true}, + rules[0], + ) + } + + # Authorization permission of the first matching rule + default authorization_permission(_) := false + + authorization_permission(grantee_name) := first_matching_authorization_rule(grantee_name).allow + + default catalog_rules := [{"allow": "all"}] + + catalog_rules := policies.catalogs + + first_matching_catalog_rule(catalog_name) := rule if { + rules := [rule | + some rule in catalog_rules + + match_user_group(rule) + + catalog_pattern := object.get(rule, "catalog", ".*") + + util.match_entire(catalog_pattern, catalog_name) + ] + rule := rules[0] + } + + catalog_access_map := { + "all": {"all", "read-only"}, + "read-only": {"read-only"}, + "none": {"none"}, + } + + # Catalog access of the first matching rule + default catalog_access(_) := {"none"} + + catalog_access(catalog_name) := catalog_access_map[first_matching_catalog_rule(catalog_name).allow] + + default catalog_session_properties_rules := [{"allow": true}] + + catalog_session_properties_rules := policies.catalog_session_properties + + first_matching_catalog_session_properties_rule( + catalog_name, + property_name, + ) := rule if { + rules := [rule | + some rule in catalog_session_properties_rules + + match_user_group(rule) + + catalog_pattern := object.get(rule, "catalog", ".*") + property_pattern := object.get(rule, "property", ".*") + + util.match_entire(catalog_pattern, catalog_name) + util.match_entire(property_pattern, property_name) + ] + rule := rules[0] + } + + # Catalog session property access of the first matching rule + default catalog_session_properties_access(_, _) := false + + catalog_session_properties_access( + catalog_name, + property_name, + ) := first_matching_catalog_session_properties_rule( + catalog_name, + property_name, + ).allow + + default function_rules := [{ + "catalog": "system", + "schema": "builtin", + "privileges": [ + "GRANT_EXECUTE", + "EXECUTE", + ], + }] + + default catalog_visibility(_) := false + + catalog_visibility(catalog_name) if { + "all" in catalog_access(catalog_name) + } + + catalog_visibility(catalog_name) if { + catalog_access(catalog_name) == {"read-only"} + + some rule in schema_rules + + match_user_group(rule) + + catalog_pattern := object.get(rule, "catalog", ".*") + + util.match_entire(catalog_pattern, catalog_name) + + rule.owner == true + } + + catalog_visibility(catalog_name) if { + catalog_access(catalog_name) == {"read-only"} + + rules := array.concat( + array.concat( + table_rules, + function_rules, + ), + procedure_rules, + ) + + some rule in rules + + match_user_group(rule) + + catalog_pattern := object.get(rule, "catalog", ".*") + + util.match_entire(catalog_pattern, catalog_name) + + count(rule.privileges) != 0 + } + + catalog_visibility(catalog_name) if { + catalog_access(catalog_name) == {"read-only"} + + some rule in catalog_session_properties_rules + + match_user_group(rule) + + catalog_pattern := object.get(rule, "catalog", ".*") + + util.match_entire(catalog_pattern, catalog_name) + + rule.allow == true + } + + function_rules := policies.functions + + first_matching_function_rule( + catalog_name, + schema_name, + function_name, + ) := rule if { + rules := [rule | + some rule in function_rules + + match_user_group(rule) + + catalog_pattern := object.get(rule, "catalog", ".*") + schema_pattern := object.get(rule, "schema", ".*") + function_pattern := object.get(rule, "function", ".*") + + util.match_entire(catalog_pattern, catalog_name) + util.match_entire(schema_pattern, schema_name) + util.match_entire(function_pattern, function_name) + ] + rule := rules[0] + } + + # Function privileges of the first matching rule + default function_privileges(_, _, _) := set() + + function_privileges( + catalog_name, + schema_name, + function_name, + ) := {privilege | + some privilege in first_matching_function_rule( + catalog_name, + schema_name, + function_name, + ).privileges + } + + default impersonation_rules := [] + + impersonation_rules := policies.impersonation + + first_matching_impersonation_rule(user) := rule if { + rules := [rule | + some rule in impersonation_rules + + match_original_user_group(rule) + + original_user_pattern := object.get(rule, "original_user", ".*") + unsubstituted_new_user_pattern := object.get(rule, "new_user", ".*") + + matches := regex.find_all_string_submatch_n( + original_user_pattern, + identity.user, -1, + ) + substitutes := {var: match | + some i, match in matches[0] + var := concat("", ["$", format_int(i, 10)]) + } + + # strings.replace_n replaces "$10" with "$1" followed by "0". + # Therefore only nine capture groups are supported. + new_user_pattern := strings.replace_n( + substitutes, + unsubstituted_new_user_pattern, + ) + + util.match_entire(new_user_pattern, user) + ] + rule := object.union( + {"allow": true}, + rules[0], + ) + } + + # Impersonation access of the first matching rule + default impersonation_access(_) := false + + impersonation_access(user) if { + user == identity.user + } + + impersonation_access(user) := access if { + user != identity.user + access := first_matching_impersonation_rule(user).allow + } + + default procedure_rules := [{ + "catalog": "system", + "schema": "builtin", + "privileges": [ + "GRANT_EXECUTE", + "EXECUTE", + ], + }] + + procedure_rules := policies.procedures + + # Matching the "function name" with the "procedure pattern" is intended. + # The requested procedure name is contained in + # `input.action.resource.function.functionName`. A rule applies if this + # name matches the pattern in + # `data.trino_policies.policies.procedures[_].procedure`. + first_matching_procedure_rule( + catalog_name, + schema_name, + function_name, + ) := rule if { + rules := [rule | + some rule in procedure_rules + + match_user_group(rule) + + catalog_pattern := object.get(rule, "catalog", ".*") + schema_pattern := object.get(rule, "schema", ".*") + procedure_pattern := object.get(rule, "procedure", ".*") + + util.match_entire(catalog_pattern, catalog_name) + util.match_entire(schema_pattern, schema_name) + util.match_entire(procedure_pattern, function_name) + ] + rule := rules[0] + } + + # Procedure privileges of the first matching rule + default procedure_privileges(_, _, _) := set() + + procedure_privileges( + catalog_name, + schema_name, + function_name, + ) := {privilege | + some privilege in first_matching_procedure_rule( + catalog_name, + schema_name, + function_name, + ).privileges + } + + default query_rules := [{"allow": ["execute", "kill", "view"]}] + + query_rules := policies.queries + + first_matching_query_rule := rule if { + rules := [rule | + some rule in query_rules + + match_user_group(rule) + ] + rule := rules[0] + } + + # Query access of the first matching rule + default query_access := set() + + query_access := {access | some access in first_matching_query_rule.allow} + + first_matching_query_owned_by_rule(user) := rule if { + rules := [rule | + some rule in query_rules + + match_user_group(rule) + + query_owner_pattern := object.get(rule, "queryOwner", ".*") + + util.match_entire(query_owner_pattern, user) + ] + rule := rules[0] + } + + # Query access of the first matching rule + default query_owned_by_access(_) := set() + + query_owned_by_access(user) := {"kill", "view"} if { + user == identity.user + } + + query_owned_by_access(user) := access if { + user != identity.user + access := {access | + some access in first_matching_query_owned_by_rule(user).allow + } + } + + default schema_rules := [{"owner": true}] + + schema_rules := policies.schemas + + first_matching_schema_rule(catalog_name, schema_name) := rule if { + rules := [rule | + some rule in schema_rules + + match_user_group(rule) + + catalog_pattern := object.get(rule, "catalog", ".*") + schema_pattern := object.get(rule, "schema", ".*") + + util.match_entire(catalog_pattern, catalog_name) + util.match_entire(schema_pattern, schema_name) + ] + rule := rules[0] + } + + # Schema ownership of the first matching rule + default schema_owner(_, _) := false + + schema_owner(catalog_name, schema_name) := first_matching_schema_rule( + catalog_name, + schema_name, + ).owner + + default schema_visibility(_, _) := false + + schema_visibility(catalog_name, schema_name) if { + schema_owner(catalog_name, schema_name) + } + + schema_visibility(_, "information_schema") := true + + schema_visibility(catalog_name, schema_name) if { + schema_name != "information_schema" + + rules := array.concat( + array.concat( + table_rules, + function_rules, + ), + procedure_rules, + ) + + some rule in rules + + match_user_group(rule) + + catalog_pattern := object.get(rule, "catalog", ".*") + schema_pattern := object.get(rule, "schema", ".*") + + util.match_entire(catalog_pattern, catalog_name) + util.match_entire(schema_pattern, schema_name) + + count(rule.privileges) != 0 + } + + default table_rules := [{ + "privileges": [ + "DELETE", + "GRANT_SELECT", + "INSERT", + "OWNERSHIP", + "SELECT", + "UPDATE", + ], + "filter": null, + "filter_environment": {"user": null}, + }] + + table_rules := policies.tables + + first_matching_table_rule(_, "information_schema", _) := { + "schema": "information_schema", + "privileges": [ + "DELETE", + "GRANT_SELECT", + "INSERT", + "OWNERSHIP", + "SELECT", + "UPDATE", + ], + "filter": null, + "filter_environment": {"user": null}, + } + + first_matching_table_rule( + catalog_name, + schema_name, + table_name, + ) := rule if { + schema_name != "information_schema" + rules := [rule | + some rule in table_rules + + match_user_group(rule) + + catalog_pattern := object.get(rule, "catalog", ".*") + schema_pattern := object.get(rule, "schema", ".*") + table_pattern := object.get(rule, "table", ".*") + + util.match_entire(catalog_pattern, catalog_name) + util.match_entire(schema_pattern, schema_name) + util.match_entire(table_pattern, table_name) + ] + rule := object.union( + { + "filter": null, + "filter_environment": {"user": null}, + }, + rules[0], + ) + } + + default column_constraints(_, _, _, _) := { + "allow": true, + "mask": null, + "mask_environment": {"user": null}, + } + + column_constraints(_, "information_schema", _, _) := { + "allow": true, + "mask": null, + "mask_environment": {"user": null}, + } + + column_constraints( + catalog_name, + schema_name, + table_name, + column_name, + ) := constraints if { + schema_name != "information_schema" + + rule := first_matching_table_rule( + catalog_name, + schema_name, + table_name, + ) + + some column in rule.columns + column.name == column_name + + constraints := object.union( + { + "allow": true, + "mask": null, + "mask_environment": {"user": null}, + }, + column, + ) + } + + # Table privileges of the first matching rule + default table_privileges(_, _, _) := set() + + table_privileges( + catalog_name, + schema_name, + table_name, + ) := {privilege | + some privilege in first_matching_table_rule( + catalog_name, + schema_name, + table_name, + ).privileges + } + + # Column access of the first matching rule + default column_access(_, _, _, _) := false + + column_access( + catalog_name, + schema_name, + table_name, + column_name, + ) := access if { + table_privileges( + catalog_name, + schema_name, + table_name, + ) != set() + + column := column_constraints( + catalog_name, + schema_name, + table_name, + column_name, + ) + + access := column.allow + } + + default system_information_rules := [] + + system_information_rules := policies.system_information + + first_matching_system_information_rule := rule if { + rules := [rule | + some rule in system_information_rules + + match_user_group(rule) + ] + rule := rules[0] + } + + # System information access of the first matching rule + default system_information_access := set() + + system_information_access := {access | + some access in first_matching_system_information_rule.allow + } + + default system_session_properties_rules := [{"allow": true}] + + system_session_properties_rules := policies.system_session_properties + + first_matching_system_session_properties_rule(property_name) := rule if { + rules := [rule | + some rule in system_session_properties_rules + + match_user_group(rule) + + property_name_pattern := object.get(rule, "property", ".*") + + util.match_entire(property_name_pattern, property_name) + ] + rule := rules[0] + } + + # System session property access of the first matching rule + default system_session_properties_access(_) := false + + system_session_properties_access(property_name) := first_matching_system_session_properties_rule(property_name).allow +{% endraw %} diff --git a/stacks/gaia-x-security/trino.yaml b/stacks/gaia-x-security/trino.yaml new file mode 100644 index 00000000..d753c16f --- /dev/null +++ b/stacks/gaia-x-security/trino.yaml @@ -0,0 +1,80 @@ +--- +apiVersion: trino.stackable.tech/v1alpha1 +kind: TrinoCluster +metadata: + name: trino +spec: + image: + productVersion: "442" + stackableVersion: 0.0.0-dev + clusterConfig: + listenerClass: external-unstable + tls: + serverSecretClass: tls + catalogLabelSelector: + matchLabels: + trino: trino + authentication: + - authenticationClass: trino-superset-user-for-impersonation + - authenticationClass: keycloak + oidc: + clientCredentialsSecret: trino-keycloak-client + authorization: + opa: + configMapName: opa + package: trino + coordinators: + roleGroups: + default: + replicas: 1 + config: + logging: + containers: + trino: + loggers: + io.trino: + level: DEBUG + configOverrides: + config.properties: + http-server.authentication.oauth2.refresh-tokens: "true" + http-server.authentication.oauth2.principal-field: sub + workers: + roleGroups: + default: + replicas: 1 +--- +apiVersion: trino.stackable.tech/v1alpha1 +kind: TrinoCatalog +metadata: + name: tpcds + labels: + trino: trino +spec: + connector: + tpcds: {} +--- +apiVersion: v1 +kind: Secret +metadata: + name: trino-keycloak-client +stringData: + clientId: trino + clientSecret: "{{ keycloakTrinoClientSecret }}" +--- +apiVersion: authentication.stackable.tech/v1alpha1 +kind: AuthenticationClass +metadata: + name: trino-superset-user-for-impersonation +spec: + provider: + static: + userCredentialsSecret: + name: trino-superset-user +--- +apiVersion: v1 +kind: Secret +metadata: + name: trino-superset-user +type: kubernetes.io/opaque +stringData: + superset: "{{ trinoSupersetUserPassword }}" \ No newline at end of file diff --git a/stacks/gaia-x-security/tsa-mock.yaml b/stacks/gaia-x-security/tsa-mock.yaml new file mode 100644 index 00000000..46a7a7ec --- /dev/null +++ b/stacks/gaia-x-security/tsa-mock.yaml @@ -0,0 +1,259 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: tsa-config +data: + tsa.py: | + from http.server import BaseHTTPRequestHandler, HTTPServer + from urllib.parse import urlparse, parse_qs + import json + import uuid + import sys + import cgi + import io + + login_form = """ + + + + + + Login + + +
+

Accept login request

+

request ID: "{}"

+

This step would usually be done in the PCM/OCM

+
+ + + +

+ +
+
+ + + """ + + users = { + 'Dave Dataowner (Onlineshop24 GmbH)': { + 'sub': 'dave1', + 'preferred_username': 'dave1', + 'name': 'Dave', + 'family_name': 'Dataowner', + 'company': 'Onlineshop24', + 'email': 'dave@onlineshop24.com', + }, + 'Alice Customer Analytics (AwesomeAnalytics Ltd)': { + 'sub': 'alice2', + 'preferred_username': 'alice2', + 'name': 'Alice', + 'family_name': 'Analytics', + 'company': 'AwesomeAnalytics', + 'email': 'alice@awesome-analytics.com', + }, + 'Sam Shipper (SwiftShip AG)': { + 'sub': 'sam3', + 'preferred_username': 'sam3', + 'name': 'Sam', + 'family_name': 'Shipper', + 'company': 'SwiftShip', + 'email': 'sam@swiftship.com', + } + } + + REQUESTS = {'a': 'b'} + + def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + + def parse_form_data(request_body): + form = cgi.FieldStorage( + fp=io.BytesIO(request_body), + environ={'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': 'application/x-www-form-urlencoded'} + ) + + parsed_data = {} + for field in form.keys(): + parsed_data[field] = form[field].value + + return parsed_data + + class RequestHandler(BaseHTTPRequestHandler): + def do_GET(self): + parsed_url = urlparse(self.path) + query_components = parse_qs(parsed_url.query) + if parsed_url.path == '/health': + self.send_response(200) + self.end_headers() + self.wfile.write("Alles tschaka\n".encode('utf-8')) + elif parsed_url.path.startswith('/requests'): + request_id = parsed_url.query.split('=')[1] + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + usernames_options = "\n".join([f"" for k, v in users.items()]) + self.wfile.write(login_form.format(request_id, usernames_options, request_id).encode()) + self.print_request() + elif parsed_url.path.startswith('/redirect'): + request_id = parsed_url.query.split('=')[1] + self.send_response(302) + self.send_header('Location', f"http://localhost:5000/requests?requestId={request_id}") + self.end_headers() + elif parsed_url.path == '/cip/claims': + sub = query_components.get('sub', [''])[0] + scope = query_components.get('scope', [''])[0] + + if not sub or not scope: + self.send_response(400) + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps({'error': 'Both "sub" and "scope" parameters are required.'}).encode('utf-8')) + return + + claims = {} + for user in users.values(): + if user['sub'] == sub: + claims = user + break + + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps(claims).encode('utf-8')) + else: + self.send_response(404) + self.end_headers() + self.wfile.write(json.dumps({'error': 'Endpoint not found'}).encode('utf-8')) + + def do_POST(self): + if self.path == '/policy/example/GetLoginProofInvitation/1.0/evaluation': + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + content_length = int(self.headers['Content-Length']) + post_data = self.rfile.read(content_length) + self.print_request(post_data) + request_id = f"request-{uuid.uuid4()}" + response_body = json.dumps({ + 'link': f"http://tsa:5000/redirect?requestId={request_id}", + 'requestId': request_id, + 'status': 'pending', + }) + self.wfile.write(response_body.encode('utf-8')) + REQUESTS[request_id] = { + 'requestId': request_id, + 'status': 'pending' + } + + elif self.path == '/policy/example/GetLoginProofResult/1.0/evaluation': + content_length = int(self.headers['Content-Length']) + post_data = self.rfile.read(content_length) + self.print_request(post_data) + request_body = json.loads(post_data) + request_id = request_body['requestId'] + if REQUESTS[request_id]['status'] == 'pending': + self.send_response(204) + else: + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + response_body = json.dumps(REQUESTS[request_id]) + self.wfile.write(response_body.encode('utf-8')) + + elif self.path == '/login': + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + content_length = int(self.headers['Content-Length']) + post_data = self.rfile.read(content_length) + self.print_request(post_data) + form_data = parse_form_data(post_data) + request_id = form_data['requestId'] + REQUESTS[request_id] = json.loads(form_data['claims']) + REQUESTS[request_id]['requestId'] = request_id + REQUESTS[request_id]['status'] = 'accepted' + REQUESTS[request_id]['scopes'] = 'openid profile email' + self.wfile.write(""" + Login succeeded, please wait until AAS notices...
+ This window will be closed shortly. + + + + + """.encode('utf-8')) + + def print_request(self, post_data=None): + eprint(f"Received {self.command} request for {self.path}") + if post_data: + eprint("Request body:") + eprint(post_data.decode('utf-8')) + eprint("=" * 40) + + def run_server(host='', port=5000): + server_address = (host, port) + httpd = HTTPServer(server_address, RequestHandler) + eprint(f"Server running on {host}:{port}") + httpd.serve_forever() + + if __name__ == "__main__": + run_server() +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tsa +spec: + replicas: 1 + selector: + matchLabels: + app: tsa + template: + metadata: + labels: + app: tsa + spec: + containers: + - name: python + image: python:3.9 + command: ["python", "/tsa.py"] + ports: + - containerPort: 5000 + resources: + limits: + cpu: 1 + memory: 512Mi + requests: + cpu: 500m + memory: 512Mi + volumeMounts: + - name: app-volume + mountPath: /tsa.py + subPath: tsa.py + volumes: + - name: app-volume + configMap: + name: tsa-config +--- +apiVersion: v1 +kind: Service +metadata: + name: tsa +spec: + selector: + app: tsa + ports: + - protocol: TCP + port: 5000 + targetPort: 5000 + type: ClusterIP diff --git a/stacks/stacks-v2.yaml b/stacks/stacks-v2.yaml index d1717654..1bb4fe4c 100644 --- a/stacks/stacks-v2.yaml +++ b/stacks/stacks-v2.yaml @@ -495,6 +495,72 @@ stacks: - name: supersetSecretKey description: Superset's secret key used to generate e.g. user session tokens default: supersetSecretKey + gaia-x-security: + description: >- + This demo showcases using Gaia-X SSI to log into superset and trino, together with OPA authorization. + stackableRelease: gaia-x-security-release + stackableOperators: + - commons + - secret + - trino + - superset + - opa + labels: + - authentication + - sso + manifests: + - helmChart: https://raw.githubusercontent.com/stackabletech/demos/main/stacks/_templates/postgresql-superset.yaml + - helmChart: stacks/gaia-x-security/postgres-aas.yaml + - plainYaml: stacks/gaia-x-security/serviceaccount.yaml + - plainYaml: stacks/gaia-x-security/keycloak.yaml + - plainYaml: stacks/gaia-x-security/keycloak-authenticationclass.yaml + - plainYaml: stacks/gaia-x-security/setup-keycloak.yaml + - plainYaml: stacks/gaia-x-security/opa.yaml + - plainYaml: stacks/gaia-x-security/trino.yaml + - plainYaml: stacks/gaia-x-security/trino-policies.yaml + - plainYaml: stacks/gaia-x-security/trino-regorules.yaml + - plainYaml: stacks/gaia-x-security/superset.yaml + - plainYaml: stacks/gaia-x-security/setup-superset.yaml + - plainYaml: stacks/gaia-x-security/aas.yaml + - plainYaml: stacks/gaia-x-security/gaia-x-realm.yaml + - plainYaml: stacks/gaia-x-security/tsa-mock.yaml + supportedNamespaces: ["default"] # ClusterRoleBinding needs explicit namespace + resourceRequests: + cpu: 7850m + memory: 23142Mi + pvc: 34Gi + parameters: + - name: alicePassword + description: Password of the user alice, which can log into Trino and Superset + default: alicealice + - name: bobPassword + description: Password of the user bob, which can log into Trino and Superset + default: bobbob + - name: keycloakAdminPassword + description: Password of the Keycloak admin user + default: adminadmin + - name: keycloakSupersetClientSecret + description: Secret ID of the Keycloak Superset client that is used by Superset to connect to Keycloak to authenticate users + default: supersetsuperset + - name: keycloakTrinoClientSecret + description: Secret ID of the Keycloak Trino client that is used by Trino to connect to Keycloak to authenticate users + default: trinotrino + - name: keycloakDruidClientSecret + description: Secret ID of the Keycloak Druid client that is used by Druid to connect to Keycloak to authenticate users + default: druiddruid + - name: keycloakDruidCookiePassphrase + description: Passphrase for encrypting the cookies used to manage authentication session with browser. + default: druiddruidcookiepassphrase + - name: druidSystemUserPassword + description: Password for the Druid user druid_system + default: druidsystemuserpassword + - name: supersetSecretKey + description: Superset's secret key used to generate e.g. user session tokens + default: supersetSecretKey + - name: trinoSupersetUserPassword + description: Password of the service Superset uses to connect to Trino. Superset itself will use impersonation for Trino users + default: supersetsuperset + signal-processing: description: >- A stack used for creating, streaming and processing in-flight data and persisting it to TimescaleDB before it is displayed in Grafana