You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by mm...@apache.org on 2020/03/16 15:56:12 UTC

[syncope] 26/36: [SYNCOPE-1545] Refactoring logging, adding Keymaster support + Docker image

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

mmoayyed pushed a commit to branch SYNCOPE-1545
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit 37251ffb31db59c23b783840d9ce1aeb39d500f7
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Wed Mar 4 16:15:16 2020 +0100

    [SYNCOPE-1545] Refactoring logging, adding Keymaster support + Docker image
---
 .../client/console/SyncopeConsoleApplication.java  |  15 +-
 .../client/console/SyncopeWebApplication.java      |  20 --
 .../client/enduser/SyncopeEnduserApplication.java  |  13 ++
 .../client/enduser/SyncopeWebApplication.java      |  15 --
 common/keymaster/client-api/pom.xml                |   5 +
 .../client/api/startstop/KeymasterStart.java       |  11 +-
 .../client/api/startstop/KeymasterStartStop.java   |  12 +-
 .../client/api/startstop/KeymasterStop.java        |  14 +-
 .../core/starter/SyncopeCoreApplication.java       |  12 ++
 ...ncopeCoreStartup.java => SyncopeCoreStart.java} |  12 +-
 docker/core/src/main/resources/log4j2.xml          |   4 +
 docker/pom.xml                                     |  16 +-
 .../docker-compose/docker-compose-all.yml          |  18 +-
 docker/wa/LICENSE                                  | 202 +++++++++++++++++++++
 docker/wa/NOTICE                                   |   5 +
 docker/wa/pom.xml                                  | 177 ++++++++++++++++++
 .../wa/src/main/resources/Dockerfile               |  35 ++--
 .../wa}/src/main/resources/application.properties  |   2 +-
 .../wa/src/main/resources/keymaster.properties     |  27 +--
 docker/wa/src/main/resources/log4j2.xml            |  59 ++++++
 .../wa/src/main/resources/startup.sh               |  28 +--
 .../wa/src/main/resources/wa.properties            |  21 +--
 .../syncope/core/logic/NetworkServiceLogic.java    |   6 +-
 fit/console-reference/pom.xml                      |   5 -
 fit/enduser-reference/pom.xml                      |   5 -
 fit/wa-reference/pom.xml                           | 159 +++++++++++++++-
 .../src/main/resources/keymaster.properties        |  27 +--
 fit/wa-reference/src/main/resources/log4j2.xml     | 148 +++++----------
 .../wa-reference/src/main/resources/wa.properties  |  21 +--
 .../src/test/resources/hotswap-agent.properties    |  25 +--
 sra/pom.xml                                        |   6 +
 .../apache/syncope/sra/SyncopeSRAApplication.java  |  13 ++
 .../org/apache/syncope/sra/SyncopeSRAShutdown.java |  33 ----
 .../apache/syncope/sra/SyncopeSRAStartStop.java    |  40 ----
 wa/pom.xml                                         | 133 +++++++++++++-
 .../apache/syncope/wa/SyncopeWAApplication.java    |  17 ++
 wa/src/main/resources/application.properties       |   8 +-
 wa/src/main/resources/log4j2.xml                   |  68 +++++++
 wa/src/main/resources/static/images/favicon.png    | Bin 0 -> 641 bytes
 wa/src/main/resources/templates/layout.html        |   4 +-
 .../{application.properties => wa.properties}      |  21 +--
 .../java/org/apache/syncope/wa/SyncopeWATest.java  | 158 ++++++++++++++++
 .../apache/syncope/wa/ZookeeperTestingServer.java  |  80 ++++++++
 .../resources/keymaster.properties}                |  27 +--
 44 files changed, 1255 insertions(+), 472 deletions(-)

diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
index 0175fe7..8e922cc 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.client.console;
 
 import com.giffing.wicket.spring.boot.starter.web.config.WicketWebInitializerAutoConfig.WebSocketWicketWebInitializerAutoConfiguration;
+import org.apache.syncope.client.console.commons.AnyDirectoryPanelAdditionalActionLinksProvider;
 import org.apache.syncope.client.console.commons.AnyDirectoryPanelAdditionalActionsProvider;
 import org.apache.syncope.client.console.commons.AnyWizardBuilderAdditionalSteps;
 import org.apache.syncope.client.console.commons.ExternalResourceProvider;
@@ -36,6 +37,9 @@ import org.apache.syncope.client.console.commons.StatusProvider;
 import org.apache.syncope.client.console.commons.VirSchemaDetailsPanelProvider;
 import org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.console.init.MIMETypesLoader;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
+import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -44,7 +48,6 @@ import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConf
 import org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
 import org.springframework.context.annotation.Bean;
