You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2019/04/26 13:32:48 UTC

[syncope] branch master updated: [SYNCOPE-1458] Docker verified working

This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 6288660  [SYNCOPE-1458] Docker verified working
6288660 is described below

commit 628866065eab8e4445e5d1c0b191b536586d234a
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Fri Apr 26 15:32:39 2019 +0200

    [SYNCOPE-1458] Docker verified working
---
 .../zookeper/ZookeeperKeymasterClientContext.java  | 39 ++++++-----
 .../core/spring/security/AuthDataAccessor.java     | 10 ++-
 .../UsernamePasswordAuthenticationProvider.java    | 77 +++++++++++++---------
 .../core/spring/security/WebSecurityContext.java   |  2 +
 docker/console/pom.xml                             | 11 ++++
 .../src/main/resources/keymaster.properties        | 19 ++++++
 docker/core/pom.xml                                | 21 ++++++
 .../core/src/main/resources/keymaster.properties   | 19 ++++++
 docker/enduser/pom.xml                             | 11 ++++
 .../src/main/resources/keymaster.properties        | 19 ++++++
 .../resources/docker-compose/docker-compose-ha.yml | 12 ++++
 .../docker-compose/docker-compose-mariadb.yml      |  9 +++
 .../docker-compose/docker-compose-mssql.yml        |  9 +++
 .../docker-compose/docker-compose-myjson.yml       |  9 +++
 .../docker-compose/docker-compose-mysql.yml        |  9 +++
 .../docker-compose/docker-compose-pgjsonb.yml      |  9 +++
 .../docker-compose/docker-compose-postgresql.yml   |  9 +++
 ...postgresql.yml => docker-compose-zookeeper.yml} | 18 ++++-
 .../self/keymaster/cxf/SelfKeymasterContext.java   | 10 +++
 ...sterUsernamePasswordAuthenticationProvider.java | 48 ++++++++++++++
 20 files changed, 315 insertions(+), 55 deletions(-)

diff --git a/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeper/ZookeeperKeymasterClientContext.java b/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeper/ZookeeperKeymasterClientContext.java
index e42bc6d..507ccbc 100644
--- a/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeper/ZookeeperKeymasterClientContext.java
+++ b/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeper/ZookeeperKeymasterClientContext.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import javax.security.auth.login.AppConfigurationEntry;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.curator.framework.CuratorFramework;
 import org.apache.curator.framework.CuratorFrameworkFactory;
 import org.apache.curator.framework.api.ACLProvider;
