diff --git a/keycloak/docker-compose.yml b/keycloak/docker-compose.yml index 93a57a4..ceb5043 100644 --- a/keycloak/docker-compose.yml +++ b/keycloak/docker-compose.yml @@ -17,6 +17,42 @@ services: ports: - "5432:5432" + openldap: + image: "bitnami/openldap:2.6" + container_name: openldap + environment: + LDAP_ADMIN_USERNAME: admin + LDAP_ADMIN_PASSWORD: admin + LDAP_ADMIN_DN: cn=admin,dc=example,dc=org + LDAP_ROOT: dc=example,dc=org + healthcheck: + test: [ "CMD-SHELL", "ldapsearch -H ldapi:/// -b dc=example,dc=org cn > /dev/null" ] + interval: 15s + retries: 5 + timeout: 5s + ports: + - "1389:1389" + - "1636:1636" + volumes: + - openldap:/bitnami/openldap + - ./openldap:/ldifs + + openldap-admin: + image: "osixia/phpldapadmin:0.9.0" + container_name: openldap-admin + ports: + - "8000:80" + healthcheck: + test: "curl --fail http://localhost || exit 1" + interval: 5s + timeout: 3s + retries: 5 + volumes: + - ./openldap-admin/.env.yaml:/container/environment/01-custom/env.yaml + depends_on: + openldap: + condition: service_started + keycloak: image: "bitnami/keycloak:26" container_name: keycloak @@ -45,7 +81,10 @@ services: depends_on: postgres: condition: service_healthy + openldap: + condition: service_healthy volumes: keycloak: + openldap: postgres: diff --git a/keycloak/keycloak/realm.json b/keycloak/keycloak/realm.json index 5592e36..482e3b2 100644 --- a/keycloak/keycloak/realm.json +++ b/keycloak/keycloak/realm.json @@ -239,7 +239,7 @@ "composite" : true, "composites" : { "client" : { - "realm-management" : [ "query-users", "query-realms", "manage-realm", "manage-users", "manage-identity-providers", "manage-authorization", "view-users", "view-clients", "manage-clients", "query-groups", "view-events", "create-client", "view-identity-providers", "view-authorization", "manage-events", "impersonation", "query-clients", "view-realm" ] + "realm-management" : [ "query-users", "query-realms", "manage-realm", "manage-users", "manage-identity-providers", "manage-authorization", "view-users", "view-clients", "manage-clients", "query-groups", "view-events", "create-client", "manage-events", "view-authorization", "view-identity-providers", "impersonation", "query-clients", "view-realm" ] } }, "clientRole" : true, @@ -378,23 +378,44 @@ "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], "webAuthnPolicyPasswordlessExtraOrigins" : [ ], "users" : [ { - "id" : "08bf372c-4033-4a45-809e-4fcffce47b36", + "id" : "b916f557-09c1-448f-8a9a-8b5a04768817", "username" : "program", - "firstName" : "Alexey", + "firstName" : "Alex", "lastName" : "Romanov", "email" : "program@mail.ru", - "emailVerified" : true, - "createdTimestamp" : 1733751128274, + "emailVerified" : false, + "attributes" : { + "LDAP_ENTRY_DN" : [ "cn=program,ou=users,dc=example,dc=org" ], + "LDAP_ID" : [ "1001" ] + }, + "origin" : "1WfrqTJuR9uIQ0vyjwRzKA", + "createdTimestamp" : 1733922080126, "enabled" : true, "totp" : false, - "credentials" : [ { - "id" : "2f06e698-eaa7-47a8-a059-d5c2c9810dba", - "type" : "password", - "userLabel" : "My password", - "createdDate" : 1733751138860, - "secretData" : "{\"value\":\"Cfjv7pFPB/L45GjjuFxGhdNKh9ArDq1JCQAl7a+d+Fc=\",\"salt\":\"L+M8zZuovvrpfz4arIZzPA==\",\"additionalParameters\":{}}", - "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" - } ], + "federationLink" : "1WfrqTJuR9uIQ0vyjwRzKA", + "credentials" : [ ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-todo-list" ], + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "474d4970-109b-43d3-b732-d19b9327ce78", + "username" : "ronin", + "firstName" : "Alex", + "lastName" : "Romanov", + "email" : "ronin@mail.ru", + "emailVerified" : false, + "attributes" : { + "LDAP_ENTRY_DN" : [ "cn=ronin,ou=users,dc=example,dc=org" ], + "LDAP_ID" : [ "1000" ] + }, + "origin" : "1WfrqTJuR9uIQ0vyjwRzKA", + "createdTimestamp" : 1733922080099, + "enabled" : true, + "totp" : false, + "federationLink" : "1WfrqTJuR9uIQ0vyjwRzKA", + "credentials" : [ ], "disableableCredentialTypes" : [ ], "requiredActions" : [ ], "realmRoles" : [ "default-roles-todo-list" ], @@ -504,7 +525,8 @@ "protocol" : "openid-connect", "attributes" : { "realm_client" : "false", - "client.use.lightweight.access.token.enabled" : "true" + "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+" }, "authenticationFlowBindingOverrides" : { }, "fullScopeAllowed" : true, @@ -541,6 +563,7 @@ "oidc.ciba.grant.enabled" : "false", "client.secret.creation.time" : "1733752117", "backchannel.logout.session.required" : "true", + "post.logout.redirect.uris" : "+", "display.on.consent.screen" : "false", "oauth2.device.authorization.grant.enabled" : "false", "backchannel.logout.revoke.offline.tokens" : "false" @@ -571,7 +594,8 @@ "frontchannelLogout" : false, "protocol" : "openid-connect", "attributes" : { - "realm_client" : "true" + "realm_client" : "true", + "post.logout.redirect.uris" : "+" }, "authenticationFlowBindingOverrides" : { }, "fullScopeAllowed" : false, @@ -599,7 +623,8 @@ "frontchannelLogout" : false, "protocol" : "openid-connect", "attributes" : { - "realm_client" : "true" + "realm_client" : "true", + "post.logout.redirect.uris" : "+" }, "authenticationFlowBindingOverrides" : { }, "fullScopeAllowed" : false, @@ -752,6 +777,7 @@ "config" : { "introspection.token.claim" : "true", "multivalued" : "true", + "userinfo.token.claim" : "true", "user.attribute" : "foo", "id.token.claim" : "true", "access.token.claim" : "true", @@ -801,8 +827,9 @@ "consentRequired" : false, "config" : { "user.session.note" : "AUTH_TIME", - "id.token.claim" : "true", "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "auth_time", "jsonType.label" : "long" @@ -1074,7 +1101,8 @@ "config" : { "id.token.claim" : "true", "introspection.token.claim" : "true", - "access.token.claim" : "true" + "access.token.claim" : "true", + "userinfo.token.claim" : "true" } } ] }, { @@ -1192,12 +1220,13 @@ "protocolMapper" : "oidc-organization-membership-mapper", "consentRequired" : false, "config" : { - "id.token.claim" : "true", "introspection.token.claim" : "true", + "multivalued" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", "access.token.claim" : "true", "claim.name" : "organization", - "jsonType.label" : "String", - "multivalued" : "true" + "jsonType.label" : "String" } } ] }, { @@ -1250,7 +1279,7 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "saml-user-property-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-address-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper" ] } }, { "id" : "610a7dee-4d8d-4df0-bd4b-17285d790ef0", @@ -1294,7 +1323,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-full-name-mapper", "saml-user-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-address-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "saml-role-list-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper" ] } }, { "id" : "e8b90a10-667e-45d4-8e06-488a3dbd7d97", @@ -1313,6 +1342,130 @@ "subComponents" : { }, "config" : { } } ], + "org.keycloak.storage.UserStorageProvider" : [ { + "id" : "1WfrqTJuR9uIQ0vyjwRzKA", + "name" : "ldap", + "providerId" : "ldap", + "subComponents" : { + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper" : [ { + "id" : "e1e84320-fe45-4b80-ad31-b7c599697626", + "name" : "email", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "mail" ], + "is.mandatory.in.ldap" : [ "false" ], + "read.only" : [ "true" ], + "always.read.value.from.ldap" : [ "false" ], + "user.model.attribute" : [ "email" ] + } + }, { + "id" : "251c4670-010c-4cf3-9129-0820d8e23712", + "name" : "first name", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "givenName" ], + "is.mandatory.in.ldap" : [ "true" ], + "always.read.value.from.ldap" : [ "true" ], + "read.only" : [ "true" ], + "user.model.attribute" : [ "firstName" ] + } + }, { + "id" : "2c9261be-5fe8-4aa5-a42d-1d19287cbfb4", + "name" : "creation date", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "whenCreated" ], + "is.mandatory.in.ldap" : [ "false" ], + "always.read.value.from.ldap" : [ "true" ], + "read.only" : [ "true" ], + "user.model.attribute" : [ "createTimestamp" ] + } + }, { + "id" : "29bb279e-33b7-4d4a-969a-b52ed8de55f7", + "name" : "username", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "cn" ], + "is.mandatory.in.ldap" : [ "true" ], + "always.read.value.from.ldap" : [ "false" ], + "read.only" : [ "true" ], + "user.model.attribute" : [ "username" ] + } + }, { + "id" : "64028dbf-8137-4cfe-abda-38b751863c91", + "name" : "Kerberos principal attribute mapper", + "providerId" : "kerberos-principal-attribute-mapper", + "subComponents" : { }, + "config" : { } + }, { + "id" : "19a68ab4-8215-4d66-a384-c7d7618ac07f", + "name" : "last name", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "sn" ], + "is.mandatory.in.ldap" : [ "true" ], + "read.only" : [ "true" ], + "always.read.value.from.ldap" : [ "true" ], + "user.model.attribute" : [ "lastName" ] + } + }, { + "id" : "a17fce5c-1ed5-43e5-8006-52bf47f5eeba", + "name" : "modify date", + "providerId" : "user-attribute-ldap-mapper", + "subComponents" : { }, + "config" : { + "ldap.attribute" : [ "whenChanged" ], + "is.mandatory.in.ldap" : [ "false" ], + "read.only" : [ "true" ], + "always.read.value.from.ldap" : [ "true" ], + "user.model.attribute" : [ "modifyTimestamp" ] + } + }, { + "id" : "33d6e815-5892-4daa-a34b-8284f9e37722", + "name" : "MSAD account controls", + "providerId" : "msad-user-account-control-mapper", + "subComponents" : { }, + "config" : { + "always.read.enabled.value.from.ldap" : [ "true" ] + } + } ] + }, + "config" : { + "fullSyncPeriod" : [ "-1" ], + "pagination" : [ "false" ], + "startTls" : [ "false" ], + "usersDn" : [ "ou=users,dc=example,dc=org" ], + "connectionPooling" : [ "false" ], + "cachePolicy" : [ "DEFAULT" ], + "useKerberosForPasswordAuthentication" : [ "false" ], + "importEnabled" : [ "true" ], + "enabled" : [ "true" ], + "changedSyncPeriod" : [ "-1" ], + "bindCredential" : [ "admin" ], + "bindDn" : [ "cn=admin,dc=example,dc=org" ], + "usernameLDAPAttribute" : [ "cn" ], + "vendor" : [ "ad" ], + "uuidLDAPAttribute" : [ "uidNumber" ], + "allowKerberosAuthentication" : [ "false" ], + "connectionUrl" : [ "ldap://openldap:1389" ], + "syncRegistrations" : [ "true" ], + "authType" : [ "simple" ], + "krbPrincipalAttribute" : [ "userPrincipalName" ], + "searchScope" : [ "1" ], + "useTruststoreSpi" : [ "always" ], + "usePasswordModifyExtendedOp" : [ "false" ], + "trustEmail" : [ "false" ], + "userObjectClasses" : [ "inetOrgPerson" ], + "rdnLDAPAttribute" : [ "cn" ], + "editMode" : [ "READ_ONLY" ], + "validatePasswordPolicy" : [ "false" ] + } + } ], "org.keycloak.keys.KeyProvider" : [ { "id" : "14afaef2-1bcf-46cc-8836-18dfb4f83f40", "name" : "rsa-generated", @@ -1986,13 +2139,18 @@ "firstBrokerLoginFlow" : "first broker login", "attributes" : { "cibaBackchannelTokenDeliveryMode" : "poll", - "cibaExpiresIn" : "120", "cibaAuthRequestedUserHint" : "login_hint", - "oauth2DeviceCodeLifespan" : "600", + "clientOfflineSessionMaxLifespan" : "0", "oauth2DevicePollingInterval" : "5", - "parRequestUriLifespan" : "60", + "clientSessionIdleTimeout" : "0", + "clientOfflineSessionIdleTimeout" : "0", "cibaInterval" : "5", - "realmReusableOtpCode" : "false" + "realmReusableOtpCode" : "false", + "cibaExpiresIn" : "120", + "oauth2DeviceCodeLifespan" : "600", + "parRequestUriLifespan" : "60", + "clientSessionMaxLifespan" : "0", + "organizationsEnabled" : "false" }, "keycloakVersion" : "26.0.7", "userManagedAccessAllowed" : false, @@ -2003,4 +2161,4 @@ "clientPolicies" : { "policies" : [ ] } -} +} \ No newline at end of file diff --git a/keycloak/openldap-admin/.env.yaml b/keycloak/openldap-admin/.env.yaml new file mode 100644 index 0000000..2cbad1b --- /dev/null +++ b/keycloak/openldap-admin/.env.yaml @@ -0,0 +1,10 @@ +PHPLDAPADMIN_LDAP_HOSTS: + - openldap: + - server: + - port: 1389 + - tls: false + - login: + - bind_id: cn=admin,dc=example,dc=org + +PHPLDAPADMIN_HTTPS: false +PHPLDAPADMIN_LDAP_CLIENT_TLS: false \ No newline at end of file diff --git a/keycloak/openldap/init.ldif b/keycloak/openldap/init.ldif new file mode 100644 index 0000000..a5ec6ba --- /dev/null +++ b/keycloak/openldap/init.ldif @@ -0,0 +1,45 @@ +version: 1 + +dn: dc=example,dc=org +dc: example +o: example +objectClass: dcObject +objectClass: organization + +dn: ou=users,dc=example,dc=org +objectClass: organizationalUnit +ou: users + +dn: cn=ronin,ou=users,dc=example,dc=org +cn: ronin +gidNumber: 1000 +homeDirectory: /home/ronin +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +sn: Romanov +givenName: Alex +mail: ronin@mail.ru +uid: ronin +uidNumber: 1000 +userpassword: test + +dn: cn=program,ou=users,dc=example,dc=org +cn: program +gidNumber: 1001 +homeDirectory: /home/program +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +sn: Romanov +givenName: Alex +mail: program@mail.ru +uid: program +uidNumber: 1001 +userpassword: test + +dn: cn=readers,ou=users,dc=example,dc=org +cn: readers +member: cn=ronin,ou=users,dc=example,dc=org +member: cn=program,ou=users,dc=example,dc=org +objectClass: groupOfNames \ No newline at end of file