-import org.apache.syncope.client.console.commons.AnyDirectoryPanelAdditionalActionLinksProvider;
 
 @SpringBootApplication(exclude = {
     ErrorMvcAutoConfiguration.class,
@@ -61,6 +64,16 @@ public class SyncopeConsoleApplication extends SpringBootServletInitializer {
         return super.configure(builder);
     }
 
+    @Bean
+    public KeymasterStart keymasterStart() {
+        return new KeymasterStart(NetworkService.Type.CONSOLE);
+    }
+
+    @Bean
+    public KeymasterStop keymasterStop() {
+        return new KeymasterStop(NetworkService.Type.CONSOLE);
+    }
+
     @ConditionalOnMissingBean(name = "classPathScanImplementationLookup")
     @Bean
     public ClassPathScanImplementationLookup classPathScanImplementationLookup() {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
index bc01e4d..e015d03 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
@@ -76,7 +76,6 @@ import org.apache.wicket.request.component.IRequestablePage;
 import org.apache.wicket.request.cycle.IRequestCycleListener;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 @Component
@@ -100,9 +99,6 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication {
     @Autowired
     private ServiceOps serviceOps;
 
-    @Value("${service.discovery.address}")
-    private String address;
-
     private String anonymousUser;
 
     private String anonymousKey;
@@ -179,13 +175,6 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication {
         }
     }
 
-    private NetworkService getNetworkService() {
-        NetworkService ns = new NetworkService();
-        ns.setType(NetworkService.Type.CONSOLE);
-        ns.setAddress(address);
-        return ns;
-    }
-
     @Override
     protected void init() {
         super.init();
@@ -311,15 +300,6 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication {
         if (getDebugSettings().isAjaxDebugModeEnabled()) {
             getDebugSettings().setComponentPathAttributeName("syncope-path");
         }
-
-        serviceOps.register(getNetworkService());
-    }
-
-    @Override
-    protected void onDestroy() {
-        serviceOps.unregister(getNetworkService());
-
-        super.onDestroy();
     }
 
     @Override
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
index 975a076..aaac232 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java
@@ -21,6 +21,9 @@ package org.apache.syncope.client.enduser;
 import com.giffing.wicket.spring.boot.starter.web.config.WicketWebInitializerAutoConfig.WebSocketWicketWebInitializerAutoConfiguration;
 import org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.enduser.init.MIMETypesLoader;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
+import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -45,6 +48,16 @@ public class SyncopeEnduserApplication extends SpringBootServletInitializer {
         return super.configure(builder);
     }
 
+    @Bean
+    public KeymasterStart keymasterStart() {
+        return new KeymasterStart(NetworkService.Type.ENDUSER);
+    }
+
+    @Bean
+    public KeymasterStop keymasterStop() {
+        return new KeymasterStop(NetworkService.Type.ENDUSER);
+    }
+
     @ConditionalOnMissingBean(name = "classPathScanImplementationLookup")
     @Bean
     public ClassPathScanImplementationLookup classPathScanImplementationLookup() {
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
index edc4ae4..176fa39 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
@@ -83,7 +83,6 @@ import org.apache.wicket.util.lang.Args;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 @Component
@@ -110,9 +109,6 @@ public class SyncopeWebApplication extends WicketBootStandardWebApplication {
     @Autowired
     private ServiceOps serviceOps;
 
-    @Value("${service.discovery.address}")
-    private String address;
-
     private boolean useGZIPCompression;
 
     private String adminUser;
@@ -148,13 +144,6 @@ public class SyncopeWebApplication extends WicketBootStandardWebApplication {
         }
     }
 
-    private NetworkService getNetworkService() {
-        NetworkService ns = new NetworkService();
-        ns.setType(NetworkService.Type.ENDUSER);
-        ns.setAddress(address);
-        return ns;
-    }
-
     @Override
     protected void init() {
         super.init();
@@ -355,14 +344,10 @@ public class SyncopeWebApplication extends WicketBootStandardWebApplication {
         if (getDebugSettings().isAjaxDebugModeEnabled()) {
             getDebugSettings().setComponentPathAttributeName("syncope-path");
         }
-
-        serviceOps.register(getNetworkService());
     }
 
     @Override
     protected void onDestroy() {
-        serviceOps.unregister(getNetworkService());
-
         if (customFormAttributesMonitor != null) {
             try {
                 customFormAttributesMonitor.stop(0);
diff --git a/common/keymaster/client-api/pom.xml b/common/keymaster/client-api/pom.xml
index 5dcf5ad..37fdb6b 100644
--- a/common/keymaster/client-api/pom.xml
+++ b/common/keymaster/client-api/pom.xml
@@ -43,6 +43,11 @@ under the License.
       <artifactId>syncope-common-idrepo-lib</artifactId>
       <version>${project.version}</version>
     </dependency>
+
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-context</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartup.java b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStart.java
similarity index 78%
rename from sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartup.java
rename to common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStart.java
index cbc7b59..f164865 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartup.java
+++ b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStart.java
@@ -16,16 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.sra;
+package org.apache.syncope.common.keymaster.client.api.startstop;
 
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextRefreshedEvent;
-import org.springframework.stereotype.Component;
 
-@Component
-public class SyncopeSRAStartup extends SyncopeSRAStartStop
+public class KeymasterStart extends KeymasterStartStop
         implements ApplicationListener<ContextRefreshedEvent> {
 
+    public KeymasterStart(final NetworkService.Type networkServiceType) {
+        super(networkServiceType);
+    }
+
     @Override
     public void onApplicationEvent(final ContextRefreshedEvent event) {
         serviceOps.register(getNetworkService());
diff --git a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStartStop.java b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStartStop.java
similarity index 79%
rename from core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStartStop.java
rename to common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStartStop.java
index 7f52923..29a161b 100644
--- a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStartStop.java
+++ b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStartStop.java
@@ -16,24 +16,30 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.starter;
+package org.apache.syncope.common.keymaster.client.api.startstop;
 
 import org.apache.syncope.common.keymaster.client.api.ServiceOps;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 
-abstract class SyncopeCoreStartStop {
+abstract class KeymasterStartStop {
 
     @Autowired
     protected ServiceOps serviceOps;
 
+    protected final NetworkService.Type networkServiceType;
+
+    protected KeymasterStartStop(final NetworkService.Type networkServiceType) {
+        this.networkServiceType = networkServiceType;
+    }
+
     @Value("${service.discovery.address}")
     private String address;
 
     protected NetworkService getNetworkService() {
         NetworkService ns = new NetworkService();
-        ns.setType(NetworkService.Type.CORE);
+        ns.setType(networkServiceType);
         ns.setAddress(address);
         return ns;
     }
diff --git a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreShutdown.java b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStop.java
similarity index 78%
rename from core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreShutdown.java
rename to common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStop.java
index 256607e..7851a39 100644
--- a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreShutdown.java
+++ b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStop.java
@@ -16,19 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.starter;
+package org.apache.syncope.common.keymaster.client.api.startstop;
 
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextClosedEvent;
-import org.springframework.stereotype.Component;
 
-/**
- * Take care of cleanup actions needed by Syncope Core.
- */
-@Component
-public class SyncopeCoreShutdown extends SyncopeCoreStartStop
+public class KeymasterStop extends KeymasterStartStop
         implements ApplicationListener<ContextClosedEvent> {
 
+    public KeymasterStop(final NetworkService.Type networkServiceType) {
+        super(networkServiceType);
+    }
+
     @Override
     public void onApplicationEvent(final ContextClosedEvent event) {
         serviceOps.unregister(getNetworkService());
diff --git a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
index 9c9aaf4..8902bec 100644
--- a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
+++ b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java
@@ -20,6 +20,8 @@ package org.apache.syncope.core.starter;
 
 import java.io.IOException;
 import org.apache.cxf.spring.boot.autoconfigure.openapi.OpenApiAutoConfiguration;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -57,4 +59,14 @@ public class SyncopeCoreApplication extends SpringBootServletInitializer {
         pspc.setIgnoreUnresolvablePlaceholders(true);
         return pspc;
     }
+
+    @Bean
+    public SyncopeCoreStart keymasterStart() {
+        return new SyncopeCoreStart();
+    }
+
+    @Bean
+    public KeymasterStop keymasterStop() {
+        return new KeymasterStop(NetworkService.Type.CORE);
+    }
 }
diff --git a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStartup.java b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStart.java
similarity index 89%
rename from core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStartup.java
rename to core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStart.java
index f93de92..515fa13 100644
--- a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStartup.java
+++ b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStart.java
@@ -19,6 +19,8 @@
 package org.apache.syncope.core.starter;
 
 import java.util.Comparator;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
 import org.apache.syncope.core.persistence.api.DomainHolder;
 import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
 import org.slf4j.Logger;
@@ -28,20 +30,22 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.core.Ordered;
-import org.springframework.stereotype.Component;
 
 /**
  * Take care of all initializations needed by Syncope Core to run up and safe.
  */
-@Component
-public class SyncopeCoreStartup extends SyncopeCoreStartStop
+public class SyncopeCoreStart extends KeymasterStart
         implements ApplicationListener<ContextRefreshedEvent>, Ordered {
 
-    private static final Logger LOG = LoggerFactory.getLogger(SyncopeCoreStartup.class);
+    private static final Logger LOG = LoggerFactory.getLogger(SyncopeCoreStart.class);
 
     @Autowired
     private DomainHolder domainHolder;
 
+    public SyncopeCoreStart() {
+        super(NetworkService.Type.CORE);
+    }
+
     @Override
     public int getOrder() {
         return 0;
diff --git a/docker/core/src/main/resources/log4j2.xml b/docker/core/src/main/resources/log4j2.xml
index 1941154..063d123 100644
--- a/docker/core/src/main/resources/log4j2.xml
+++ b/docker/core/src/main/resources/log4j2.xml
@@ -108,6 +108,10 @@ under the License.
       <appender-ref ref="console"/>
       <appender-ref ref="main"/>
     </asyncLogger>
+    <asyncLogger name="liquibase" additivity="false" level="ERROR">
+      <appender-ref ref="console"/>
+      <appender-ref ref="main"/>
+    </asyncLogger>
     <asyncLogger name="org.apache.cocoon" additivity="false" level="ERROR">
       <appender-ref ref="console"/>
       <appender-ref ref="main"/>
diff --git a/docker/pom.xml b/docker/pom.xml
index db2f0d5..dd17d08 100644
--- a/docker/pom.xml
+++ b/docker/pom.xml
@@ -42,13 +42,6 @@ under the License.
     <rootpom.basedir>${basedir}/..</rootpom.basedir>
   </properties>
 
-  <modules>
-    <module>core</module>
-    <module>console</module>
-    <module>enduser</module>
-    <module>sra</module>
-  </modules>
-
   <build>
     <resources>
       <resource>
@@ -120,5 +113,12 @@ under the License.
       </build>
     </profile>    
   </profiles>
-  
+
+  <modules>
+    <module>core</module>
+    <module>console</module>
+    <module>enduser</module>
+    <module>sra</module>
+    <module>wa</module>
+  </modules>
 </project>
diff --git a/docker/src/main/resources/docker-compose/docker-compose-all.yml b/docker/src/main/resources/docker-compose/docker-compose-all.yml
index e3a9c25..29d6918 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-all.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-all.yml
@@ -24,7 +24,7 @@ version: '3.3'
 
 services:
    keymaster:
-     image: zookeeper:3.4.14
+     image: zookeeper:3.5.6
      restart: always
 
    db:
@@ -84,13 +84,27 @@ services:
        KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD:-}
        SERVICE_DISCOVERY_ADDRESS: http://syncope-enduser:8080/syncope-enduser/
 
+   syncope-wa:
+     depends_on:
+       - syncope
+       - keymaster
+     image: apache/syncope-wa:${SYNCOPE_VERSION}
+     ports:
+       - "48080:8080"
+     restart: always
+     environment:
+       KEYMASTER_ADDRESS: keymaster:2181
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME:-}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD:-}
+       SERVICE_DISCOVERY_ADDRESS: http://syncope-wa:8080/syncope-wa/
+
    syncope-sra:
      depends_on:
        - syncope
        - keymaster
      image: apache/syncope-sra:${SYNCOPE_VERSION}
      ports:
-       - "48080:8080"
+       - "58080:8080"
      restart: always
      environment:
        KEYMASTER_ADDRESS: keymaster:2181
diff --git a/docker/wa/LICENSE b/docker/wa/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/docker/wa/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
diff --git a/docker/wa/NOTICE b/docker/wa/NOTICE
new file mode 100644
index 0000000..3db7985
--- /dev/null
+++ b/docker/wa/NOTICE
@@ -0,0 +1,5 @@
+Apache Syncope
+Copyright 2012-2019 The Apache Software Foundation
+
+This product includes software developed by:
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/docker/wa/pom.xml b/docker/wa/pom.xml
new file mode 100644
index 0000000..13721b8
--- /dev/null
+++ b/docker/wa/pom.xml
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.syncope</groupId>
+    <artifactId>syncope-docker</artifactId>
+    <version>3.0.0-SNAPSHOT</version>
+  </parent>
+
+  <name>Apache Syncope Docker WA</name>
+  <description>Apache Syncope Docker WA</description>
+  <groupId>org.apache.syncope</groupId>
+  <artifactId>syncope-docker-wa</artifactId>
+  <packaging>war</packaging>
+
+  <properties>
+    <guava.version>28.2-jre</guava.version>
+    <opensaml.version>3.4.5</opensaml.version>
+    <bootstrap.version>4.4.1</bootstrap.version>
+    <slf4j.version>2.0.0-alpha1</slf4j.version>
+
+    <rootpom.basedir>${basedir}/../..</rootpom.basedir>
+  </properties>
+  
+  <dependencies>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-undertow</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.syncope</groupId>
+      <artifactId>syncope-wa</artifactId>
+      <version>${project.version}</version>
+    </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>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <inherited>false</inherited>
+        <configuration>
+          <webXml>${basedir}/../../fit/wa-reference/src/main/webapp/WEB-INF/web.xml</webXml>
+          <webResources>
+            <resource>
+              <directory>${basedir}</directory>
+              <targetPath>META-INF</targetPath>
+              <includes>
+                <include>LICENSE</include>
+                <include>NOTICE</include>
+              </includes>
+            </resource>
+          </webResources>
+          <outputDirectory>${project.build.outputDirectory}</outputDirectory>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <configuration>
+          <mainClass>org.apache.syncope.wa.SyncopeWAApplication</mainClass>
+          <layout>ZIP</layout>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>repackage</goal>
+            </goals>
+            <configuration>
+              <outputDirectory>${project.build.outputDirectory}</outputDirectory>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>io.fabric8</groupId>
+        <artifactId>docker-maven-plugin</artifactId>
+        <configuration>
+          <verbose>true</verbose>
+          <images>
+            <image>
+              <name>apache/syncope-wa:%v</name>
+              <build>
+                <dockerFileDir>${project.build.outputDirectory}</dockerFileDir>
+              </build>
+            </image>
+          </images>
+        </configuration>
+        <executions>
+          <execution>
+            <id>remove-syncope-wa</id>
+            <phase>initialize</phase>
+            <goals>
+              <goal>remove</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>build-syncope-wa</id>
+            <phase>package</phase>
+            <goals>
+              <goal>build</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+    
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+      </resource>
+    </resources>
+  </build>
+
+  <profiles>
+    <profile>
+      <id>apache-release</id>
+
+      <build>
+        <plugins>      
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-deploy-plugin</artifactId>
+            <configuration>
+              <skip>true</skip>
+            </configuration>
+          </plugin>           
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-source-plugin</artifactId>
+            <inherited>false</inherited>
+            <configuration>
+              <skipSource>true</skipSource>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+
+</project>
diff --git a/wa/src/main/resources/application.properties b/docker/wa/src/main/resources/Dockerfile
similarity index 53%
copy from wa/src/main/resources/application.properties
copy to docker/wa/src/main/resources/Dockerfile
index 487d11b..7edf8dc 100644
--- a/wa/src/main/resources/application.properties
+++ b/docker/wa/src/main/resources/Dockerfile
@@ -14,27 +14,26 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-spring.application.name=Apache Syncope ${syncope.version} WA
-spring.groovy.template.check-template-location=false
-spring.main.banner-mode=log
 
-server.port=8080
+# Cannot FROM adoptopenjdk/openjdk11-openj9:alpine-slim because it's headless, and fonts might be required
+FROM openjdk:11
+MAINTAINER dev@syncope.apache.org
 
-spring.http.encoding.charset=UTF-8
-spring.http.encoding.enabled=true
-spring.http.encoding.force=true
+RUN set -x
 
-server.servlet.contextPath=/syncope-wa
+RUN mkdir /opt/syncope
+RUN mkdir /opt/syncope/bin
+RUN mkdir /opt/syncope/conf
+RUN mkdir /opt/syncope/lib
+RUN mkdir /opt/syncope/log
 
-##
-# Allow configuration classes to override bean definitions from Spring Boot
-#
-spring.main.allow-bean-definition-overriding=true
-spring.main.lazy-initialization=false
+COPY *.properties /opt/syncope/conf/
+COPY log4j2.xml /opt/syncope/conf/
+
+COPY syncope-docker-wa-*war /opt/syncope/lib/syncope-wa.war
 
-service.discovery.address=http://localhost:8080/syncope-wa/
+COPY startup.sh /opt/syncope/bin
+RUN chmod 755 /opt/syncope/bin/startup.sh
+CMD ["/opt/syncope/bin/startup.sh"]
 
-# Conf directories
-conf.directory=${conf.directory}
-cas.standalone.configurationDirectory=${conf.directory}
-cas.authn.oidc.jwksFile=file:${conf.directory}/oidc.keystore
+EXPOSE 8080
diff --git a/wa/src/main/resources/application.properties b/docker/wa/src/main/resources/application.properties
similarity index 95%
copy from wa/src/main/resources/application.properties
copy to docker/wa/src/main/resources/application.properties
index 487d11b..254b399 100644
--- a/wa/src/main/resources/application.properties
+++ b/docker/wa/src/main/resources/application.properties
@@ -32,7 +32,7 @@ server.servlet.contextPath=/syncope-wa
 spring.main.allow-bean-definition-overriding=true
 spring.main.lazy-initialization=false
 
-service.discovery.address=http://localhost:8080/syncope-wa/
+service.discovery.address=${SERVICE_DISCOVERY_ADDRESS}
 
 # Conf directories
 conf.directory=${conf.directory}
diff --git a/wa/src/main/resources/application.properties b/docker/wa/src/main/resources/keymaster.properties
similarity index 52%
copy from wa/src/main/resources/application.properties
copy to docker/wa/src/main/resources/keymaster.properties
index 487d11b..14e8ca6 100644
--- a/wa/src/main/resources/application.properties
+++ b/docker/wa/src/main/resources/keymaster.properties
@@ -14,27 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-spring.application.name=Apache Syncope ${syncope.version} WA
-spring.groovy.template.check-template-location=false
-spring.main.banner-mode=log
-
-server.port=8080
-
-spring.http.encoding.charset=UTF-8
-spring.http.encoding.enabled=true
-spring.http.encoding.force=true
-
-server.servlet.contextPath=/syncope-wa
-
-##
-# Allow configuration classes to override bean definitions from Spring Boot
-#
-spring.main.allow-bean-definition-overriding=true
-spring.main.lazy-initialization=false
-
-service.discovery.address=http://localhost:8080/syncope-wa/
-
-# Conf directories
-conf.directory=${conf.directory}
-cas.standalone.configurationDirectory=${conf.directory}
-cas.authn.oidc.jwksFile=file:${conf.directory}/oidc.keystore
+keymaster.address=${KEYMASTER_ADDRESS}
+keymaster.username=${KEYMASTER_USERNAME}
+keymaster.password=${KEYMASTER_PASSWORD}
diff --git a/docker/wa/src/main/resources/log4j2.xml b/docker/wa/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..269180c
--- /dev/null
+++ b/docker/wa/src/main/resources/log4j2.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<configuration status="WARN">
+
+  <appenders>
+
+    <Console name="console" target="SYSTEM_OUT" follow="true">
+      <PatternLayout pattern="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} %highlight{${LOG_LEVEL_PATTERN:-%5p}}{FATAL=red blink, ERROR=red, WARN=yellow bold, INFO=green, DEBUG=green bold, TRACE=blue} [%11.11t] %style{%-60.60c{60}}{cyan} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
+    </Console>
+    
+  </appenders>
+  
+  <loggers>
+    
+    <asyncLogger name="org.apereo.cas" additivity="false" level="INFO">
+      <appender-ref ref="console"/>
+    </asyncLogger>
+    <asyncLogger name="org.apereo.inspektr.audit.support" additivity="false" level="INFO">
+      <appender-ref ref="console"/>
+    </asyncLogger>
+
+    <asyncLogger name="org.springframework" additivity="false" level="INFO">
+      <appender-ref ref="console"/>
+    </asyncLogger>
+
+    <asyncLogger name="org.apache.syncope.client.lib" additivity="false" level="OFF">
+      <appender-ref ref="console"/>
+    </asyncLogger>
+    <asyncLogger name="org.apache.syncope.wa" additivity="false" level="INFO">
+      <appender-ref ref="console"/>
+    </asyncLogger>
+
+    <asyncLogger name="org.apache.cxf" additivity="false" level="ERROR">
+      <appender-ref ref="console"/>
+    </asyncLogger>
+
+    <root level="INFO">
+      <appender-ref ref="console"/>
+    </root>
+    
+  </loggers>
+</configuration>
diff --git a/wa/src/main/resources/application.properties b/docker/wa/src/main/resources/startup.sh
old mode 100644
new mode 100755
similarity index 52%
copy from wa/src/main/resources/application.properties
copy to docker/wa/src/main/resources/startup.sh
index 487d11b..68c70da
--- a/wa/src/main/resources/application.properties
+++ b/docker/wa/src/main/resources/startup.sh
@@ -1,3 +1,5 @@
+#!/bin/sh
+
 # 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
@@ -14,27 +16,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-spring.application.name=Apache Syncope ${syncope.version} WA
-spring.groovy.template.check-template-location=false
-spring.main.banner-mode=log
-
-server.port=8080
-
-spring.http.encoding.charset=UTF-8
-spring.http.encoding.enabled=true
-spring.http.encoding.force=true
-
-server.servlet.contextPath=/syncope-wa
-
-##
-# Allow configuration classes to override bean definitions from Spring Boot
-#
-spring.main.allow-bean-definition-overriding=true
-spring.main.lazy-initialization=false
-
-service.discovery.address=http://localhost:8080/syncope-wa/
 
-# Conf directories
-conf.directory=${conf.directory}
-cas.standalone.configurationDirectory=${conf.directory}
-cas.authn.oidc.jwksFile=file:${conf.directory}/oidc.keystore
+export LOADER_PATH="/opt/syncope/conf,/opt/syncope/lib"
+java -Dfile.encoding=UTF-8 -server -Xms1536m -Xmx1536m -XX:NewSize=256m -XX:MaxNewSize=256m \
+ -XX:+DisableExplicitGC -Djava.security.egd=file:/dev/./urandom -jar /opt/syncope/lib/syncope-wa.war
diff --git a/wa/src/main/resources/application.properties b/docker/wa/src/main/resources/wa.properties
similarity index 64%
copy from wa/src/main/resources/application.properties
copy to docker/wa/src/main/resources/wa.properties
index 487d11b..a208075 100644
--- a/wa/src/main/resources/application.properties
+++ b/docker/wa/src/main/resources/wa.properties
@@ -14,25 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-spring.application.name=Apache Syncope ${syncope.version} WA
-spring.groovy.template.check-template-location=false
-spring.main.banner-mode=log
+anonymousUser=${anonymousUser}
+anonymousKey=${anonymousKey}
 
-server.port=8080
-
-spring.http.encoding.charset=UTF-8
-spring.http.encoding.enabled=true
-spring.http.encoding.force=true
-
-server.servlet.contextPath=/syncope-wa
-
-##
-# Allow configuration classes to override bean definitions from Spring Boot
-#
-spring.main.allow-bean-definition-overriding=true
-spring.main.lazy-initialization=false
-
-service.discovery.address=http://localhost:8080/syncope-wa/
+useGZIPCompression=true
 
 # Conf directories
 conf.directory=${conf.directory}
diff --git a/ext/self-keymaster/logic/src/main/java/org/apache/syncope/core/logic/NetworkServiceLogic.java b/ext/self-keymaster/logic/src/main/java/org/apache/syncope/core/logic/NetworkServiceLogic.java
index d97af30..554cb29 100644
--- a/ext/self-keymaster/logic/src/main/java/org/apache/syncope/core/logic/NetworkServiceLogic.java
+++ b/ext/self-keymaster/logic/src/main/java/org/apache/syncope/core/logic/NetworkServiceLogic.java
@@ -43,8 +43,8 @@ public class NetworkServiceLogic extends AbstractTransactionalLogic<EntityTO> {
     private SelfKeymasterEntityFactory entityFactory;
 
     private static NetworkService toNetworkService(
-        final NetworkService.Type serviceType,
-        final NetworkServiceEntity service) {
+            final NetworkService.Type serviceType,
+            final NetworkServiceEntity service) {
 
         NetworkService ns = new NetworkService();
         ns.setType(serviceType);
@@ -84,7 +84,7 @@ public class NetworkServiceLogic extends AbstractTransactionalLogic<EntityTO> {
     public void unregister(final NetworkService networkService) {
         serviceDAO.findAll(networkService.getType()).stream().
                 filter(service -> service.getAddress().equals(networkService.getAddress())).
-                findFirst().ifPresent(service -> serviceDAO.delete(service));
+                forEach(service -> serviceDAO.delete(service));
     }
 
     @Override
diff --git a/fit/console-reference/pom.xml b/fit/console-reference/pom.xml
index f80f55b..9e4fca4 100644
--- a/fit/console-reference/pom.xml
+++ b/fit/console-reference/pom.xml
@@ -56,11 +56,6 @@ under the License.
       <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>
diff --git a/fit/enduser-reference/pom.xml b/fit/enduser-reference/pom.xml
index 812478d..29c34fd 100644
--- a/fit/enduser-reference/pom.xml
+++ b/fit/enduser-reference/pom.xml
@@ -51,11 +51,6 @@ under the License.
       <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>
diff --git a/fit/wa-reference/pom.xml b/fit/wa-reference/pom.xml
index 7c08b20..e097196 100644
--- a/fit/wa-reference/pom.xml
+++ b/fit/wa-reference/pom.xml
@@ -50,6 +50,52 @@ under the License.
       <artifactId>syncope-wa</artifactId>
       <version>${project.version}</version>
     </dependency>
+
+    <dependency>
+      <groupId>org.apache.syncope.ext.self-keymaster</groupId>
+      <artifactId>syncope-ext-self-keymaster-client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <!-- TEST -->
+    <dependency>
+      <groupId>com.h2database</groupId>
+      <artifactId>h2</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.fit</groupId>
+      <artifactId>syncope-fit-build-tools</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.fit</groupId>
+      <artifactId>syncope-fit-core-reference</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.fit</groupId>
+      <artifactId>syncope-fit-console-reference</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.fit</groupId>
+      <artifactId>syncope-fit-enduser-reference</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
@@ -66,9 +112,15 @@ under the License.
               <downloadDir>${settings.localRepository}/org/codehaus/cargo/cargo-container-archives</downloadDir>
               <extractDir>${project.build.directory}/cargo/extract</extractDir>
             </zipUrlInstaller>
-            <timeout>300000</timeout>
+            <timeout>600000</timeout>
             <log>${cargo.log}</log>
             <output>${cargo.output}</output>
+            <dependencies>
+              <dependency>
+                <groupId>com.h2database</groupId>
+                <artifactId>h2</artifactId>
+              </dependency>
+            </dependencies>
           </container>
           <configuration>
             <properties>
@@ -79,6 +131,34 @@ under the License.
           </configuration>
           <deployables>
             <deployable>
+              <groupId>org.apache.syncope.fit</groupId>
+              <artifactId>syncope-fit-build-tools</artifactId>
+              <type>war</type>
+              <properties>
+                <context>syncope-fit-build-tools</context>
+              </properties>
+            </deployable>
+            <deployable>
+              <location>${basedir}/../core-reference/target/syncope-fit-core-reference-${project.version}</location>
+              <pingURL>http://localhost:${cargo.servlet.port}/syncope/index.html</pingURL>
+              <pingTimeout>${cargo.deployable.ping.timeout}</pingTimeout>
+              <properties>
+                <context>syncope</context>
+              </properties>
+            </deployable>
+            <deployable>
+              <location>${basedir}/../console-reference/target/syncope-fit-console-reference-${project.version}</location>
+              <properties>
+                <context>syncope-console</context>
+              </properties>
+            </deployable>
+            <deployable>
+              <location>${basedir}/../enduser-reference/target/syncope-fit-enduser-reference-${project.version}</location>
+              <properties>
+                <context>syncope-enduser</context>
+              </properties>
+            </deployable>
+            <deployable>
               <location>${project.build.directory}/${project.build.finalName}</location>
               <properties>
                 <context>syncope-wa</context>
@@ -113,13 +193,6 @@ under the License.
   
   <profiles>
     <profile>
-      <id>properties4cas</id>
-      <activation>
-        <activeByDefault>true</activeByDefault>
-      </activation>
-    </profile>
-
-    <profile>
       <id>skipTests</id>
     </profile>
 
@@ -143,6 +216,7 @@ under the License.
                 <properties>
                   <cargo.jvmargs>
                     -Dspring.profiles.active=embedded
+                    -Dwicket.core.settings.general.configuration-type=development
                     -Xdebug -Djaxb.debug=true -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
                     -XX:+CMSClassUnloadingEnabled -XX:+UseG1GC -Xmx1024m -Xms512m</cargo.jvmargs>
                 </properties>
@@ -152,6 +226,73 @@ under the License.
         </plugins>
       </build>
     </profile>
+    
+    <!-- requires JAVA_HOME set to the latest JDK from https://github.com/TravaOpenJDK/trava-jdk-11-dcevm -->
+    <profile>
+      <id>hotswap</id>
+      
+      <build>
+        <defaultGoal>clean verify cargo:run</defaultGoal>
+
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-antrun-plugin</artifactId>
+            <inherited>true</inherited>
+            <executions>
+              <execution>
+                <id>enableHotSwapForCoreAndConsoleAndEnduser</id>
+                <phase>package</phase>
+                <configuration>
+                  <target>                                               
+                    <copy file="${basedir}/../core-reference/target/test-classes/hotswap-agent.properties"
+                          tofile="${basedir}/../core-reference/target/syncope-fit-core-reference-${project.version}/WEB-INF/classes/hotswap-agent.properties"
+                          overwrite="true"/>
+                    <copy file="${basedir}/../console-reference/target/test-classes/hotswap-agent.properties"
+                          tofile="${basedir}/../console-reference/target/syncope-fit-console-reference-${project.version}/WEB-INF/classes/hotswap-agent.properties"
+                          overwrite="true"/>
+                    <copy file="${basedir}/../enduser-reference/target/test-classes/hotswap-agent.properties"
+                          tofile="${basedir}/../enduser-reference/target/syncope-fit-enduser-reference-${project.version}/WEB-INF/classes/hotswap-agent.properties"
+                          overwrite="true"/>
+                  </target>
+                </configuration>
+                <goals>
+                  <goal>run</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+
+          <plugin>
+            <groupId>org.codehaus.cargo</groupId>
+            <artifactId>cargo-maven2-plugin</artifactId>
+            <inherited>true</inherited>
+            <configuration>
+              <configuration>
+                <properties>
+                  <cargo.jvmargs>
+                    -Dspring.profiles.active=embedded
+                    -Dwicket.core.settings.general.configuration-type=development
+                    -javaagent:${java.home}/lib/hotswap/hotswap-agent.jar=autoHotswap=true,disablePlugin=Spring,disablePlugin=Hibernate,disablePlugin=CxfJAXRS
+                    -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
+                    -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -Xmx1024m -Xms512m</cargo.jvmargs>
+                </properties>
+              </configuration>
+            </configuration>
+          </plugin>
+        </plugins>
+        
+        <resources>
+          <resource>
+            <directory>src/test/resources</directory>
+            <filtering>true</filtering>
+            <includes>
+              <include>hotswap-agent.properties</include>
+            </includes>
+          </resource>
+        </resources>
+      </build>
+    </profile>
 
     <profile>
       <id>apache-release</id>
@@ -172,7 +313,7 @@ under the License.
             <configuration>
               <skipSource>true</skipSource>
             </configuration>
-          </plugin>
+          </plugin>          
         </plugins>
       </build>
     </profile>
diff --git a/wa/src/main/resources/application.properties b/fit/wa-reference/src/main/resources/keymaster.properties
similarity index 52%
copy from wa/src/main/resources/application.properties
copy to fit/wa-reference/src/main/resources/keymaster.properties
index 487d11b..033fe3b 100644
--- a/wa/src/main/resources/application.properties
+++ b/fit/wa-reference/src/main/resources/keymaster.properties
@@ -14,27 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-spring.application.name=Apache Syncope ${syncope.version} WA
-spring.groovy.template.check-template-location=false
-spring.main.banner-mode=log
-
-server.port=8080
-
-spring.http.encoding.charset=UTF-8
-spring.http.encoding.enabled=true
-spring.http.encoding.force=true
-
-server.servlet.contextPath=/syncope-wa
-
-##
-# Allow configuration classes to override bean definitions from Spring Boot
-#
-spring.main.allow-bean-definition-overriding=true
-spring.main.lazy-initialization=false
-
-service.discovery.address=http://localhost:8080/syncope-wa/
-
-# Conf directories
-conf.directory=${conf.directory}
-cas.standalone.configurationDirectory=${conf.directory}
-cas.authn.oidc.jwksFile=file:${conf.directory}/oidc.keystore
+keymaster.address=http://localhost:9080/syncope/rest/keymaster
+keymaster.username=${anonymousUser}
+keymaster.password=${anonymousKey}
diff --git a/fit/wa-reference/src/main/resources/log4j2.xml b/fit/wa-reference/src/main/resources/log4j2.xml
index 08196df..e184c60 100644
--- a/fit/wa-reference/src/main/resources/log4j2.xml
+++ b/fit/wa-reference/src/main/resources/log4j2.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8" ?>
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -17,118 +17,52 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-<!-- Specify the refresh internal in seconds. -->
-<Configuration monitorInterval="5" packages="org.apereo.cas.logging">
-  <Properties>
-    <Property name="baseDir">${log.directory}</Property>
-    <Property name="cas.log.level">info</Property>
-    <Property name="spring.webflow.log.level">warn</Property>
-    <Property name="spring.security.log.level">info</Property>
-    <Property name="spring.cloud.log.level">warn</Property>
-    <Property name="spring.web.log.level">warn</Property>
-    <Property name="spring.boot.log.level">warn</Property>
-    <Property name="ldap.log.level">warn</Property>
-    <Property name="pac4j.log.level">warn</Property>
-    <Property name="opensaml.log.level">warn</Property>
-    <Property name="hazelcast.log.level">warn</Property>
-  </Properties>
-  <Appenders>
-    <RollingFile name="file" fileName="${baseDir}/wa.log" append="true"
-                 filePattern="${baseDir}/wa-%d{yyyy-MM-dd-HH}-%i.log">
-      <PatternLayout pattern="%highlight{%d %p [%c] - &lt;%m&gt;%n}" />
-      <Policies>
-        <OnStartupTriggeringPolicy />
-        <SizeBasedTriggeringPolicy size="10 MB"/>
-        <TimeBasedTriggeringPolicy />
-      </Policies>
-      <DefaultRolloverStrategy max="5" compressionLevel="9">
-        <Delete basePath="${baseDir}" maxDepth="2">
-          <IfFileName glob="*/*.log.gz" />
-          <IfLastModified age="7d" />
-        </Delete>
-      </DefaultRolloverStrategy>
-    </RollingFile>
-    <RollingFile name="auditlogfile" fileName="${baseDir}/wa-audit.log" append="true"
-                 filePattern="${baseDir}/wa-audit-%d{yyyy-MM-dd-HH}-%i.log">
-      <PatternLayout pattern="%highlight{%d %p [%c] - %m%n}" />
+<configuration status="WARN">
+
+  <appenders>
+
+    <RollingRandomAccessFile name="main" fileName="${log.directory}/wa.log"
+                             filePattern="${log.directory}/wa-%d{yyyy-MM-dd}.log.gz"
+                             immediateFlush="false" append="true">
+      <PatternLayout>
+        <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
+      </PatternLayout>
       <Policies>
-        <OnStartupTriggeringPolicy />
-        <SizeBasedTriggeringPolicy size="10 MB"/>
-        <TimeBasedTriggeringPolicy />
+        <TimeBasedTriggeringPolicy/>
+        <SizeBasedTriggeringPolicy size="250 MB"/>
       </Policies>
-      <DefaultRolloverStrategy max="5" compressionLevel="9">
-        <Delete basePath="${baseDir}" maxDepth="2">
-          <IfFileName glob="*/*.log.gz" />
-          <IfLastModified age="7d" />
-        </Delete>
-      </DefaultRolloverStrategy>
-    </RollingFile>
+    </RollingRandomAccessFile>
 
-    <CasAppender name="casAudit">
-      <AppenderRef ref="auditlogfile" />
-    </CasAppender>
-    <CasAppender name="casFile">
-      <AppenderRef ref="file" />
-    </CasAppender>
-  </Appenders>
-  <Loggers>
-    <AsyncLogger name="org.apereo.cas" level="${sys:cas.log.level}" includeLocation="true" />
-    <AsyncLogger name="org.apereo.cas.services" level="${sys:cas.log.level}" includeLocation="true" />
-    <AsyncLogger name="org.apereo.spring" level="${sys:cas.log.level}" includeLocation="true" />
-    <AsyncLogger name="org.apereo.services.persondir" level="${sys:cas.log.level}" includeLocation="true" />
-    <AsyncLogger name="org.apereo.cas.web.flow" level="${sys:cas.log.level}" includeLocation="true" />
-    <AsyncLogger name="org.apereo.cas.web.CasWebApplication" level="${sys:cas.log.level}" includeLocation="true"/>
+  </appenders>
 
-    <AsyncLogger name="org.apereo.inspektr.audit.support" additivity="true" level="info" includeLocation="true">
-      <AppenderRef ref="casAudit"/>
-    </AsyncLogger>
+  <loggers>
 
-    <AsyncLogger name="org.springframework.boot" level="${sys:spring.boot.log.level}" />
-    <AsyncLogger name="org.springframework.boot.context.embedded" level="info" />
-    <AsyncLogger name="org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration" level="${sys:spring.security.log.level}" />
-    <AsyncLogger name="org.springframework.boot.autoconfigure.security" level="${sys:spring.security.log.level}" />
-    <AsyncLogger name="org.springframework.boot.devtools" level="debug" />
-        
-    <AsyncLogger name="org.springframework" level="warn" includeLocation="true" />
-    <AsyncLogger name="org.springframework.webflow" level="${sys:spring.webflow.log.level}" includeLocation="true"/>
-    <AsyncLogger name="org.springframework.aop" level="warn" includeLocation="true" />
-    <AsyncLogger name="org.springframework.session" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.springframework.scheduling" level="info" includeLocation="true"/>
-    <AsyncLogger name="org.springframework.cloud.vault" level="warn" includeLocation="true" />
-    <AsyncLogger name="org.springframework.web.client" level="warn" includeLocation="true" />
-    <AsyncLogger name="org.springframework.security" level="${sys:spring.security.log.level}" includeLocation="true"/>
-    <AsyncLogger name="org.springframework.cloud" level="${sys:spring.cloud.log.level}" includeLocation="true"/>
-    <AsyncLogger name="org.springframework.amqp" level="error" />
-    <AsyncLogger name="org.springframework.integration" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.springframework.messaging" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.springframework.web" level="${sys:spring.web.log.level}" includeLocation="true"/>
-    <AsyncLogger name="org.springframework.orm.jpa" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.springframework.scheduling" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.springframework.context.annotation" level="off" includeLocation="true"/>
-    <AsyncLogger name="org.springframework.web.socket" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter" level="trace" includeLocation="true"/>
+    <asyncLogger name="org.apereo.cas" additivity="false" level="INFO">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+    <asyncLogger name="org.apereo.inspektr.audit.support" additivity="false" level="INFO">
+      <appender-ref ref="main"/>
+    </asyncLogger>
 
-    <AsyncLogger name="com.couchbase" level="warn" includeLocation="true" />
-    <AsyncLogger name="org.apache" level="error" includeLocation="true"/>
-    <AsyncLogger name="com.netflix" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.quartz" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.thymeleaf" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.pac4j" level="${sys:pac4j.log.level}" includeLocation="true"/>
+    <asyncLogger name="org.springframework" additivity="false" level="INFO">
+      <appender-ref ref="main"/>
+    </asyncLogger>
 
-    <AsyncLogger name="org.opensaml" level="${sys:opensaml.log.level}" includeLocation="true"/>
-    <AsyncLogger name="PROTOCOL_MESSAGE" level="${sys:opensaml.log.level}" includeLocation="true" />
+    <asyncLogger name="org.apache.syncope.client.lib" additivity="false" level="OFF">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+    <asyncLogger name="org.apache.syncope.wa" additivity="false" level="INFO">
+      <appender-ref ref="main"/>
+    </asyncLogger>
 
-    <AsyncLogger name="net.sf.ehcache" level="warn" includeLocation="true"/>
-    <AsyncLogger name="net.jradius" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.openid4java" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.ldaptive" level="${sys:ldap.log.level}" includeLocation="true"/>
-    <AsyncLogger name="com.hazelcast" level="${sys:hazelcast.log.level}" includeLocation="true"/>
-    <AsyncLogger name="org.jasig.spring" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.apache.cxf" level="warn" includeLocation="true"/>
-    <AsyncLogger name="org.apache.http" level="warn" includeLocation="true"/>
+    <asyncLogger name="org.apache.cxf" additivity="false" level="ERROR">
+      <appender-ref ref="main"/>
+    </asyncLogger>
 
-    <AsyncRoot level="warn">
-      <AppenderRef ref="casFile"/>
-    </AsyncRoot>
-  </Loggers>
-</Configuration>
+    <root level="INFO">
+      <appender-ref ref="main"/>
+    </root>
+  
+  </loggers>
+  
+</configuration>
diff --git a/wa/src/main/resources/application.properties b/fit/wa-reference/src/main/resources/wa.properties
similarity index 64%
copy from wa/src/main/resources/application.properties
copy to fit/wa-reference/src/main/resources/wa.properties
index 487d11b..a208075 100644
--- a/wa/src/main/resources/application.properties
+++ b/fit/wa-reference/src/main/resources/wa.properties
@@ -14,25 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-spring.application.name=Apache Syncope ${syncope.version} WA
-spring.groovy.template.check-template-location=false
-spring.main.banner-mode=log
+anonymousUser=${anonymousUser}
+anonymousKey=${anonymousKey}
 
-server.port=8080
-
-spring.http.encoding.charset=UTF-8
-spring.http.encoding.enabled=true
-spring.http.encoding.force=true
-
-server.servlet.contextPath=/syncope-wa
-
-##
-# Allow configuration classes to override bean definitions from Spring Boot
-#
-spring.main.allow-bean-definition-overriding=true
-spring.main.lazy-initialization=false
-
-service.discovery.address=http://localhost:8080/syncope-wa/
+useGZIPCompression=true
 
 # Conf directories
 conf.directory=${conf.directory}
diff --git a/wa/src/main/resources/application.properties b/fit/wa-reference/src/test/resources/hotswap-agent.properties
similarity index 52%
copy from wa/src/main/resources/application.properties
copy to fit/wa-reference/src/test/resources/hotswap-agent.properties
index 487d11b..d0bf167 100644
--- a/wa/src/main/resources/application.properties
+++ b/fit/wa-reference/src/test/resources/hotswap-agent.properties
@@ -14,27 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-spring.application.name=Apache Syncope ${syncope.version} WA
-spring.groovy.template.check-template-location=false
-spring.main.banner-mode=log
 
-server.port=8080
+LOGGER=error
 
-spring.http.encoding.charset=UTF-8
-spring.http.encoding.enabled=true
-spring.http.encoding.force=true
+autoHotswap=true
 
-server.servlet.contextPath=/syncope-wa
-
-##
-# Allow configuration classes to override bean definitions from Spring Boot
-#
-spring.main.allow-bean-definition-overriding=true
-spring.main.lazy-initialization=false
-
-service.discovery.address=http://localhost:8080/syncope-wa/
-
-# Conf directories
-conf.directory=${conf.directory}
-cas.standalone.configurationDirectory=${conf.directory}
-cas.authn.oidc.jwksFile=file:${conf.directory}/oidc.keystore
+extraClasspath=\
+${basedir}/../../wa/target/classes/org
diff --git a/sra/pom.xml b/sra/pom.xml
index 1139819..6644b0e 100644
--- a/sra/pom.xml
+++ b/sra/pom.xml
@@ -166,6 +166,7 @@ under the License.
         <directory>src/main/resources</directory>
         <filtering>true</filtering>
       </resource>
+
       <resource>
         <directory>${basedir}/../src/main/resources</directory>
         <filtering>true</filtering>
@@ -208,6 +209,11 @@ under the License.
           <artifactId>cxf-rt-transports-http-netty-server</artifactId>
           <scope>compile</scope>
         </dependency>
+
+        <dependency>
+          <groupId>org.springframework.boot</groupId>
+          <artifactId>spring-boot-devtools</artifactId>
+        </dependency>
       </dependencies>
 
       <build>
diff --git a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java
index 5a2e05e..3f56eb4 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java
+++ b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java
@@ -19,6 +19,9 @@
 package org.apache.syncope.sra;
 
 import java.util.Objects;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
+import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringApplication;
@@ -62,6 +65,16 @@ public class SyncopeSRAApplication implements EnvironmentAware {
     }
 
     @Bean
+    public KeymasterStart keymasterStart() {
+        return new KeymasterStart(NetworkService.Type.SRA);
+    }
+
+    @Bean
+    public KeymasterStop keymasterStop() {
+        return new KeymasterStop(NetworkService.Type.SRA);
+    }
+
+    @Bean
     public RouteLocator routes(final RouteLocatorBuilder builder) {
         return () -> Flux.fromIterable(provider.fetch()).map(Route.AbstractBuilder::build);
     }
diff --git a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAShutdown.java b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAShutdown.java
deleted file mode 100644
index 4ed772c..0000000
--- a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAShutdown.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.sra;
-
-import org.springframework.context.ApplicationListener;
-import org.springframework.context.event.ContextClosedEvent;
-import org.springframework.stereotype.Component;
-
-@Component
-public class SyncopeSRAShutdown extends SyncopeSRAStartStop
-        implements ApplicationListener<ContextClosedEvent> {
-
-    @Override
-    public void onApplicationEvent(final ContextClosedEvent event) {
-        serviceOps.unregister(getNetworkService());
-    }
-}
diff --git a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartStop.java b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartStop.java
deleted file mode 100644
index 926a608..0000000
--- a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartStop.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.sra;
-
-import org.apache.syncope.common.keymaster.client.api.ServiceOps;
-import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-
-abstract class SyncopeSRAStartStop {
-
-    @Autowired
-    protected ServiceOps serviceOps;
-
-    @Value("${service.discovery.address}")
-    private String address;
-
-    protected NetworkService getNetworkService() {
-        NetworkService ns = new NetworkService();
-        ns.setType(NetworkService.Type.SRA);
-        ns.setAddress(address);
-        return ns;
-    }
-}
diff --git a/wa/pom.xml b/wa/pom.xml
index 2bdb8b0..13d69aa 100644
--- a/wa/pom.xml
+++ b/wa/pom.xml
@@ -43,6 +43,18 @@ under the License.
 
   <dependencies>
     <dependency>
+      <groupId>org.apache.syncope.common.keymaster</groupId>
+      <artifactId>syncope-common-keymaster-client-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.syncope.client.am</groupId>
+      <artifactId>syncope-client-am-lib</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
       <groupId>org.glassfish</groupId>
       <artifactId>javax.el</artifactId>
     </dependency>
@@ -175,15 +187,30 @@ under the License.
     </dependency>
 
     <dependency>
+      <groupId>org.apache.syncope.common.keymaster</groupId>
+      <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.curator</groupId>
+      <artifactId>curator-test</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-tomcat</artifactId>
+      <scope>test</scope>      
+    </dependency>
+    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
-      <exclusions>
-        <exclusion>
-          <groupId>org.junit.vintage</groupId>
-          <artifactId>junit-vintage-engine</artifactId>
-        </exclusion>
-      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <scope>test</scope>
     </dependency>
   </dependencies>
 
@@ -209,24 +236,105 @@ under the License.
         </includes>
       </resource>
     </resources>
+
+    <testResources>
+      <testResource>
+        <directory>${basedir}/src/test/resources</directory>
+        <filtering>true</filtering>
+      </testResource>
+    </testResources>
   </build>
 
   <profiles>
     <profile>
       <id>debug</id>
 
+      <properties>
+        <skipTests>true</skipTests>
+      </properties>
+
       <dependencies>
         <dependency>
           <groupId>org.springframework.boot</groupId>
+          <artifactId>spring-boot-starter-undertow</artifactId>
+        </dependency>
+
+        <dependency>
+          <groupId>org.apache.syncope.common.keymaster</groupId>
+          <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
+          <version>${project.version}</version>
+          <scope>compile</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.curator</groupId>
+          <artifactId>curator-test</artifactId>
+          <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+          <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-devtools</artifactId>
         </dependency>
       </dependencies>
 
       <build>
-        <defaultGoal>clean package spring-boot:run</defaultGoal>
+        <defaultGoal>clean package io.fabric8:docker-maven-plugin:start spring-boot:run</defaultGoal>
 
         <plugins>
           <plugin>
+            <groupId>io.fabric8</groupId>
+            <artifactId>docker-maven-plugin</artifactId>
+            <configuration>
+              <images>
+                <image>
+                  <name>zookeeper:${zookeeper.version}</name>
+                  <run>
+                    <ports>
+                      <port>2181:2181</port>
+                    </ports>
+                    <volumes>
+                      <bind>
+                        <volume>${project.build.testOutputDirectory}/zoo.cfg:/conf/zoo.cfg</volume>
+                        <volume>${project.build.testOutputDirectory}/java.env:/conf/java.env</volume>
+                        <volume>${project.build.testOutputDirectory}/server-jaas.conf:/conf/server-jaas.conf</volume>
+                        <volume>${project.build.testOutputDirectory}/client-jaas.conf:/conf/client-jaas.conf</volume>
+                      </bind>
+                    </volumes>
+                  </run>
+                </image>
+              </images>
+            </configuration>
+          </plugin>
+
+          <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>build-helper-maven-plugin</artifactId>
+            <executions>
+              <execution>
+                <goals>
+                  <goal>add-source</goal>
+                </goals>
+                <configuration>
+                  <sources>
+                    <source>${basedir}/src/test/java</source>
+                  </sources>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <configuration>
+              <excludes>
+                <exclude>**/org/apache/syncope/wa/**Test.java</exclude>
+                <exclude>**/org/apache/syncope/wa/**Zookeeper*.java</exclude>
+              </excludes>
+            </configuration>
+          </plugin>
+
+          <plugin>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-maven-plugin</artifactId>
             <configuration>
@@ -244,11 +352,16 @@ under the License.
         <resources>
           <resource>
             <directory>${basedir}/src/test/resources</directory>
-            <includes>
-              <include>application-debug.properties</include>
-            </includes>
+            <filtering>true</filtering>
           </resource>
         </resources>
+
+        <testResources>
+          <testResource>
+            <directory>${basedir}/../common/keymaster/client-zookeeper/src/main/resources</directory>
+            <filtering>true</filtering>
+          </testResource>
+        </testResources>
       </build>
     </profile>
 
diff --git a/wa/src/main/java/org/apache/syncope/wa/SyncopeWAApplication.java b/wa/src/main/java/org/apache/syncope/wa/SyncopeWAApplication.java
index 970d890..16d985d 100644
--- a/wa/src/main/java/org/apache/syncope/wa/SyncopeWAApplication.java
+++ b/wa/src/main/java/org/apache/syncope/wa/SyncopeWAApplication.java
@@ -19,6 +19,9 @@
 package org.apache.syncope.wa;
 
 import org.apache.commons.lang.StringUtils;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
+import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
 import org.apereo.cas.configuration.CasConfigurationProperties;
 import org.apereo.cas.util.AsciiArtUtils;
 import org.apereo.cas.util.DateTimeUtils;
@@ -41,12 +44,16 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.boot.context.event.ApplicationReadyEvent;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.context.annotation.PropertySource;
 import org.springframework.context.event.EventListener;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 
+@PropertySource("classpath:wa.properties")
+@PropertySource(value = "file:${conf.directory}/wa.properties", ignoreResourceNotFound = true)
 @SpringBootApplication(exclude = {
     HibernateJpaAutoConfiguration.class,
     JerseyAutoConfiguration.class,
@@ -84,4 +91,14 @@ public class SyncopeWAApplication extends SpringBootServletInitializer {
         AsciiArtUtils.printAsciiArtReady(LOG, StringUtils.EMPTY);
         LOG.info("Ready to process requests @ [{}]", DateTimeUtils.zonedDateTimeOf(event.getTimestamp()));
     }
+
+    @Bean
+    public KeymasterStart keymasterStart() {
+        return new KeymasterStart(NetworkService.Type.WA);
+    }
+
+    @Bean
+    public KeymasterStop keymasterStop() {
+        return new KeymasterStop(NetworkService.Type.WA);
+    }
 }
diff --git a/wa/src/main/resources/application.properties b/wa/src/main/resources/application.properties
index 487d11b..53ae4b0 100644
--- a/wa/src/main/resources/application.properties
+++ b/wa/src/main/resources/application.properties
@@ -26,6 +26,9 @@ spring.http.encoding.force=true
 
 server.servlet.contextPath=/syncope-wa
 
+management.endpoints.web.exposure.include=health,loggers
+management.endpoint.health.show-details=always
+
 ##
 # Allow configuration classes to override bean definitions from Spring Boot
 #
@@ -33,8 +36,3 @@ spring.main.allow-bean-definition-overriding=true
 spring.main.lazy-initialization=false
 
 service.discovery.address=http://localhost:8080/syncope-wa/
-
-# Conf directories
-conf.directory=${conf.directory}
-cas.standalone.configurationDirectory=${conf.directory}
-cas.authn.oidc.jwksFile=file:${conf.directory}/oidc.keystore
diff --git a/wa/src/main/resources/log4j2.xml b/wa/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..e184c60
--- /dev/null
+++ b/wa/src/main/resources/log4j2.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<configuration status="WARN">
+
+  <appenders>
+
+    <RollingRandomAccessFile name="main" fileName="${log.directory}/wa.log"
+                             filePattern="${log.directory}/wa-%d{yyyy-MM-dd}.log.gz"
+                             immediateFlush="false" append="true">
+      <PatternLayout>
+        <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
+      </PatternLayout>
+      <Policies>
+        <TimeBasedTriggeringPolicy/>
+        <SizeBasedTriggeringPolicy size="250 MB"/>
+      </Policies>
+    </RollingRandomAccessFile>
+
+  </appenders>
+
+  <loggers>
+
+    <asyncLogger name="org.apereo.cas" additivity="false" level="INFO">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+    <asyncLogger name="org.apereo.inspektr.audit.support" additivity="false" level="INFO">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+
+    <asyncLogger name="org.springframework" additivity="false" level="INFO">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+
+    <asyncLogger name="org.apache.syncope.client.lib" additivity="false" level="OFF">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+    <asyncLogger name="org.apache.syncope.wa" additivity="false" level="INFO">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+
+    <asyncLogger name="org.apache.cxf" additivity="false" level="ERROR">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+
+    <root level="INFO">
+      <appender-ref ref="main"/>
+    </root>
+  
+  </loggers>
+  
+</configuration>
diff --git a/wa/src/main/resources/static/images/favicon.png b/wa/src/main/resources/static/images/favicon.png
new file mode 100644
index 0000000..aa2f3e2
Binary files /dev/null and b/wa/src/main/resources/static/images/favicon.png differ
diff --git a/wa/src/main/resources/templates/layout.html b/wa/src/main/resources/templates/layout.html
index 846f0d5..ad83fbc 100644
--- a/wa/src/main/resources/templates/layout.html
+++ b/wa/src/main/resources/templates/layout.html
@@ -24,7 +24,7 @@ under the License.
     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
 
-    <title layout:title-pattern="$CONTENT_TITLE - $LAYOUT_TITLE">Apache Syncope</title>
+    <title layout:title-pattern="$LAYOUT_TITLE">Apache Syncope</title>
 
     <!--/* Core CAS CSS */-->
     <link rel="stylesheet" type="text/css" href="../static/css/normalize.css" th:href="@{#{webjars.normalize.css}}" />
@@ -33,7 +33,7 @@ under the License.
     <link rel="stylesheet" type="text/css" href="../static/css/mdi-font.css" th:href="@{#{webjars.mdi-font.css}}" />
     <link rel="stylesheet" type="text/css" href="../static/css/cas.css" th:href="@{${#themes.code('cas.standard.css.file')}}"/>
 
-    <link rel="icon" th:href="@{/favicon.ico}" type="image/x-icon"/>
+    <link rel="shortcut icon" th:href="@{'/images/favicon.png'}" type="image/png"/>
 </head>
 
 <body>
diff --git a/wa/src/main/resources/application.properties b/wa/src/main/resources/wa.properties
similarity index 64%
copy from wa/src/main/resources/application.properties
copy to wa/src/main/resources/wa.properties
index 487d11b..a208075 100644
--- a/wa/src/main/resources/application.properties
+++ b/wa/src/main/resources/wa.properties
@@ -14,25 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-spring.application.name=Apache Syncope ${syncope.version} WA
-spring.groovy.template.check-template-location=false
-spring.main.banner-mode=log
+anonymousUser=${anonymousUser}
+anonymousKey=${anonymousKey}
 
-server.port=8080
-
-spring.http.encoding.charset=UTF-8
-spring.http.encoding.enabled=true
-spring.http.encoding.force=true
-
-server.servlet.contextPath=/syncope-wa
-
-##
-# Allow configuration classes to override bean definitions from Spring Boot
-#
-spring.main.allow-bean-definition-overriding=true
-spring.main.lazy-initialization=false
-
-service.discovery.address=http://localhost:8080/syncope-wa/
+useGZIPCompression=true
 
 # Conf directories
 conf.directory=${conf.directory}
diff --git a/wa/src/test/java/org/apache/syncope/wa/SyncopeWATest.java b/wa/src/test/java/org/apache/syncope/wa/SyncopeWATest.java
new file mode 100644
index 0000000..c4ed578
--- /dev/null
+++ b/wa/src/test/java/org/apache/syncope/wa/SyncopeWATest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.wa;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+import javax.ws.rs.core.HttpHeaders;
+import org.apache.http.Consts;
+import org.apache.http.Header;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.test.context.ContextConfiguration;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {
+    "cas.authn.accept.users=mrossi::password"
+})
+@ContextConfiguration(initializers = ZookeeperTestingServer.class)
+public class SyncopeWATest {
+
+    @LocalServerPort
+    private int port;
+
+    private String getLoginURL() {
+        return "http://localhost:" + port + "/syncope-wa/login";
+    }
+
+    @Test
+    public void loginLogout() throws IOException {
+        CloseableHttpClient httpclient = HttpClients.createDefault();
+        HttpClientContext context = HttpClientContext.create();
+        context.setCookieStore(new BasicCookieStore());
+
+        // 1. first GET to fetch execution
+        HttpGet get = new HttpGet(getLoginURL());
+        get.addHeader(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en-US,en;q=0.5"));
+        CloseableHttpResponse response = httpclient.execute(get, context);
+        assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+
+        String responseBody = EntityUtils.toString(response.getEntity());
+        int begin = responseBody.indexOf("name=\"execution\" value=\"");
+        assertNotEquals(-1, begin);
+        int end = responseBody.indexOf("\"/><input type=\"hidden\" name=\"_eventId\"");
+        assertNotEquals(-1, end);
+
+        String execution = responseBody.substring(begin + 24, end);
+        assertNotNull(execution);
+
+        // 2. then POST to authenticate
+        List<NameValuePair> form = new ArrayList<>();
+        form.add(new BasicNameValuePair("_eventId", "submit"));
+        form.add(new BasicNameValuePair("execution", execution));
+        form.add(new BasicNameValuePair("username", "mrossi"));
+        form.add(new BasicNameValuePair("password", "password"));
+        form.add(new BasicNameValuePair("geolocation", ""));
+
+        HttpPost post = new HttpPost(getLoginURL());
+        post.addHeader(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en-US,en;q=0.5"));
+        post.setEntity(new UrlEncodedFormEntity(form, Consts.UTF_8));
+        response = httpclient.execute(post, context);
+
+        // 3. check authentication results
+        assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+
+        Header[] cookie = response.getHeaders("Set-Cookie");
+        assertNotNull(cookie);
+        assertTrue(cookie.length > 0);
+        assertEquals(1, Stream.of(cookie).filter(item -> item.getValue().startsWith("TGC")).count());
+
+        String body = EntityUtils.toString(response.getEntity());
+        assertTrue(body.contains("Log In Successful"));
+        assertTrue(body.contains("have successfully logged into the Central Authentication Service"));
+
+        // 4. logout
+        HttpGet logout = new HttpGet(getLoginURL().replace("login", "logout"));
+        logout.addHeader(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en-US,en;q=0.5"));
+        response = httpclient.execute(logout, context);
+        assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+
+        body = EntityUtils.toString(response.getEntity());
+        assertTrue(body.contains("Logout successful"));
+        assertTrue(body.contains("have successfully logged out of the Central Authentication Service"));
+    }
+
+    @Test
+    public void loginError() throws IOException {
+        CloseableHttpClient httpclient = HttpClients.createDefault();
+        HttpClientContext context = HttpClientContext.create();
+        context.setCookieStore(new BasicCookieStore());
+
+        // 1. first GET to fetch execution
+        HttpGet get = new HttpGet(getLoginURL());
+        get.addHeader(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en-US,en;q=0.5"));
+        CloseableHttpResponse response = httpclient.execute(get, context);
+        assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+
+        String responseBody = EntityUtils.toString(response.getEntity());
+        int begin = responseBody.indexOf("name=\"execution\" value=\"");
+        assertNotEquals(-1, begin);
+        int end = responseBody.indexOf("\"/><input type=\"hidden\" name=\"_eventId\"");
+        assertNotEquals(-1, end);
+
+        String execution = responseBody.substring(begin + 24, end);
+        assertNotNull(execution);
+
+        // 2. then POST to authenticate
+        List<NameValuePair> form = new ArrayList<>();
+        form.add(new BasicNameValuePair("_eventId", "submit"));
+        form.add(new BasicNameValuePair("execution", execution));
+        form.add(new BasicNameValuePair("username", "mrossi"));
+        form.add(new BasicNameValuePair("password", "WRONG"));
+        form.add(new BasicNameValuePair("geolocation", ""));
+
+        HttpPost post = new HttpPost(getLoginURL());
+        post.addHeader(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "en-US,en;q=0.5"));
+        post.setEntity(new UrlEncodedFormEntity(form, Consts.UTF_8));
+        response = httpclient.execute(post, context);
+
+        // 3. check authentication results
+        assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getStatusLine().getStatusCode());
+    }
+}
diff --git a/wa/src/test/java/org/apache/syncope/wa/ZookeeperTestingServer.java b/wa/src/test/java/org/apache/syncope/wa/ZookeeperTestingServer.java
new file mode 100644
index 0000000..956e6f3
--- /dev/null
+++ b/wa/src/test/java/org/apache/syncope/wa/ZookeeperTestingServer.java
@@ -0,0 +1,80 @@
+/*
+ * 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.wa;
+
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import org.apache.curator.test.InstanceSpec;
+import org.apache.curator.test.TestingServer;
+import org.apache.zookeeper.server.auth.DigestLoginModule;
+import org.apache.zookeeper.server.auth.SASLAuthenticationProvider;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+
+public class ZookeeperTestingServer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
+
+    @Override
+    public void initialize(final ConfigurableApplicationContext ctx) {
+        AtomicReference<String> username = new AtomicReference<>();
+        AtomicReference<String> password = new AtomicReference<>();
+        try (InputStream propStream = getClass().getResourceAsStream("/keymaster.properties")) {
+            Properties props = new Properties();
+            props.load(propStream);
+
+            username.set(props.getProperty("keymaster.username"));
+            password.set(props.getProperty("keymaster.password"));
+        } catch (Exception e) {
+            throw new IllegalStateException("Could not load /keymaster.properties", e);
+        }
+
+        Configuration.setConfiguration(new Configuration() {
+
+            private final AppConfigurationEntry[] entries = {
+                new AppConfigurationEntry(
+                DigestLoginModule.class.getName(),
+                AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+                Map.of(
+                "user_" + username.get(), password.get()
+                ))
+            };
+
+            @Override
+            public AppConfigurationEntry[] getAppConfigurationEntry(final String name) {
+                return entries;
+            }
+        });
+
+        Map<String, Object> customProperties = new HashMap<>();
+        customProperties.put("authProvider.1", SASLAuthenticationProvider.class.getName());
+        InstanceSpec spec = new InstanceSpec(null, 2181, -1, -1, true, 1, -1, -1, customProperties);
+
+        try {
+            new TestingServer(spec, true);
+        } catch (Exception e) {
+            fail(e);
+        }
+    }
+}
diff --git a/wa/src/main/resources/application.properties b/wa/src/test/resources/keymaster.properties
similarity index 52%
copy from wa/src/main/resources/application.properties
copy to wa/src/test/resources/keymaster.properties
index 487d11b..f374d8c 100644
--- a/wa/src/main/resources/application.properties
+++ b/wa/src/test/resources/keymaster.properties
@@ -14,27 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-spring.application.name=Apache Syncope ${syncope.version} WA
-spring.groovy.template.check-template-location=false
-spring.main.banner-mode=log
-
-server.port=8080
-
-spring.http.encoding.charset=UTF-8
-spring.http.encoding.enabled=true
-spring.http.encoding.force=true
-
-server.servlet.contextPath=/syncope-wa
-
-##
-# Allow configuration classes to override bean definitions from Spring Boot
-#
-spring.main.allow-bean-definition-overriding=true
-spring.main.lazy-initialization=false
-
-service.discovery.address=http://localhost:8080/syncope-wa/
-
-# Conf directories
-conf.directory=${conf.directory}
-cas.standalone.configurationDirectory=${conf.directory}
-cas.authn.oidc.jwksFile=file:${conf.directory}/oidc.keystore
+keymaster.address=127.0.0.1:2181
+keymaster.username=${anonymousUser}
+keymaster.password=${anonymousKey}