@@ -79,24 +80,30 @@ public class ZookeeperKeymasterClientContext {
     @ConditionalOnExpression("#{'${keymaster.address}' matches '^(?!http).+'}")
     @Bean
     public CuratorFramework curatorFramework() throws InterruptedException {
-        javax.security.auth.login.Configuration.setConfiguration(createJaasConfig());
+        if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {
+            javax.security.auth.login.Configuration.setConfiguration(createJaasConfig());
+        }
 
-        CuratorFramework client = CuratorFrameworkFactory.builder().
+        CuratorFrameworkFactory.Builder clientBuilder = CuratorFrameworkFactory.builder().
                 connectString(address).
-                retryPolicy(new ExponentialBackoffRetry(baseSleepTimeMs, maxRetries)).
-                authorization("digest", (username).getBytes()).
-                aclProvider(new ACLProvider() {
-
-                    @Override
-                    public List<ACL> getDefaultAcl() {
-                        return ZooDefs.Ids.CREATOR_ALL_ACL;
-                    }
-
-                    @Override
-                    public List<ACL> getAclForPath(final String path) {
-                        return ZooDefs.Ids.CREATOR_ALL_ACL;
-                    }
-                }).build();
+                retryPolicy(new ExponentialBackoffRetry(baseSleepTimeMs, maxRetries));
+        if (StringUtils.isNotBlank(username)) {
+            clientBuilder.
+                    authorization("digest", (username).getBytes()).
+                    aclProvider(new ACLProvider() {
+
+                        @Override
+                        public List<ACL> getDefaultAcl() {
+                            return ZooDefs.Ids.CREATOR_ALL_ACL;
+                        }
+
+                        @Override
+                        public List<ACL> getAclForPath(final String path) {
+                            return ZooDefs.Ids.CREATOR_ALL_ACL;
+                        }
+                    });
+        }
+        CuratorFramework client = clientBuilder.build();
         client.start();
         client.blockUntilConnected(3, TimeUnit.SECONDS);
 
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
index 3226ff1..0e4851a 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
@@ -31,7 +31,6 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import javax.annotation.Resource;
 import org.apache.commons.lang3.BooleanUtils;
-import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.lib.SyncopeConstants;
@@ -67,7 +66,6 @@ import org.springframework.security.authentication.AuthenticationCredentialsNotF
 import org.springframework.security.authentication.AuthenticationServiceException;
 import org.springframework.security.authentication.DisabledException;
 import org.springframework.security.core.Authentication;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
@@ -237,7 +235,7 @@ public class AuthDataAccessor {
             }
         }
 
-        return ImmutablePair.of(user, authenticated);
+        return Pair.of(user, authenticated);
     }
 
     protected boolean authenticate(final User user, final String password) {
@@ -360,10 +358,10 @@ public class AuthDataAccessor {
         } else {
             User user = userDAO.findByUsername(username);
             if (user == null) {
-                throw new UsernameNotFoundException("Could not find any user with id " + username);
+                authorities = Collections.emptySet();
+            } else {
+                authorities = getUserAuthorities(user);
             }
-
-            authorities = getUserAuthorities(user);
         }
 
         return authorities;
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
index a1e77e2..a805b45 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/UsernamePasswordAuthenticationProvider.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.spring.security;
 
 import javax.annotation.Resource;
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AuditElements;
@@ -50,7 +51,7 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
     protected UserProvisioningManager provisioningManager;
 
     @Autowired
-    private DefaultCredentialChecker credentialChecker;
+    protected DefaultCredentialChecker credentialChecker;
 
     @Resource(name = "adminUser")
     protected String adminUser;
@@ -90,10 +91,10 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
 
     @Override
     public Authentication authenticate(final Authentication authentication) {
-        String domainKey = SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).getDomain();
+        String domain = SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).getDomain();
 
-        final String[] username = new String[1];
-        Boolean authenticated;
+        String[] username = new String[1];
+        boolean authenticated;
 
         if (anonymousUser.equals(authentication.getName())) {
             username[0] = anonymousUser;
@@ -101,32 +102,31 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
             authenticated = authentication.getCredentials().toString().equals(anonymousKey);
         } else if (adminUser.equals(authentication.getName())) {
             username[0] = adminUser;
-            if (SyncopeConstants.MASTER_DOMAIN.equals(domainKey)) {
+            if (SyncopeConstants.MASTER_DOMAIN.equals(domain)) {
                 credentialChecker.checkIsDefaultAdminPasswordInUse();
                 authenticated = ENCRYPTOR.verify(
                         authentication.getCredentials().toString(),
                         CipherAlgorithm.valueOf(adminPasswordAlgorithm),
                         adminPassword);
             } else {
-                final String domainToFind = domainKey;
                 authenticated = AuthContextUtils.callAsAdmin(SyncopeConstants.MASTER_DOMAIN, () -> {
-                    Domain domain = dataAccessor.findDomain(domainToFind);
+                    Domain domainEntity = dataAccessor.findDomain(domain);
 
                     return ENCRYPTOR.verify(
                             authentication.getCredentials().toString(),
-                            domain.getAdminCipherAlgorithm(),
-                            domain.getAdminPwd());
+                            domainEntity.getAdminCipherAlgorithm(),
+                            domainEntity.getAdminPwd());
                 });
             }
         } else {
-            Pair<User, Boolean> authResult = AuthContextUtils.callAsAdmin(domainKey,
-                    () -> dataAccessor.authenticate(domainKey, authentication));
-            authenticated = authResult.getValue();
+            Pair<User, Boolean> authResult = AuthContextUtils.callAsAdmin(domain,
+                    () -> dataAccessor.authenticate(domain, authentication));
+            authenticated = BooleanUtils.toBoolean(authResult.getRight());
             if (authResult.getLeft() != null && authResult.getRight() != null) {
                 username[0] = authResult.getLeft().getUsername();
 
                 if (!authResult.getRight()) {
-                    AuthContextUtils.callAsAdmin(domainKey, () -> {
+                    AuthContextUtils.callAsAdmin(domain, () -> {
                         provisioningManager.internalSuspend(authResult.getLeft().getKey());
                         return null;
                     });
@@ -137,45 +137,58 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
             username[0] = authentication.getPrincipal().toString();
         }
 
-        final boolean isAuthenticated = authenticated != null && authenticated;
+        return finalizeAuthentication(authenticated, domain, username[0], authentication);
+    }
+
+    protected Authentication finalizeAuthentication(
+            final boolean authenticated,
+            final String domain,
+            final String username,
+            final Authentication authentication) {
+
         UsernamePasswordAuthenticationToken token;
-        if (isAuthenticated) {
-            token = AuthContextUtils.callAsAdmin(domainKey, () -> {
-                UsernamePasswordAuthenticationToken token1 = new UsernamePasswordAuthenticationToken(
-                        username[0],
+        if (authenticated) {
+            token = AuthContextUtils.callAsAdmin(domain, () -> {
+                UsernamePasswordAuthenticationToken upat = new UsernamePasswordAuthenticationToken(
+                        username,
                         null,
-                        dataAccessor.getAuthorities(username[0]));
-                token1.setDetails(authentication.getDetails());
+                        dataAccessor.getAuthorities(username));
+                upat.setDetails(authentication.getDetails());
                 dataAccessor.audit(
-                        username[0],
+                        username,
                         AuditElements.EventCategoryType.LOGIC,
-                        AuditElements.AUTHENTICATION_CATEGORY, null,
-                        AuditElements.LOGIN_EVENT, Result.SUCCESS, null, isAuthenticated, authentication,
-                        "Successfully authenticated, with entitlements: " + token1.getAuthorities());
-                return token1;
+                        AuditElements.AUTHENTICATION_CATEGORY,
+                        null,
+                        AuditElements.LOGIN_EVENT,
+                        Result.SUCCESS,
+                        null,
+                        authenticated,
+                        authentication,
+                        "User " + username + " successfully authenticated with entitlements: " + upat.getAuthorities());
+                return upat;
             });
 
             LOG.debug("User {} successfully authenticated, with entitlements {}",
-                    username[0], token.getAuthorities());
+                    username, token.getAuthorities());
         } else {
-            AuthContextUtils.callAsAdmin(domainKey, () -> {
+            AuthContextUtils.callAsAdmin(domain, () -> {
                 dataAccessor.audit(
-                        username[0],
+                        username,
                         AuditElements.EventCategoryType.LOGIC,
                         AuditElements.AUTHENTICATION_CATEGORY,
                         null,
                         AuditElements.LOGIN_EVENT,
                         Result.FAILURE,
                         null,
-                        isAuthenticated,
+                        authenticated,
                         authentication,
-                        "User " + username[0] + " not authenticated");
+                        "User " + username + " not authenticated");
                 return null;
             });
 
-            LOG.debug("User {} not authenticated", username[0]);
+            LOG.debug("User {} not authenticated", username);
 
-            throw new BadCredentialsException("User " + username[0] + " not authenticated");
+            throw new BadCredentialsException("User " + username + " not authenticated");
         }
 
         return token;
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/WebSecurityContext.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/WebSecurityContext.java
index f6db336..29e9e5c 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/WebSecurityContext.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/WebSecurityContext.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.spring.security;
 
 import javax.annotation.Resource;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@@ -60,6 +61,7 @@ public class WebSecurityContext extends WebSecurityConfigurerAdapter {
         web.httpFirewall(allowUrlEncodedSlashHttpFirewall());
     }
 
+    @ConditionalOnMissingBean
     @Bean
     public UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider() {
         return new UsernamePasswordAuthenticationProvider();
diff --git a/docker/console/pom.xml b/docker/console/pom.xml
index 8ecfb3e..5016a0f 100644
--- a/docker/console/pom.xml
+++ b/docker/console/pom.xml
@@ -50,6 +50,17 @@ under the License.
     </dependency>
 
     <dependency>
+      <groupId>org.apache.syncope.ext.self-keymaster</groupId>
+      <artifactId>syncope-ext-self-keymaster-client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.common.keymaster</groupId>
+      <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
       <groupId>org.apache.syncope.ext.flowable</groupId>
       <artifactId>syncope-ext-flowable-client-console</artifactId>
       <version>${project.version}</version>
diff --git a/docker/console/src/main/resources/keymaster.properties b/docker/console/src/main/resources/keymaster.properties
new file mode 100644
index 0000000..14e8ca6
--- /dev/null
+++ b/docker/console/src/main/resources/keymaster.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+keymaster.address=${KEYMASTER_ADDRESS}
+keymaster.username=${KEYMASTER_USERNAME}
+keymaster.password=${KEYMASTER_PASSWORD}
diff --git a/docker/core/pom.xml b/docker/core/pom.xml
index d81bb19..b58170c 100644
--- a/docker/core/pom.xml
+++ b/docker/core/pom.xml
@@ -53,6 +53,27 @@ under the License.
       <artifactId>syncope-core-idm-rest-cxf</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.core.am</groupId>
+      <artifactId>syncope-core-am-rest-cxf</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.syncope.ext.self-keymaster</groupId>
+      <artifactId>syncope-ext-self-keymaster-rest-cxf</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.ext.self-keymaster</groupId>
+      <artifactId>syncope-ext-self-keymaster-persistence-jpa</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.common.keymaster</groupId>
+      <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
+      <version>${project.version}</version>
+    </dependency>
 
     <dependency>
       <groupId>org.apache.syncope.core</groupId>
diff --git a/docker/core/src/main/resources/keymaster.properties b/docker/core/src/main/resources/keymaster.properties
new file mode 100644
index 0000000..14e8ca6
--- /dev/null
+++ b/docker/core/src/main/resources/keymaster.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+keymaster.address=${KEYMASTER_ADDRESS}
+keymaster.username=${KEYMASTER_USERNAME}
+keymaster.password=${KEYMASTER_PASSWORD}
diff --git a/docker/enduser/pom.xml b/docker/enduser/pom.xml
index 2600cb3..e58ab48 100644
--- a/docker/enduser/pom.xml
+++ b/docker/enduser/pom.xml
@@ -50,6 +50,17 @@ under the License.
     </dependency>
 
     <dependency>
+      <groupId>org.apache.syncope.ext.self-keymaster</groupId>
+      <artifactId>syncope-ext-self-keymaster-client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.common.keymaster</groupId>
+      <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
       <groupId>org.apache.syncope.ext.flowable</groupId>
       <artifactId>syncope-ext-flowable-client-enduser</artifactId>
       <version>${project.version}</version>
diff --git a/docker/enduser/src/main/resources/keymaster.properties b/docker/enduser/src/main/resources/keymaster.properties
new file mode 100644
index 0000000..14e8ca6
--- /dev/null
+++ b/docker/enduser/src/main/resources/keymaster.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+keymaster.address=${KEYMASTER_ADDRESS}
+keymaster.username=${KEYMASTER_USERNAME}
+keymaster.password=${KEYMASTER_PASSWORD}
diff --git a/docker/src/main/resources/docker-compose/docker-compose-ha.yml b/docker/src/main/resources/docker-compose/docker-compose-ha.yml
index c5857ba..1c08942 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-ha.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-ha.yml
@@ -44,6 +44,9 @@ services:
        DB_POOL_MAX: 10
        DB_POOL_MIN: 2
        OPENJPA_REMOTE_COMMIT: tcp(Addresses=syncope1;syncope2)
+       KEYMASTER_ADDRESS: http://localhost:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope2:
      depends_on:
@@ -60,6 +63,9 @@ services:
        DB_POOL_MAX: 10
        DB_POOL_MIN: 2
        OPENJPA_REMOTE_COMMIT: tcp(Addresses=syncope1;syncope2)
+       KEYMASTER_ADDRESS: http://localhost:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-console:
      depends_on:
@@ -72,6 +78,9 @@ services:
        CORE_SCHEME: http
        CORE_HOST: syncope1
        CORE_PORT: 8080
+       KEYMASTER_ADDRESS: http://syncope1:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-enduser:
      depends_on:
@@ -85,3 +94,6 @@ services:
        CORE_HOST: syncope1
        CORE_PORT: 8080
        DOMAIN: Master
+       KEYMASTER_ADDRESS: http://syncope1:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
diff --git a/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml b/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml
index e39ff8b..11ad7c1 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-mariadb.yml
@@ -44,6 +44,9 @@ services:
        DB_POOL_MAX: 10
        DB_POOL_MIN: 2
        OPENJPA_REMOTE_COMMIT: sjvm
+       KEYMASTER_ADDRESS: http://localhost:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-console:
      depends_on:
@@ -56,6 +59,9 @@ services:
        CORE_SCHEME: http
        CORE_HOST: syncope
        CORE_PORT: 8080
+       KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-enduser:
      depends_on:
@@ -69,3 +75,6 @@ services:
        CORE_HOST: syncope
        CORE_PORT: 8080
        DOMAIN: Master
+       KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
diff --git a/docker/src/main/resources/docker-compose/docker-compose-mssql.yml b/docker/src/main/resources/docker-compose/docker-compose-mssql.yml
index 32a9300..daeed50 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-mssql.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-mssql.yml
@@ -46,6 +46,9 @@ services:
        DB_POOL_MAX: 10
        DB_POOL_MIN: 2
        OPENJPA_REMOTE_COMMIT: sjvm
+       KEYMASTER_ADDRESS: http://localhost:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-console:
      depends_on:
@@ -58,6 +61,9 @@ services:
        CORE_SCHEME: http
        CORE_HOST: syncope
        CORE_PORT: 8080
+       KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-enduser:
      depends_on:
@@ -71,3 +77,6 @@ services:
        CORE_HOST: syncope
        CORE_PORT: 8080
        DOMAIN: Master
+       KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
diff --git a/docker/src/main/resources/docker-compose/docker-compose-myjson.yml b/docker/src/main/resources/docker-compose/docker-compose-myjson.yml
index b5922b8..4c4abb9 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-myjson.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-myjson.yml
@@ -44,6 +44,9 @@ services:
        DB_POOL_MAX: 10
        DB_POOL_MIN: 2
        OPENJPA_REMOTE_COMMIT: sjvm
+       KEYMASTER_ADDRESS: http://localhost:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-console:
      depends_on:
@@ -56,6 +59,9 @@ services:
        CORE_SCHEME: http
        CORE_HOST: syncope
        CORE_PORT: 8080
+       KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-enduser:
      depends_on:
@@ -69,3 +75,6 @@ services:
        CORE_HOST: syncope
        CORE_PORT: 8080
        DOMAIN: Master
+       KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
diff --git a/docker/src/main/resources/docker-compose/docker-compose-mysql.yml b/docker/src/main/resources/docker-compose/docker-compose-mysql.yml
index e9e25c7..510440a 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-mysql.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-mysql.yml
@@ -44,6 +44,9 @@ services:
        DB_POOL_MAX: 10
        DB_POOL_MIN: 2
        OPENJPA_REMOTE_COMMIT: sjvm
+       KEYMASTER_ADDRESS: http://localhost:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-console:
      depends_on:
@@ -56,6 +59,9 @@ services:
        CORE_SCHEME: http
        CORE_HOST: syncope
        CORE_PORT: 8080
+       KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-enduser:
      depends_on:
@@ -69,3 +75,6 @@ services:
        CORE_HOST: syncope
        CORE_PORT: 8080
        DOMAIN: Master
+       KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
diff --git a/docker/src/main/resources/docker-compose/docker-compose-pgjsonb.yml b/docker/src/main/resources/docker-compose/docker-compose-pgjsonb.yml
index 8af76b7..745931e 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-pgjsonb.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-pgjsonb.yml
@@ -43,6 +43,9 @@ services:
        DB_POOL_MAX: 10
        DB_POOL_MIN: 2
        OPENJPA_REMOTE_COMMIT: sjvm
+       KEYMASTER_ADDRESS: http://localhost:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-console:
      depends_on:
@@ -55,6 +58,9 @@ services:
        CORE_SCHEME: http
        CORE_HOST: syncope
        CORE_PORT: 8080
+       KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-enduser:
      depends_on:
@@ -68,3 +74,6 @@ services:
        CORE_HOST: syncope
        CORE_PORT: 8080
        DOMAIN: Master
+       KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
diff --git a/docker/src/main/resources/docker-compose/docker-compose-postgresql.yml b/docker/src/main/resources/docker-compose/docker-compose-postgresql.yml
index 5911083..3509caa 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-postgresql.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-postgresql.yml
@@ -43,6 +43,9 @@ services:
        DB_POOL_MAX: 10
        DB_POOL_MIN: 2
        OPENJPA_REMOTE_COMMIT: sjvm
+       KEYMASTER_ADDRESS: http://localhost:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-console:
      depends_on:
@@ -55,6 +58,9 @@ services:
        CORE_SCHEME: http
        CORE_HOST: syncope
        CORE_PORT: 8080
+       KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
 
    syncope-enduser:
      depends_on:
@@ -68,3 +74,6 @@ services:
        CORE_HOST: syncope
        CORE_PORT: 8080
        DOMAIN: Master
+       KEYMASTER_ADDRESS: http://syncope:8080/syncope/rest/keymaster
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD}
diff --git a/docker/src/main/resources/docker-compose/docker-compose-postgresql.yml b/docker/src/main/resources/docker-compose/docker-compose-zookeeper.yml
similarity index 72%
copy from docker/src/main/resources/docker-compose/docker-compose-postgresql.yml
copy to docker/src/main/resources/docker-compose/docker-compose-zookeeper.yml
index 5911083..a4be37a 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-postgresql.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-zookeeper.yml
@@ -15,11 +15,18 @@
 # specific language governing permissions and limitations
 # under the License.
 
-# Full deployment (Core, Console, Enduser) on PostgreSQL
+# Full deployment (Core, Console, Enduser) on PostgreSQL, with Keymaster on Zookeeper
+
+# Zookeeper is configured without JAAS, hence empty KEYMASTER_USERNAME / KEYMASTER_PASSWORD
+# are passed to other containers
 
 version: '3.3'
 
 services:
+   keymaster:
+     image: zookeeper:3.4.14
+     restart: always
+
    db:
      image: postgres:11.2
      restart: always
@@ -43,6 +50,9 @@ services:
        DB_POOL_MAX: 10
        DB_POOL_MIN: 2
        OPENJPA_REMOTE_COMMIT: sjvm
+       KEYMASTER_ADDRESS: keymaster:2181
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME:-}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD:-}
 
    syncope-console:
      depends_on:
@@ -55,6 +65,9 @@ services:
        CORE_SCHEME: http
        CORE_HOST: syncope
        CORE_PORT: 8080
+       KEYMASTER_ADDRESS: keymaster:2181
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME:-}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD:-}
 
    syncope-enduser:
      depends_on:
@@ -68,3 +81,6 @@ services:
        CORE_HOST: syncope
        CORE_PORT: 8080
        DOMAIN: Master
+       KEYMASTER_ADDRESS: keymaster:2181
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME:-}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD:-}
diff --git a/ext/self-keymaster/rest-cxf/src/main/java/org/apache/syncope/ext/self/keymaster/cxf/SelfKeymasterContext.java b/ext/self-keymaster/rest-cxf/src/main/java/org/apache/syncope/ext/self/keymaster/cxf/SelfKeymasterContext.java
index 6071bd6..4b16893 100644
--- a/ext/self-keymaster/rest-cxf/src/main/java/org/apache/syncope/ext/self/keymaster/cxf/SelfKeymasterContext.java
+++ b/ext/self-keymaster/rest-cxf/src/main/java/org/apache/syncope/ext/self/keymaster/cxf/SelfKeymasterContext.java
@@ -31,8 +31,12 @@ import org.apache.cxf.jaxrs.validation.JAXRSBeanValidationOutInterceptor;
 import org.apache.cxf.transport.common.gzip.GZIPInInterceptor;
 import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
+import org.apache.syncope.core.spring.security.UsernamePasswordAuthenticationProvider;
+import org.apache.syncope.core.spring.security.WebSecurityContext;
 import org.apache.syncope.ext.self.keymaster.cxf.client.SelfKeymasterInternalConfParamOps;
+import org.apache.syncope.ext.self.keymaster.cxf.security.SelfKeymasterUsernamePasswordAuthenticationProvider;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
@@ -44,6 +48,7 @@ import org.springframework.context.annotation.PropertySource;
 @PropertySource(value = "file:${conf.directory}/keymaster.properties", ignoreResourceNotFound = true)
 @ComponentScan("org.apache.syncope.ext.self.keymaster.cxf.service")
 @Configuration
+@AutoConfigureBefore(WebSecurityContext.class)
 @ConditionalOnExpression("'${keymaster.address}' matches '^http.+'")
 public class SelfKeymasterContext {
 
@@ -109,6 +114,11 @@ public class SelfKeymasterContext {
     }
 
     @Bean
+    public UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider() {
+        return new SelfKeymasterUsernamePasswordAuthenticationProvider();
+    }
+
+    @Bean
     public ConfParamOps internalConfParamOps() {
         return new SelfKeymasterInternalConfParamOps();
     }
diff --git a/ext/self-keymaster/rest-cxf/src/main/java/org/apache/syncope/ext/self/keymaster/cxf/security/SelfKeymasterUsernamePasswordAuthenticationProvider.java b/ext/self-keymaster/rest-cxf/src/main/java/org/apache/syncope/ext/self/keymaster/cxf/security/SelfKeymasterUsernamePasswordAuthenticationProvider.java
new file mode 100644
index 0000000..2068326
--- /dev/null
+++ b/ext/self-keymaster/rest-cxf/src/main/java/org/apache/syncope/ext/self/keymaster/cxf/security/SelfKeymasterUsernamePasswordAuthenticationProvider.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.ext.self.keymaster.cxf.security;
+
+import org.apache.syncope.core.spring.security.SyncopeAuthenticationDetails;
+import org.apache.syncope.core.spring.security.UsernamePasswordAuthenticationProvider;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.Authentication;
+
+@Configurable
+public class SelfKeymasterUsernamePasswordAuthenticationProvider extends UsernamePasswordAuthenticationProvider {
+
+    @Value("${keymaster.username}")
+    private String keymasterUsername;
+
+    @Value("${keymaster.password}")
+    private String keymasterPassword;
+
+    @Override
+    public Authentication authenticate(final Authentication authentication) {
+        if (keymasterUsername.equals(authentication.getName())) {
+            return finalizeAuthentication(
+                    authentication.getCredentials().toString().equals(keymasterPassword),
+                    SyncopeAuthenticationDetails.class.cast(authentication.getDetails()).getDomain(),
+                    keymasterUsername,
+                    authentication);
+        }
+
+        return super.authenticate(authentication);
+    }
+}