You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@metron.apache.org by GitBox <gi...@apache.org> on 2019/01/03 18:14:14 UTC

[GitHub] asfgit closed pull request #1275: METRON-1878: Add Metron as a Knox service

asfgit closed pull request #1275: METRON-1878: Add Metron as a Knox service
URL: https://github.com/apache/metron/pull/1275
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/dependencies_with_url.csv b/dependencies_with_url.csv
index 745e3c9f8a..5856a3a8f8 100644
--- a/dependencies_with_url.csv
+++ b/dependencies_with_url.csv
@@ -495,3 +495,6 @@ org.fusesource.jansi:jansi:jar:1.16:compile,ASLv2,https://github.com/fusesource/
 com.vividsolutions:jts:jar:1.13:compile
 joda-time:joda-time:jar:2.10:compile
 org.elasticsearch:securesm:jar:1.2:compile
+com.github.stephenc.jcip:jcip-annotations:jar:1.0-1:compile,ASLv2,http://stephenc.github.io/jcip-annotations/
+com.nimbusds:nimbus-jose-jwt:jar:4.41.2:compile,ASLv2,https://bitbucket.org/connect2id/nimbus-jose-jwt/wiki/Home
+
diff --git a/metron-deployment/packaging/docker/rpm-docker/SPECS/metron.spec b/metron-deployment/packaging/docker/rpm-docker/SPECS/metron.spec
index 294e24b69a..8f0cd9d102 100644
--- a/metron-deployment/packaging/docker/rpm-docker/SPECS/metron.spec
+++ b/metron-deployment/packaging/docker/rpm-docker/SPECS/metron.spec
@@ -467,8 +467,17 @@ This package installs the Metron Rest %{metron_home}
 %dir %{metron_home}/bin
 %dir %{metron_home}/lib
 %{metron_home}/config/rest_application.yml
+%{metron_home}/config/knox/conf/topologies/metron.xml
+%{metron_home}/config/knox/conf/topologies/metronsso.xml
+%{metron_home}/config/knox/data/services/alerts/rewrite.xml
+%{metron_home}/config/knox/data/services/alerts/service.xml
+%{metron_home}/config/knox/data/services/management/rewrite.xml
+%{metron_home}/config/knox/data/services/management/service.xml
+%{metron_home}/config/knox/data/services/rest/rewrite.xml
+%{metron_home}/config/knox/data/services/rest/service.xml
 %{metron_home}/bin/metron-rest.sh
 %{metron_home}/bin/pcap_to_pdml.sh
+%{metron_home}/bin/install_metron_knox.sh
 %attr(0644,root,root) %{metron_home}/lib/metron-rest-%{full_version}.jar
 
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -520,6 +529,7 @@ This package installs the Metron Management UI %{metron_home}
 %attr(0644,root,root) %{metron_home}/web/management-ui/assets/fonts/Roboto/LICENSE.txt
 %attr(0644,root,root) %{metron_home}/web/management-ui/assets/fonts/Roboto/*.ttf
 %attr(0644,root,root) %{metron_home}/web/management-ui/assets/images/*
+%attr(0644,root,root) %{metron_home}/web/management-ui/assets/app-config.json
 %attr(0644,root,root) %{metron_home}/web/management-ui/license/*
 
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/metron-interface/README.md b/metron-interface/README.md
new file mode 100644
index 0000000000..639667f59a
--- /dev/null
+++ b/metron-interface/README.md
@@ -0,0 +1,75 @@
+<!--
+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.
+-->
+# Metron Interface
+
+Metron interface contains code and assets to support the various web applications in Metron.  The existing modules are:
+
+* metron-alerts : An Angular application that exposes a way to browse, filter and act on alerts.
+* metron-config: An Angular application that allows an administrator to configure and maintain Metron.
+* metron-rest: A Spring REST application that supports the UI and exposes Metron features through a REST/json interface.
+* metron-rest-client: Model objects used for passing data back and forth in the REST application.  A Metron client would use these classes for de/serializing requests and responses.
+
+## Architecture
+
+The UIs and REST server are all run in separate web containers.  The UIs are served from separate [Express](https://expressjs.com/) servers that are configured to proxy REST requests
+to the REST application.  Proxying REST requests satisfies the same-origin browser restriction because all requests go to Express, or the same origin.  
+
+REST requests are handled by a [Spring Boot](https://spring.io/projects/spring-boot) application.  A [Swagger](https://swagger.io/) interface is available and served by the REST application.
+
+### Security
+
+The UIs depend on REST for authentication.  Credentials are passed with [Basic authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) and only REST requests require
+authentication, static assets are not secured.  Once authentication has been performed a session is established and a cookie is stored and used to authenticate subsequent requests.
+
+### Request/Response Flow
+
+The following diagram illustrates the flow of data for the various types of requests:
+
+![Flow Diagram](flow_diagram.png)
+
+
+## Architecture with Knox
+
+[Apache Knox](https://knox.apache.org/) is a "REST API and Application Gateway for the Apache Hadoop Ecosystem".  It can be enabled for Metron and provides several security benefits:
+
+* All requests go through Knox so same-origin browser restrictions are not a concern.
+* Knox, in combination with a firewall, can restrict traffic to always go through Knox.  This greatly reduces the security attack surface area of the UIs and REST application.
+* Provides access to other common Apache Hadoop services
+* Provides a single sign on experience between the UIs and REST application
+* All requests can be protected and secured
+
+We primarily use Knox's proxying and authentication services.  Knox acts as a reverse proxy for all UIs and the REST application.  
+
+### Knox Security
+
+With Knox enabled, Knox now handles authentication when accessing the UIs and REST together.  Basic authentication is still an option for making requests directly to the REST application.  Any request to the UIs must go through Knox first and contain the proper security token.  
+If a valid token is not found, Knox will redirect to the Knox SSO login form.  Once a valid token is found, Knox will then redirect to the original url and the request will be forwarded on.  Accessing the REST application through Knox also follows this pattern.
+The UIs make REST requests this way with Knox enabled since they no longer depend on Express to proxy requests.  The context path now determines which type of request it is rather than the host and port.  
+
+REST still requires authentication so a filter is provided that can validate a Knox token using token properties and a Knox public key.  The REST application also supports Basic authentication.  Since both Knox and the REST application should use
+the same authentication mechanism, LDAP authentication is required for the REST application.
+
+Roles are mapped directly to LDAP groups when Knox is enabled for REST.  LDAP group names are converted to upper case and prepended with "ROLE_".  For example, if a user's groups in LDAP were "user" and "admin", the corresponding roles in REST with Knox enabled would be "ROLE_USER" and "ROLE_ADMIN".
+
+### Knox Request/Response Flow
+
+The following diagram illustrates the flow of data for the various types of requests when Knox is enabled:
+
+![Knox Flow Diagram](knox_flow_diagram.png)
+
+Note how the flow diagrams for Static asset requests and Rest requests (through Knox) are identical.
diff --git a/metron-interface/flow_diagram.png b/metron-interface/flow_diagram.png
new file mode 100644
index 0000000000..835db791ee
Binary files /dev/null and b/metron-interface/flow_diagram.png differ
diff --git a/metron-interface/flow_diagrams.pptx b/metron-interface/flow_diagrams.pptx
new file mode 100644
index 0000000000..fcaa7a64b1
Binary files /dev/null and b/metron-interface/flow_diagrams.pptx differ
diff --git a/metron-interface/knox_flow_diagram.png b/metron-interface/knox_flow_diagram.png
new file mode 100644
index 0000000000..7a418678c5
Binary files /dev/null and b/metron-interface/knox_flow_diagram.png differ
diff --git a/metron-interface/metron-alerts/cypress/integration/pcap/pcap.spec.js b/metron-interface/metron-alerts/cypress/integration/pcap/pcap.spec.js
index 835c714dea..fa0cd51fa2 100644
--- a/metron-interface/metron-alerts/cypress/integration/pcap/pcap.spec.js
+++ b/metron-interface/metron-alerts/cypress/integration/pcap/pcap.spec.js
@@ -26,9 +26,9 @@ context('PCAP Tab', () => {
       response: 'user'
     });
     cy.route({
-      method: 'POST',
-      url: 'logout',
-      response: []
+        method: 'POST',
+        url: '/api/v1/logout',
+        response: []
     });
 
     cy.route('GET', '/api/v1/global/config', 'fixture:config.json');
diff --git a/metron-interface/metron-alerts/proxy.conf.json b/metron-interface/metron-alerts/proxy.conf.json
index 612bd67420..571af9af91 100644
--- a/metron-interface/metron-alerts/proxy.conf.json
+++ b/metron-interface/metron-alerts/proxy.conf.json
@@ -2,9 +2,5 @@
   "/api/v1": {
     "target": "http://node1:8082",
     "secure": false
-  },
-  "/logout": {
-    "target": "http://node1:8082",
-    "secure": false
   }
 }
diff --git a/metron-interface/metron-alerts/src/app/app-routing.module.ts b/metron-interface/metron-alerts/src/app/app-routing.module.ts
index db6e29f1cf..f899a15839 100644
--- a/metron-interface/metron-alerts/src/app/app-routing.module.ts
+++ b/metron-interface/metron-alerts/src/app/app-routing.module.ts
@@ -21,7 +21,7 @@ import {AuthGuard} from './shared/auth-guard';
 import {LoginGuard} from './shared/login-guard';
 
 const routes: Routes = [
-  { path: '',  redirectTo: 'login', pathMatch: 'full'},
+  { path: '',  redirectTo: 'alerts-list', pathMatch: 'full'},
   { path: 'login', loadChildren: 'app/login/login.module#LoginModule', canActivate: [LoginGuard]},
   { path: 'alerts-list', loadChildren: 'app/alerts/alerts-list/alerts-list.module#AlertsListModule', canActivate: [AuthGuard]},
   { path: 'save-search', loadChildren: 'app/alerts/save-search/save-search.module#SaveSearchModule', canActivate: [AuthGuard]},
diff --git a/metron-interface/metron-alerts/src/app/app.component.html b/metron-interface/metron-alerts/src/app/app.component.html
index d94a6d72d1..1e1e9bf67c 100644
--- a/metron-interface/metron-alerts/src/app/app.component.html
+++ b/metron-interface/metron-alerts/src/app/app.component.html
@@ -14,7 +14,7 @@
 <div class="container-fluid px-0" [class.notransition]="noTransition">
     <nav class="navbar" *ngIf="loggedIn">
         <a class="" href="#">
-            <img alt="" src="../assets/images/logo.png" width="135" height="45">
+            <img alt="" src="assets/images/logo.png" width="135" height="45">
         </a>
         <ul class="nav ml-4">
             <li class="nav-item" routerLinkActive="active">
diff --git a/metron-interface/metron-alerts/src/app/pcap/service/pcap.service.spec.ts b/metron-interface/metron-alerts/src/app/pcap/service/pcap.service.spec.ts
index 98537c2b26..8a21363d97 100644
--- a/metron-interface/metron-alerts/src/app/pcap/service/pcap.service.spec.ts
+++ b/metron-interface/metron-alerts/src/app/pcap/service/pcap.service.spec.ts
@@ -48,6 +48,10 @@ class FakeAppConfigService {
   getApiRoot() {
     return '/api/v1'
   }
+
+  getLoginPath() {
+    return '/login'
+  }
 }
 
 describe('PcapService', () => {
diff --git a/metron-interface/metron-alerts/src/app/service/app-config.service.ts b/metron-interface/metron-alerts/src/app/service/app-config.service.ts
index 0445f35751..a3b7414da8 100644
--- a/metron-interface/metron-alerts/src/app/service/app-config.service.ts
+++ b/metron-interface/metron-alerts/src/app/service/app-config.service.ts
@@ -22,7 +22,7 @@ import { HttpClient } from '@angular/common/http';
 @Injectable()
 export class AppConfigService {
 
-  private appConfig;
+  private static appConfigStatic;
 
   constructor(private http: HttpClient) { }
 
@@ -31,11 +31,19 @@ export class AppConfigService {
             // APP_INITIALIZER only supports promises
             .toPromise()
             .then(data => {
-              this.appConfig = data;
+              AppConfigService.appConfigStatic = data;
             });
   }
 
   getApiRoot() {
-    return this.appConfig['apiRoot'];
+    return AppConfigService.appConfigStatic['apiRoot'];
+  }
+
+  getLoginPath() {
+    return AppConfigService.appConfigStatic['loginPath'];
+  }
+
+  static getAppConfigStatic() {
+    return AppConfigService.appConfigStatic;
   }
 }
\ No newline at end of file
diff --git a/metron-interface/metron-alerts/src/app/service/authentication.service.ts b/metron-interface/metron-alerts/src/app/service/authentication.service.ts
index 7739be128d..f54dee2709 100644
--- a/metron-interface/metron-alerts/src/app/service/authentication.service.ts
+++ b/metron-interface/metron-alerts/src/app/service/authentication.service.ts
@@ -23,14 +23,15 @@ import { ReplaySubject } from 'rxjs';
 import { GlobalConfigService } from './global-config.service';
 import { DataSource } from './data-source';
 import { AppConfigService } from './app-config.service';
+import {HttpUtil} from "../utils/httpUtil";
 
 @Injectable()
 export class AuthenticationService {
 
   private static USER_NOT_VERIFIED = 'USER-NOT-VERIFIED';
   private currentUser: string = AuthenticationService.USER_NOT_VERIFIED;
-  loginUrl = this.appConfigService.getApiRoot() + '/user';
-  logoutUrl = '/logout';
+  userUrl = this.appConfigService.getApiRoot() + '/user';
+  logoutUrl = this.appConfigService.getApiRoot() + '/logout';
   onLoginEvent: ReplaySubject<boolean> = new ReplaySubject<boolean>();
   $onLoginEvent = this.onLoginEvent.asObservable();
 
@@ -71,24 +72,23 @@ export class AuthenticationService {
 
   public logout(): void {
     this.http.post(this.logoutUrl, {}).subscribe(response => {
-        this.currentUser = AuthenticationService.USER_NOT_VERIFIED;
-        this.onLoginEvent.next(false);
-        this.router.navigateByUrl('/login');
+        this.clearAuthentication();
+        HttpUtil.navigateToLogin();
       },
       error => {
         console.log('Logout failed:', error);
-        this.router.navigateByUrl('/login');
+        HttpUtil.navigateToLogin();
       });
   }
 
   public checkAuthentication() {
     if (!this.isAuthenticated()) {
-      this.router.navigateByUrl('/login');
+      HttpUtil.navigateToLogin();
     }
   }
 
   public getCurrentUser(options?: {}) {
-    return this.http.get(this.loginUrl, options);
+    return this.http.get(this.userUrl, options);
   }
 
   public getCurrentUserName(): string {
@@ -102,4 +102,9 @@ export class AuthenticationService {
   public isAuthenticated(): boolean {
     return this.currentUser !== AuthenticationService.USER_NOT_VERIFIED && this.currentUser != null;
   }
+
+  public clearAuthentication(): void {
+    this.currentUser = AuthenticationService.USER_NOT_VERIFIED;
+    this.onLoginEvent.next(false);
+  }
 }
diff --git a/metron-interface/metron-alerts/src/app/shared/auth-guard.ts b/metron-interface/metron-alerts/src/app/shared/auth-guard.ts
index 3271853cfa..7db9f9b9f0 100644
--- a/metron-interface/metron-alerts/src/app/shared/auth-guard.ts
+++ b/metron-interface/metron-alerts/src/app/shared/auth-guard.ts
@@ -24,11 +24,13 @@ import {
 } from '@angular/router';
 import { Observable } from 'rxjs';
 import { AuthenticationService } from '../service/authentication.service';
+import {AppConfigService} from "../service/app-config.service";
+import {HttpUtil} from "../utils/httpUtil";
 
 @Injectable()
 export class AuthGuard implements CanActivate {
 
-  constructor(private authService: AuthenticationService, private router: Router) {}
+  constructor(private authService: AuthenticationService, private router: Router, private appConfigService: AppConfigService) {}
 
   canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
     if (!this.authService.isAuthenticationChecked()) {
@@ -40,7 +42,7 @@ export class AuthGuard implements CanActivate {
           } else {
             observer.next(false);
             observer.complete();
-            this.router.navigateByUrl('/login');
+            HttpUtil.navigateToLogin();
           }
         });
       });
diff --git a/metron-interface/metron-alerts/src/app/shared/directives/alert-search.directive.ts b/metron-interface/metron-alerts/src/app/shared/directives/alert-search.directive.ts
index eb69b56cea..6af5baddd2 100644
--- a/metron-interface/metron-alerts/src/app/shared/directives/alert-search.directive.ts
+++ b/metron-interface/metron-alerts/src/app/shared/directives/alert-search.directive.ts
@@ -38,7 +38,7 @@ export class AlertSearchDirective implements AfterViewInit, OnChanges {
     const el = elementRef.nativeElement;
     el.classList.add('editor');
 
-    ace.config.set('basePath', '/assets/ace');
+    ace.config.set('basePath', 'assets/ace');
 
     this.editor = ace.edit(el);
     this.editor.$blockScrolling = Infinity;
diff --git a/metron-interface/metron-alerts/src/app/shared/login-guard.ts b/metron-interface/metron-alerts/src/app/shared/login-guard.ts
index d8b8f0d052..414f590867 100644
--- a/metron-interface/metron-alerts/src/app/shared/login-guard.ts
+++ b/metron-interface/metron-alerts/src/app/shared/login-guard.ts
@@ -32,7 +32,7 @@ export class LoginGuard implements CanActivate {
 
   canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
 
-    this.authService.logout();
+    this.authService.clearAuthentication();
 
     return true;
 
diff --git a/metron-interface/metron-alerts/src/app/utils/httpUtil.ts b/metron-interface/metron-alerts/src/app/utils/httpUtil.ts
index 6c9949018f..ffbd474e59 100644
--- a/metron-interface/metron-alerts/src/app/utils/httpUtil.ts
+++ b/metron-interface/metron-alerts/src/app/utils/httpUtil.ts
@@ -19,6 +19,7 @@
 import {HttpErrorResponse} from '@angular/common/http';
 import {RestError} from '../model/rest-error';
 import {throwError as observableThrowError, Observable} from 'rxjs';
+import {AppConfigService} from "../service/app-config.service";
 
 export class HttpUtil {
 
@@ -37,7 +38,7 @@ export class HttpUtil {
     // We'd also dig deeper into the error to get a better message
     let restError: RestError;
     if (res.status === 401) {
-      window.location.assign('/login?sessionExpired=true');
+      HttpUtil.navigateToLogin();
     } else if (res.status !== 404) {
       restError = res;
     } else {
@@ -46,4 +47,9 @@ export class HttpUtil {
     }
     return observableThrowError(restError);
   }
+
+  public static navigateToLogin() {
+    let loginPath = AppConfigService.getAppConfigStatic()['loginPath'];
+    location.href = loginPath;
+  }
 }
diff --git a/metron-interface/metron-alerts/src/assets/app-config.json b/metron-interface/metron-alerts/src/assets/app-config.json
index 7fa734d96c..e48507170d 100644
--- a/metron-interface/metron-alerts/src/assets/app-config.json
+++ b/metron-interface/metron-alerts/src/assets/app-config.json
@@ -1,3 +1,4 @@
 {
-  "apiRoot": "/api/v1"
+  "apiRoot": "/api/v1",
+  "loginPath": "/login"
 }
\ No newline at end of file
diff --git a/metron-interface/metron-alerts/src/index.html b/metron-interface/metron-alerts/src/index.html
index e051e27f78..fef547c8ab 100644
--- a/metron-interface/metron-alerts/src/index.html
+++ b/metron-interface/metron-alerts/src/index.html
@@ -16,7 +16,8 @@
 <head>
   <meta charset="utf-8">
   <title>Metron Alerts</title>
-  <base href="/">
+  <!-- The base href is relative so that static assets are properly resolved when behind a reverse proxy -->
+  <base href="./">
 
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="icon" type="image/x-icon" href="favicon.ico">
diff --git a/metron-interface/metron-config/src/app/_fonts.scss b/metron-interface/metron-config/src/app/_fonts.scss
index fdcb87e5af..a3a84b8d10 100644
--- a/metron-interface/metron-config/src/app/_fonts.scss
+++ b/metron-interface/metron-config/src/app/_fonts.scss
@@ -19,83 +19,83 @@
   font-family: 'Roboto';
   font-style: normal;
   font-weight: 100;
-  src: local('Roboto Thin'), local('Roboto-Thin'), url("/assets/fonts/Roboto/Roboto-Thin.ttf");
+  src: local('Roboto Thin'), local('Roboto-Thin'), url("assets/fonts/Roboto/Roboto-Thin.ttf");
 }
 
 @font-face {
   font-family: 'Roboto';
   font-style: normal;
   font-weight: 300;
-  src: local('Roboto Light'), local('Roboto-Light'), url("/assets/fonts/Roboto/Roboto-Light.ttf");
+  src: local('Roboto Light'), local('Roboto-Light'), url("assets/fonts/Roboto/Roboto-Light.ttf");
 }
 
 @font-face {
   font-family: 'Roboto-Regular';
   font-style: normal;
   font-weight: 400;
-  src: local('Roboto'), local('Roboto-Regular'), url("/assets/fonts/Roboto/Roboto-Regular.ttf");
+  src: local('Roboto'), local('Roboto-Regular'), url("assets/fonts/Roboto/Roboto-Regular.ttf");
 }
 
 @font-face {
   font-family: 'Roboto-Medium';
   font-style: normal;
   font-weight: 500;
-  src: local('Roboto Medium'), local('Roboto-Medium'), url("/assets/fonts/Roboto/Roboto-Medium.ttf");
+  src: local('Roboto Medium'), local('Roboto-Medium'), url("assets/fonts/Roboto/Roboto-Medium.ttf");
 }
 
 @font-face {
   font-family: 'Roboto';
   font-style: normal;
   font-weight: 700;
-  src: local('Roboto Bold'), local('Roboto-Bold'), url("/assets/fonts/Roboto/Roboto-Bold.ttf");
+  src: local('Roboto Bold'), local('Roboto-Bold'), url("assets/fonts/Roboto/Roboto-Bold.ttf");
 }
 
 @font-face {
   font-family: 'Roboto';
   font-style: normal;
   font-weight: 900;
-  src: local('Roboto Black'), local('Roboto-Black'), url("/assets/fonts/Roboto/Roboto-Black.ttf");
+  src: local('Roboto Black'), local('Roboto-Black'), url("assets/fonts/Roboto/Roboto-Black.ttf");
 }
 
 @font-face {
   font-family: 'Roboto';
   font-style: italic;
   font-weight: 100;
-  src: local('Roboto Thin Italic'), local('Roboto-ThinItalic'), url("/assets/fonts/Roboto/Roboto-ThinItalic.ttf");
+  src: local('Roboto Thin Italic'), local('Roboto-ThinItalic'), url("assets/fonts/Roboto/Roboto-ThinItalic.ttf");
 }
 
 @font-face {
   font-family: 'Roboto';
   font-style: italic;
   font-weight: 300;
-  src: local('Roboto Light Italic'), local('Roboto-LightItalic'), url("/assets/fonts/Roboto/Roboto-LightItalic.ttf");
+  src: local('Roboto Light Italic'), local('Roboto-LightItalic'), url("assets/fonts/Roboto/Roboto-LightItalic.ttf");
 }
 
 @font-face {
   font-family: 'Roboto';
   font-style: italic;
   font-weight: 400;
-  src: local('Roboto Italic'), local('Roboto-Italic'), url("/assets/fonts/Roboto/Roboto-Italic.ttf");
+  src: local('Roboto Italic'), local('Roboto-Italic'), url("assets/fonts/Roboto/Roboto-Italic.ttf");
 }
 
 @font-face {
   font-family: 'Roboto-MediumItalic';
   font-style: italic;
   font-weight: 500;
-  src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'), url("/assets/fonts/Roboto/Roboto-MediumItalic.ttf");
+  src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'), url("assets/fonts/Roboto/Roboto-MediumItalic.ttf");
 }
 
 @font-face {
   font-family: 'Roboto';
   font-style: italic;
   font-weight: 700;
-  src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'), url("/assets/fonts/Roboto/Roboto-BoldItalic.ttf");
+  src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'), url("assets/fonts/Roboto/Roboto-BoldItalic.ttf");
 }
 
 @font-face {
   font-family: 'Roboto';
   font-style: italic;
   font-weight: 900;
-  src: local('Roboto Black Italic'), local('Roboto-BlackItalic'), url("/assets/fonts/Roboto/Roboto-BlackItalic.ttf");
+  src: local('Roboto Black Italic'), local('Roboto-BlackItalic'), url("assets/fonts/Roboto/Roboto-BlackItalic.ttf");
 }
 
diff --git a/metron-interface/metron-config/src/app/app.component.spec.ts b/metron-interface/metron-config/src/app/app.component.spec.ts
index 4ca1bfa2f6..c5be76ab86 100644
--- a/metron-interface/metron-config/src/app/app.component.spec.ts
+++ b/metron-interface/metron-config/src/app/app.component.spec.ts
@@ -22,19 +22,11 @@ import { Observable } from 'rxjs';
 import { AppComponent } from './app.component';
 import { AuthenticationService } from './service/authentication.service';
 import { AppModule } from './app.module';
-import { APP_CONFIG, METRON_REST_CONFIG } from './app.config';
-import { IAppConfig } from './app.config.interface';
 import { HttpResponse, HttpClient } from '@angular/common/http';
+import {AppConfigService} from './service/app-config.service';
+import {MockAppConfigService} from './service/mock.app-config.service';
 
 class MockAuthenticationService extends AuthenticationService {
-  constructor(
-    private http2: HttpClient,
-    private router2: Router,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, router2, config2);
-    this.onLoginEvent.next(false);
-  }
 
   public checkAuthentication() {}
 
@@ -61,7 +53,7 @@ describe('App: Static', () => {
       providers: [
         { provide: AuthenticationService, useClass: MockAuthenticationService },
         { provide: Router, useClass: MockRouter },
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     fixture = TestBed.createComponent(AppComponent);
@@ -74,7 +66,6 @@ describe('App: Static', () => {
   });
 
   it('should return true/false from loginevent and loggedIn should be set', () => {
-    expect(comp.loggedIn).toEqual(false);
     authenticationService.onLoginEvent.next(true);
     expect(comp.loggedIn).toEqual(true);
     authenticationService.onLoginEvent.next(false);
diff --git a/metron-interface/metron-config/src/app/app.module.ts b/metron-interface/metron-config/src/app/app.module.ts
index 8df52932f5..89aa937820 100644
--- a/metron-interface/metron-config/src/app/app.module.ts
+++ b/metron-interface/metron-config/src/app/app.module.ts
@@ -15,7 +15,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {NgModule} from '@angular/core';
+import {APP_INITIALIZER, NgModule} from '@angular/core';
 import {FormsModule, ReactiveFormsModule} from '@angular/forms';
 import {BrowserModule} from '@angular/platform-browser';
 import {HttpClientModule, HTTP_INTERCEPTORS} from '@angular/common/http';
@@ -45,17 +45,23 @@ import {SensorParserConfigHistoryService} from './service/sensor-parser-config-h
 import {SensorIndexingConfigService} from './service/sensor-indexing-config.service';
 import {HdfsService} from './service/hdfs.service';
 import { DefaultHeadersInterceptor } from './http-interceptors/default-headers.interceptor';
+import {AppConfigService} from './service/app-config.service';
 
+export function initConfig(appConfigService: AppConfigService) {
+  return () => appConfigService.loadAppConfig();
+}
 
 @NgModule({
   imports: [ BrowserModule, FormsModule, ReactiveFormsModule, HttpClientModule, SensorParserListModule,
     SensorParserConfigModule, SensorParserConfigReadonlyModule, GeneralSettingsModule, MetronConfigRoutingModule ],
   declarations: [ AppComponent, NavbarComponent, VerticalNavbarComponent ],
-  providers: [  AuthenticationService, AuthGuard, LoginGuard, SensorParserConfigService,
+  providers: [  AppConfigService, AuthenticationService, AuthGuard, LoginGuard, SensorParserConfigService,
     SensorParserConfigHistoryService, SensorEnrichmentConfigService, SensorIndexingConfigService,
     StormService, KafkaService, GrokValidationService, StellarService, HdfsService,
-    GlobalConfigService, MetronAlerts, MetronDialogBox, { provide: APP_CONFIG, useValue: METRON_REST_CONFIG },
-    { provide: HTTP_INTERCEPTORS, useClass: DefaultHeadersInterceptor, multi: true }],
+    GlobalConfigService, MetronAlerts, MetronDialogBox,
+    { provide: HTTP_INTERCEPTORS, useClass: DefaultHeadersInterceptor, multi: true },
+    { provide: APP_INITIALIZER, useFactory: initConfig, deps: [AppConfigService], multi: true }
+    ],
   bootstrap:    [ AppComponent ]
 })
 export class AppModule {
diff --git a/metron-interface/metron-config/src/app/app.routes.ts b/metron-interface/metron-config/src/app/app.routes.ts
index fd3655d9e5..fd04063d6e 100644
--- a/metron-interface/metron-config/src/app/app.routes.ts
+++ b/metron-interface/metron-config/src/app/app.routes.ts
@@ -21,7 +21,7 @@ import {AuthGuard} from './shared/auth-guard';
 import {LoginGuard} from './shared/login-guard';
 
 export const routes: Routes = [
-  { path: '',  redirectTo: 'sensors', canActivate: [AuthGuard], pathMatch: 'full'},
+  { path: '',  redirectTo: 'sensors', pathMatch: 'full'},
   { path: 'login', loadChildren: 'app/login/login.module#LoginModule', canActivate: [LoginGuard] },
   { path: 'sensors', loadChildren: 'app/sensors/sensor-parser-list/sensor-parser-list.module#SensorParserListModule',
     canActivate: [AuthGuard] },
diff --git a/metron-interface/metron-config/src/app/general-settings/general-settings.component.spec.ts b/metron-interface/metron-config/src/app/general-settings/general-settings.component.spec.ts
index b1c5edef36..d517565c55 100644
--- a/metron-interface/metron-config/src/app/general-settings/general-settings.component.spec.ts
+++ b/metron-interface/metron-config/src/app/general-settings/general-settings.component.spec.ts
@@ -24,20 +24,13 @@ import { MetronDialogBox } from '../shared/metron-dialog-box';
 import { GlobalConfigService } from '../service/global-config.service';
 import { GeneralSettingsModule } from './general-settings.module';
 import { Observable, throwError } from 'rxjs';
-import { APP_CONFIG, METRON_REST_CONFIG } from '../app.config';
-import { IAppConfig } from '../app.config.interface';
+import {AppConfigService} from '../service/app-config.service';
+import {MockAppConfigService} from '../service/mock.app-config.service';
 
 class MockGlobalConfigService extends GlobalConfigService {
   _config: any = {};
   _postSuccess = true;
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public post(globalConfig: {}): Observable<{}> {
     if (this._postSuccess) {
       return Observable.create(observer => {
@@ -94,7 +87,7 @@ describe('GeneralSettingsComponent', () => {
         MetronAlerts,
         MetronDialogBox,
         { provide: GlobalConfigService, useClass: MockGlobalConfigService },
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     fixture = TestBed.createComponent(GeneralSettingsComponent);
diff --git a/metron-interface/metron-config/src/app/navbar/navbar.component.ts b/metron-interface/metron-config/src/app/navbar/navbar.component.ts
index 78614239be..0deda6698e 100644
--- a/metron-interface/metron-config/src/app/navbar/navbar.component.ts
+++ b/metron-interface/metron-config/src/app/navbar/navbar.component.ts
@@ -18,6 +18,7 @@
 import { Component, OnInit, OnDestroy } from '@angular/core';
 import { AuthenticationService } from '../service/authentication.service';
 import { Subscription } from 'rxjs';
+import {HttpHeaders} from "@angular/common/http";
 
 @Component({
   selector: 'metron-config-navbar',
@@ -32,7 +33,7 @@ export class NavbarComponent implements OnInit, OnDestroy {
 
   ngOnInit() {
     this.authService = this.authenticationService
-      .getCurrentUser({ responseType: 'text' })
+      .getCurrentUser({ headers: new HttpHeaders({'Accept': 'text/plain'}), responseType: 'text'})
       .subscribe(r => {
         this.currentUser = r;
       });
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts
index d744481140..1a027f523e 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts
@@ -33,8 +33,6 @@ import { StormService } from '../../service/storm.service';
 import { MetronAlerts } from '../../shared/metron-alerts';
 import { FieldTransformer } from '../../model/field-transformer';
 import { SensorParserConfigReadonlyModule } from './sensor-parser-config-readonly.module';
-import { APP_CONFIG, METRON_REST_CONFIG } from '../../app.config';
-import { IAppConfig } from '../../app.config.interface';
 import { SensorEnrichmentConfigService } from '../../service/sensor-enrichment-config.service';
 import {
   SensorEnrichmentConfig,
@@ -44,6 +42,8 @@ import {
 import { HdfsService } from '../../service/hdfs.service';
 import { GrokValidationService } from '../../service/grok-validation.service';
 import { RiskLevelRule } from '../../model/risk-level-rule';
+import {AppConfigService} from '../../service/app-config.service';
+import {MockAppConfigService} from '../../service/mock.app-config.service';
 
 class MockRouter {
   navigateByUrl(url: string) {}
@@ -63,13 +63,6 @@ class MockActivatedRoute {
 }
 
 class MockAuthenticationService extends AuthenticationService {
-  constructor(
-    private http2: HttpClient,
-    private router2: Router,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, router2, config2);
-  }
 
   public getCurrentUser(options): Observable<{}> {
     let response: { body: 'user' };
@@ -83,13 +76,6 @@ class MockAuthenticationService extends AuthenticationService {
 class MockSensorParserConfigHistoryService extends SensorParserConfigHistoryService {
   private sensorParserConfigHistory: SensorParserConfigHistory;
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public setForTest(sensorParserConfigHistory: SensorParserConfigHistory) {
     this.sensorParserConfigHistory = sensorParserConfigHistory;
   }
@@ -103,24 +89,11 @@ class MockSensorParserConfigHistoryService extends SensorParserConfigHistoryServ
 }
 
 class MockSensorParserConfigService extends SensorParserConfigService {
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
 }
 
 class MockStormService extends StormService {
   private topologyStatus: TopologyStatus;
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public setForTest(topologyStatus: TopologyStatus) {
     this.topologyStatus = topologyStatus;
   }
@@ -134,12 +107,6 @@ class MockStormService extends StormService {
 }
 
 class MockGrokValidationService extends GrokValidationService {
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
 
   public list(): Observable<string[]> {
     return Observable.create(observer => {
@@ -161,13 +128,6 @@ class MockGrokValidationService extends GrokValidationService {
 class MockKafkaService extends KafkaService {
   private kafkaTopic: KafkaTopic;
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public setForTest(kafkaTopic: KafkaTopic) {
     this.kafkaTopic = kafkaTopic;
   }
@@ -191,13 +151,6 @@ class MockHdfsService extends HdfsService {
   private fileList: string[];
   private contents: string;
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public setContents(contents: string) {
     this.contents = contents;
   }
@@ -297,7 +250,7 @@ describe('Component: SensorParserConfigReadonly', () => {
         { provide: HdfsService, useClass: MockHdfsService },
         { provide: GrokValidationService, useClass: MockGrokValidationService },
         { provide: Router, useClass: MockRouter },
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG },
+        { provide: AppConfigService, useClass: MockAppConfigService },
         MetronAlerts
       ]
     });
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.spec.ts
index 06927f408a..09809a2148 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.spec.ts
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.spec.ts
@@ -39,14 +39,14 @@ import { FieldTransformer } from '../../model/field-transformer';
 import { SensorParserConfigModule } from './sensor-parser-config.module';
 import { SensorEnrichmentConfigService } from '../../service/sensor-enrichment-config.service';
 import { SensorEnrichmentConfig } from '../../model/sensor-enrichment-config';
-import { APP_CONFIG, METRON_REST_CONFIG } from '../../app.config';
-import { IAppConfig } from '../../app.config.interface';
 import { SensorIndexingConfigService } from '../../service/sensor-indexing-config.service';
 import { IndexingConfigurations } from '../../model/sensor-indexing-config';
 import { of } from 'rxjs';
 import { HdfsService } from '../../service/hdfs.service';
 import { RestError } from '../../model/rest-error';
 import { RiskLevelRule } from '../../model/risk-level-rule';
+import {AppConfigService} from '../../service/app-config.service';
+import {MockAppConfigService} from '../../service/mock.app-config.service';
 
 class MockRouter {
   navigateByUrl(url: string) {}
@@ -72,13 +72,6 @@ class MockSensorParserConfigService extends SensorParserConfigService {
   private postedSensorParserConfig: SensorParserConfig;
   private throwError: boolean;
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public post(
     name: string,
     sensorParserConfig: SensorParserConfig
@@ -157,13 +150,6 @@ class MockSensorIndexingConfigService extends SensorIndexingConfigService {
   private sensorIndexingConfig: IndexingConfigurations;
   private throwError: boolean;
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public post(
     name: string,
     sensorIndexingConfig: IndexingConfigurations
@@ -214,13 +200,6 @@ class MockKafkaService extends KafkaService {
   private kafkaTopicForPost: KafkaTopic;
   private sampleData = { key1: 'value1', key2: 'value2' };
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public setKafkaTopic(name: string, kafkaTopic: KafkaTopic) {
     this.name = name;
     this.kafkaTopic = kafkaTopic;
@@ -274,13 +253,6 @@ class MockGrokValidationService extends GrokValidationService {
   private path: string;
   private contents: string;
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public setContents(path: string, contents: string) {
     this.path = path;
     this.contents = contents;
@@ -321,13 +293,6 @@ class MockHdfsService extends HdfsService {
   private contents: string;
   private postedContents: string;
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public setFileList(path: string, fileList: string[]) {
     this.path = path;
     this.fileList = fileList;
@@ -388,13 +353,6 @@ class MockHdfsService extends HdfsService {
 }
 
 class MockAuthenticationService extends AuthenticationService {
-  constructor(
-    private http2: HttpClient,
-    private router2: Router,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, router2, config2);
-  }
 
   public getCurrentUser(options: {}): Observable<HttpResponse<{}>> {
     let responseOptions: {} = { body: 'user' };
@@ -407,13 +365,6 @@ class MockTransformationValidationService extends StellarService {
   private transformationValidationResult: any;
   private transformationValidationForValidate: SensorParserContext;
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public setTransformationValidationResultForTest(
     transformationValidationResult: any
   ): void {
@@ -586,7 +537,7 @@ describe('Component: SensorParserConfig', () => {
           provide: SensorEnrichmentConfigService,
           useClass: MockSensorEnrichmentConfigService
         },
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     fixture = TestBed.createComponent(SensorParserConfigComponent);
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.component.spec.ts
index ec07cbb2b4..eb4dbffac6 100644
--- a/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.component.spec.ts
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.component.spec.ts
@@ -35,19 +35,11 @@ import { Sort } from '../../util/enums';
 import 'jquery';
 import { SensorParserConfigHistoryService } from '../../service/sensor-parser-config-history.service';
 import { SensorParserConfigHistory } from '../../model/sensor-parser-config-history';
-import { APP_CONFIG, METRON_REST_CONFIG } from '../../app.config';
 import { StormService } from '../../service/storm.service';
-import { IAppConfig } from '../../app.config.interface';
+import {AppConfigService} from '../../service/app-config.service';
+import {MockAppConfigService} from '../../service/mock.app-config.service';
 
 class MockAuthenticationService extends AuthenticationService {
-  constructor(
-    private http2: HttpClient,
-    private router2: Router,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, router2, config2);
-  }
-
   public checkAuthentication() {}
 
   public getCurrentUser(options: {}): Observable<HttpResponse<{}>> {
@@ -61,13 +53,6 @@ class MockAuthenticationService extends AuthenticationService {
 class MockSensorParserConfigHistoryService extends SensorParserConfigHistoryService {
   private allSensorParserConfigHistory: SensorParserConfigHistory[];
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public setSensorParserConfigHistoryForTest(
     allSensorParserConfigHistory: SensorParserConfigHistory[]
   ) {
@@ -85,13 +70,6 @@ class MockSensorParserConfigHistoryService extends SensorParserConfigHistoryServ
 class MockSensorParserConfigService extends SensorParserConfigService {
   private sensorParserConfigs: {};
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public setSensorParserConfigForTest(sensorParserConfigs: {}) {
     this.sensorParserConfigs = sensorParserConfigs;
   }
@@ -124,13 +102,6 @@ class MockSensorParserConfigService extends SensorParserConfigService {
 class MockStormService extends StormService {
   private topologyStatuses: TopologyStatus[];
 
-  constructor(
-    private http2: HttpClient,
-    @Inject(APP_CONFIG) private config2: IAppConfig
-  ) {
-    super(http2, config2);
-  }
-
   public setTopologyStatusForTest(topologyStatuses: TopologyStatus[]) {
     this.topologyStatuses = topologyStatuses;
   }
@@ -198,7 +169,7 @@ describe('Component: SensorParserList', () => {
         },
         { provide: Router, useClass: MockRouter },
         { provide: MetronDialogBox, useClass: MockMetronDialogBox },
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG },
+        { provide: AppConfigService, useClass: MockAppConfigService },
         MetronAlerts
       ]
     });
diff --git a/metron-interface/metron-config/src/app/service/app-config.service.ts b/metron-interface/metron-config/src/app/service/app-config.service.ts
new file mode 100644
index 0000000000..6081ea22eb
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/app-config.service.ts
@@ -0,0 +1,49 @@
+/**
+ * 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.
+ */
+
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+@Injectable()
+export class AppConfigService {
+
+  private static appConfigStatic;
+
+  constructor(private http: HttpClient) { }
+
+  loadAppConfig() {
+    return this.http.get('assets/app-config.json')
+            // APP_INITIALIZER only supports promises
+            .toPromise()
+            .then(data => {
+              AppConfigService.appConfigStatic = data;
+            });
+  }
+
+  getApiRoot() {
+    return AppConfigService.appConfigStatic['apiRoot'];
+  };
+
+  getLoginPath() {
+    return AppConfigService.appConfigStatic['loginPath'];
+  }
+
+  static getAppConfigStatic() {
+    return AppConfigService.appConfigStatic;
+  }
+}
\ No newline at end of file
diff --git a/metron-interface/metron-config/src/app/service/authentication.service.spec.ts b/metron-interface/metron-config/src/app/service/authentication.service.spec.ts
index dedb69cb38..7a82ac0c67 100644
--- a/metron-interface/metron-config/src/app/service/authentication.service.spec.ts
+++ b/metron-interface/metron-config/src/app/service/authentication.service.spec.ts
@@ -23,7 +23,9 @@ import {
   HttpTestingController
 } from '@angular/common/http/testing';
 import { AuthenticationService } from './authentication.service';
-import { APP_CONFIG, METRON_REST_CONFIG } from '../app.config';
+import {AppConfigService} from "./app-config.service";
+import {MockAppConfigService} from './mock.app-config.service';
+import {HttpUtil} from '../util/httpUtil';
 
 class MockRouter {
   navigateByUrl(url: string) {}
@@ -40,7 +42,7 @@ describe('AuthenticationService', () => {
       providers: [
         AuthenticationService,
         { provide: Router, useClass: MockRouter },
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     authenticationService = TestBed.get(AuthenticationService);
@@ -86,31 +88,31 @@ describe('AuthenticationService', () => {
     });
 
     it('logout', () => {
-      spyOn(router, 'navigateByUrl');
+      spyOn(HttpUtil, 'navigateToLogin');
       spyOn(authenticationService.onLoginEvent, 'next');
 
       authenticationService.logout();
-      const req = mockBackend.match('/logout');
+      const req = mockBackend.match('/api/v1/logout');
       const req2 = mockBackend.expectOne('/api/v1/user');
       req.map(r => r.flush(''));
       expect(req[0].request.method).toBe('POST');
 
-      expect(router.navigateByUrl).toHaveBeenCalledWith('/login');
+      expect(HttpUtil.navigateToLogin).toHaveBeenCalledWith();
       expect(authenticationService.onLoginEvent.getValue()).toEqual(false);
     });
 
     it('checkAuthentication', () => {
       let isAuthenticated = false;
-      spyOn(router, 'navigateByUrl');
+      spyOn(HttpUtil, 'navigateToLogin');
       spyOn(authenticationService, 'isAuthenticated').and.callFake(function() {
         return isAuthenticated;
       });
 
       authenticationService.checkAuthentication();
-      expect(router.navigateByUrl).toHaveBeenCalledWith('/login');
+      expect(HttpUtil.navigateToLogin).toHaveBeenCalledWith();
       isAuthenticated = true;
       authenticationService.checkAuthentication();
-      expect(router.navigateByUrl['calls'].count()).toEqual(1);
+      expect(HttpUtil.navigateToLogin['calls'].count()).toEqual(1);
       const req = mockBackend.expectOne('/api/v1/user');
     });
 
diff --git a/metron-interface/metron-config/src/app/service/authentication.service.ts b/metron-interface/metron-config/src/app/service/authentication.service.ts
index 3c4d880d4f..f6d0040e1c 100644
--- a/metron-interface/metron-config/src/app/service/authentication.service.ts
+++ b/metron-interface/metron-config/src/app/service/authentication.service.ts
@@ -18,28 +18,28 @@
 import { Injectable, Inject } from '@angular/core';
 import { HttpClient, HttpHeaders } from '@angular/common/http';
 import { Router } from '@angular/router';
-import { BehaviorSubject } from 'rxjs';
-import { IAppConfig } from '../app.config.interface';
-import { APP_CONFIG } from '../app.config';
+import {BehaviorSubject, Subject} from 'rxjs';
+import {AppConfigService} from './app-config.service';
+import {HttpUtil} from '../util/httpUtil';
 
 @Injectable()
 export class AuthenticationService {
   private static USER_NOT_VERIFIED = 'USER-NOT-VERIFIED';
   private currentUser: string = AuthenticationService.USER_NOT_VERIFIED;
-  loginUrl: string = this.config.apiEndpoint + '/user';
-  logoutUrl = '/logout';
+  loginUrl: string = this.appConfigService.getApiRoot() + '/user';
+  logoutUrl = this.appConfigService.getApiRoot() + '/logout';
   onLoginEvent: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
 
   constructor(
     private http: HttpClient,
     private router: Router,
-    @Inject(APP_CONFIG) private config: IAppConfig
+    private appConfigService: AppConfigService
   ) {
     this.init();
   }
 
   public init() {
-    this.getCurrentUser({ responseType: 'text' }).subscribe(
+    this.getCurrentUser({ headers: new HttpHeaders({'Accept': 'text/plain'}), responseType: 'text'}).subscribe(
       response => {
         this.currentUser = response.toString();
         if (this.currentUser) {
@@ -55,7 +55,7 @@ export class AuthenticationService {
   public login(username: string, password: string, onError): void {
     let credentials = btoa(username + ':' + password);
     this.getCurrentUser({
-      headers: new HttpHeaders({ Authorization: `Basic ${credentials}` }),
+      headers: new HttpHeaders({ Authorization: `Basic ${credentials}` , 'Accept': 'text/plain'}),
       responseType: 'text'
     }).subscribe(
       response => {
@@ -72,19 +72,19 @@ export class AuthenticationService {
   public logout(): void {
     this.http.post(this.logoutUrl, {}).subscribe(
       response => {
-        this.currentUser = AuthenticationService.USER_NOT_VERIFIED;
-        this.onLoginEvent.next(false);
-        this.router.navigateByUrl('/login');
+        this.clearAuthentication();
+        HttpUtil.navigateToLogin();
       },
       error => {
         console.log(error);
+        HttpUtil.navigateToLogin();
       }
     );
   }
 
   public checkAuthentication() {
     if (!this.isAuthenticated()) {
-      this.router.navigateByUrl('/login');
+      HttpUtil.navigateToLogin();
     }
   }
 
@@ -102,4 +102,9 @@ export class AuthenticationService {
       this.currentUser != null
     );
   }
+
+  public clearAuthentication(): void {
+    this.currentUser = AuthenticationService.USER_NOT_VERIFIED;
+    this.onLoginEvent.next(false);
+  }
 }
diff --git a/metron-interface/metron-config/src/app/service/global-config.service.spec.ts b/metron-interface/metron-config/src/app/service/global-config.service.spec.ts
index 9ad3b133bb..1bd3ef8c4e 100644
--- a/metron-interface/metron-config/src/app/service/global-config.service.spec.ts
+++ b/metron-interface/metron-config/src/app/service/global-config.service.spec.ts
@@ -22,6 +22,8 @@ import {
   HttpTestingController,
   HttpClientTestingModule
 } from '@angular/common/http/testing';
+import {AppConfigService} from './app-config.service';
+import {MockAppConfigService} from './mock.app-config.service';
 
 describe('GlobalConfigService', () => {
   let mockBackend: HttpTestingController;
@@ -32,7 +34,7 @@ describe('GlobalConfigService', () => {
       imports: [HttpClientTestingModule],
       providers: [
         GlobalConfigService,
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     mockBackend = TestBed.get(HttpTestingController);
diff --git a/metron-interface/metron-config/src/app/service/global-config.service.ts b/metron-interface/metron-config/src/app/service/global-config.service.ts
index 84bd01410a..97ae1677f4 100644
--- a/metron-interface/metron-config/src/app/service/global-config.service.ts
+++ b/metron-interface/metron-config/src/app/service/global-config.service.ts
@@ -21,16 +21,15 @@ import { Response, ResponseOptions } from '@angular/http';
 import {Observable} from 'rxjs';
 import { map, catchError } from 'rxjs/operators';
 import {HttpUtil} from '../util/httpUtil';
-import {IAppConfig} from '../app.config.interface';
-import {APP_CONFIG} from '../app.config';
+import {AppConfigService} from './app-config.service';
 
 @Injectable()
 export class GlobalConfigService {
-  url = this.config.apiEndpoint + '/global/config';
+  url = this.appConfigService.getApiRoot() + '/global/config';
 
   private globalConfig = {};
 
-  constructor(private http: HttpClient, @Inject(APP_CONFIG) private config: IAppConfig) {
+  constructor(private http: HttpClient, private appConfigService: AppConfigService) {
     this.globalConfig['solr.collection'] = 'metron';
     this.globalConfig['storm.indexingWorkers'] = 1;
     this.globalConfig['storm.indexingExecutors'] = 2;
diff --git a/metron-interface/metron-config/src/app/service/grok-validation.service.spec.ts b/metron-interface/metron-config/src/app/service/grok-validation.service.spec.ts
index d66b0aae48..bed2268162 100644
--- a/metron-interface/metron-config/src/app/service/grok-validation.service.spec.ts
+++ b/metron-interface/metron-config/src/app/service/grok-validation.service.spec.ts
@@ -18,11 +18,12 @@
 import { async, inject, TestBed } from '@angular/core/testing';
 import { GrokValidationService } from './grok-validation.service';
 import { GrokValidation } from '../model/grok-validation';
-import { APP_CONFIG, METRON_REST_CONFIG } from '../app.config';
 import {
   HttpClientTestingModule,
   HttpTestingController
 } from '@angular/common/http/testing';
+import {AppConfigService} from './app-config.service';
+import {MockAppConfigService} from './mock.app-config.service';
 
 describe('GrokValidationService', () => {
   let grokValidationService: GrokValidationService;
@@ -33,7 +34,7 @@ describe('GrokValidationService', () => {
       imports: [HttpClientTestingModule],
       providers: [
         GrokValidationService,
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     grokValidationService = TestBed.get(GrokValidationService);
diff --git a/metron-interface/metron-config/src/app/service/grok-validation.service.ts b/metron-interface/metron-config/src/app/service/grok-validation.service.ts
index 7da8798313..1e61b3e82d 100644
--- a/metron-interface/metron-config/src/app/service/grok-validation.service.ts
+++ b/metron-interface/metron-config/src/app/service/grok-validation.service.ts
@@ -21,16 +21,15 @@ import { Observable } from 'rxjs';
 import { map, catchError } from 'rxjs/operators';
 import { GrokValidation } from '../model/grok-validation';
 import { HttpUtil } from '../util/httpUtil';
-import { IAppConfig } from '../app.config.interface';
-import { APP_CONFIG } from '../app.config';
+import {AppConfigService} from './app-config.service';
 
 @Injectable()
 export class GrokValidationService {
-  url = this.config.apiEndpoint + '/grok';
+  url = this.appConfigService.getApiRoot() + '/grok';
 
   constructor(
     private http: HttpClient,
-    @Inject(APP_CONFIG) private config: IAppConfig
+    private appConfigService: AppConfigService
   ) {}
 
   public validate(grokValidation: GrokValidation): Observable<GrokValidation> {
diff --git a/metron-interface/metron-config/src/app/service/hdfs.service.spec.ts b/metron-interface/metron-config/src/app/service/hdfs.service.spec.ts
index 251d392f3b..9163e29d92 100644
--- a/metron-interface/metron-config/src/app/service/hdfs.service.spec.ts
+++ b/metron-interface/metron-config/src/app/service/hdfs.service.spec.ts
@@ -23,6 +23,8 @@ import {
   HttpTestingController,
   HttpClientTestingModule
 } from '@angular/common/http/testing';
+import {AppConfigService} from './app-config.service';
+import {MockAppConfigService} from './mock.app-config.service';
 
 describe('HdfsService', () => {
   let hdfsService: HdfsService;
@@ -33,7 +35,7 @@ describe('HdfsService', () => {
       imports: [HttpClientTestingModule],
       providers: [
         HdfsService,
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     hdfsService = TestBed.get(HdfsService);
diff --git a/metron-interface/metron-config/src/app/service/hdfs.service.ts b/metron-interface/metron-config/src/app/service/hdfs.service.ts
index fdeb00116b..54e736924a 100644
--- a/metron-interface/metron-config/src/app/service/hdfs.service.ts
+++ b/metron-interface/metron-config/src/app/service/hdfs.service.ts
@@ -20,16 +20,15 @@ import { HttpClient, HttpParams } from '@angular/common/http';
 import { Observable } from 'rxjs';
 import { map, catchError } from 'rxjs/operators';
 import { HttpUtil } from '../util/httpUtil';
-import { IAppConfig } from '../app.config.interface';
-import { APP_CONFIG } from '../app.config';
+import {AppConfigService} from './app-config.service';
 
 @Injectable()
 export class HdfsService {
-  url = this.config.apiEndpoint + '/hdfs';
+  url = this.appConfigService.getApiRoot() + '/hdfs';
 
   constructor(
     private http: HttpClient,
-    @Inject(APP_CONFIG) private config: IAppConfig
+    private appConfigService: AppConfigService
   ) {}
 
   public list(path: string): Observable<string[]> {
diff --git a/metron-interface/metron-config/src/app/service/kafka.service.spec.ts b/metron-interface/metron-config/src/app/service/kafka.service.spec.ts
index 12f0c1285e..cc7269f820 100644
--- a/metron-interface/metron-config/src/app/service/kafka.service.spec.ts
+++ b/metron-interface/metron-config/src/app/service/kafka.service.spec.ts
@@ -18,11 +18,12 @@
 import { TestBed } from '@angular/core/testing';
 import { KafkaService } from './kafka.service';
 import { KafkaTopic } from '../model/kafka-topic';
-import { APP_CONFIG, METRON_REST_CONFIG } from '../app.config';
 import {
   HttpClientTestingModule,
   HttpTestingController
 } from '@angular/common/http/testing';
+import {AppConfigService} from './app-config.service';
+import {MockAppConfigService} from './mock.app-config.service';
 
 describe('KafkaService', () => {
   let mockBackend: HttpTestingController;
@@ -33,7 +34,7 @@ describe('KafkaService', () => {
       imports: [HttpClientTestingModule],
       providers: [
         KafkaService,
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     mockBackend = TestBed.get(HttpTestingController);
diff --git a/metron-interface/metron-config/src/app/service/kafka.service.ts b/metron-interface/metron-config/src/app/service/kafka.service.ts
index f39f364286..62a95a088b 100644
--- a/metron-interface/metron-config/src/app/service/kafka.service.ts
+++ b/metron-interface/metron-config/src/app/service/kafka.service.ts
@@ -21,17 +21,16 @@ import { Observable } from 'rxjs';
 import { map, catchError } from 'rxjs/operators';
 import { KafkaTopic } from '../model/kafka-topic';
 import { HttpUtil } from '../util/httpUtil';
-import { IAppConfig } from '../app.config.interface';
-import { APP_CONFIG } from '../app.config';
 import { RestError } from '../model/rest-error';
+import {AppConfigService} from './app-config.service';
 
 @Injectable()
 export class KafkaService {
-  url = this.config.apiEndpoint + '/kafka/topic';
+  url = this.appConfigService.getApiRoot() + '/kafka/topic';
 
   constructor(
     private http: HttpClient,
-    @Inject(APP_CONFIG) private config: IAppConfig
+    private appConfigService: AppConfigService
   ) {}
 
   public post(kafkaTopic: KafkaTopic): Observable<KafkaTopic> {
diff --git a/metron-interface/metron-config/src/app/service/mock.app-config.service.ts b/metron-interface/metron-config/src/app/service/mock.app-config.service.ts
new file mode 100644
index 0000000000..66f41e4140
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/mock.app-config.service.ts
@@ -0,0 +1,29 @@
+/**
+ * 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.
+ */
+import {AppConfigService} from "./app-config.service";
+
+export class MockAppConfigService extends AppConfigService {
+
+  getApiRoot() {
+    return '/api/v1'
+  }
+
+  getLoginPath() {
+    return '/login'
+  }
+}
\ No newline at end of file
diff --git a/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.spec.ts b/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.spec.ts
index e2cb131cbd..3bc9a98b89 100644
--- a/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.spec.ts
+++ b/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.spec.ts
@@ -27,6 +27,8 @@ import {
   HttpClientTestingModule,
   HttpTestingController
 } from '@angular/common/http/testing';
+import {AppConfigService} from './app-config.service';
+import {MockAppConfigService} from './mock.app-config.service';
 
 describe('SensorEnrichmentConfigService', () => {
   let mockBackend: HttpTestingController;
@@ -37,7 +39,7 @@ describe('SensorEnrichmentConfigService', () => {
       imports: [HttpClientTestingModule],
       providers: [
         SensorEnrichmentConfigService,
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     mockBackend = TestBed.get(HttpTestingController);
diff --git a/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.ts b/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.ts
index bc26581cda..d56591d64e 100644
--- a/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.ts
+++ b/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.ts
@@ -21,16 +21,15 @@ import { Observable } from 'rxjs';
 import { map, catchError } from 'rxjs/operators';
 import { SensorEnrichmentConfig } from '../model/sensor-enrichment-config';
 import { HttpUtil } from '../util/httpUtil';
-import { IAppConfig } from '../app.config.interface';
-import { APP_CONFIG } from '../app.config';
+import {AppConfigService} from './app-config.service';
 
 @Injectable()
 export class SensorEnrichmentConfigService {
-  url = this.config.apiEndpoint + '/sensor/enrichment/config';
+  url = this.appConfigService.getApiRoot() + '/sensor/enrichment/config';
 
   constructor(
     private http: HttpClient,
-    @Inject(APP_CONFIG) private config: IAppConfig
+    private appConfigService: AppConfigService
   ) {}
 
   public post(
diff --git a/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.spec.ts b/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.spec.ts
index e2c1568bb5..0ab89ccbd0 100644
--- a/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.spec.ts
+++ b/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.spec.ts
@@ -17,13 +17,14 @@
  */
 import { TestBed } from '@angular/core/testing';
 import { HttpResponse } from '@angular/common/http';
-import { METRON_REST_CONFIG, APP_CONFIG } from '../app.config';
 import { SensorIndexingConfigService } from './sensor-indexing-config.service';
 import { IndexingConfigurations } from '../model/sensor-indexing-config';
 import {
   HttpClientTestingModule,
   HttpTestingController
 } from '@angular/common/http/testing';
+import {AppConfigService} from './app-config.service';
+import {MockAppConfigService} from './mock.app-config.service';
 
 describe('SensorIndexingConfigService', () => {
   let mockBackend: HttpTestingController;
@@ -34,7 +35,7 @@ describe('SensorIndexingConfigService', () => {
       imports: [HttpClientTestingModule],
       providers: [
         SensorIndexingConfigService,
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     mockBackend = TestBed.get(HttpTestingController);
diff --git a/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.ts b/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.ts
index 41e2c00e6d..920a958497 100644
--- a/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.ts
+++ b/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.ts
@@ -22,16 +22,15 @@ import { map, catchError } from 'rxjs/operators';
 
 import { IndexingConfigurations } from '../model/sensor-indexing-config';
 import { HttpUtil } from '../util/httpUtil';
-import { IAppConfig } from '../app.config.interface';
-import { APP_CONFIG } from '../app.config';
+import {AppConfigService} from './app-config.service';
 
 @Injectable()
 export class SensorIndexingConfigService {
-  url = this.config.apiEndpoint + '/sensor/indexing/config';
+  url = this.appConfigService.getApiRoot() + '/sensor/indexing/config';
 
   constructor(
     private http: HttpClient,
-    @Inject(APP_CONFIG) private config: IAppConfig
+    private appConfigService: AppConfigService
   ) {}
 
   public post(
diff --git a/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.spec.ts b/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.spec.ts
index 2170a594eb..4816ae5add 100644
--- a/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.spec.ts
+++ b/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.spec.ts
@@ -17,13 +17,14 @@
  */
 import { TestBed } from '@angular/core/testing';
 import { SensorParserConfig } from '../model/sensor-parser-config';
-import { METRON_REST_CONFIG, APP_CONFIG } from '../app.config';
 import { SensorParserConfigHistoryService } from './sensor-parser-config-history.service';
 import { SensorParserConfigHistory } from '../model/sensor-parser-config-history';
 import {
   HttpTestingController,
   HttpClientTestingModule
 } from '@angular/common/http/testing';
+import {AppConfigService} from './app-config.service';
+import {MockAppConfigService} from './mock.app-config.service';
 
 describe('SensorParserConfigHistoryService', () => {
   let mockBackend: HttpTestingController;
@@ -34,7 +35,7 @@ describe('SensorParserConfigHistoryService', () => {
       imports: [HttpClientTestingModule],
       providers: [
         SensorParserConfigHistoryService,
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     mockBackend = TestBed.get(HttpTestingController);
diff --git a/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.ts b/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.ts
index a7a73b29bd..c5d0c67cd3 100644
--- a/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.ts
+++ b/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.ts
@@ -20,19 +20,18 @@ import { HttpClient } from '@angular/common/http';
 import { Observable } from 'rxjs';
 import { map, catchError } from 'rxjs/operators';
 import { HttpUtil } from '../util/httpUtil';
-import { IAppConfig } from '../app.config.interface';
 import { SensorParserConfigHistory } from '../model/sensor-parser-config-history';
-import { APP_CONFIG } from '../app.config';
 import { SensorParserConfig } from '../model/sensor-parser-config';
 import { RestError } from '../model/rest-error';
+import {AppConfigService} from './app-config.service';
 
 @Injectable()
 export class SensorParserConfigHistoryService {
-  url = this.config.apiEndpoint + '/sensor/parser/config';
+  url = this.appConfigService.getApiRoot() + '/sensor/parser/config';
 
   constructor(
     private http: HttpClient,
-    @Inject(APP_CONFIG) private config: IAppConfig
+    private appConfigService: AppConfigService
   ) {}
 
   public get(name: string): Observable<RestError | SensorParserConfigHistory> {
diff --git a/metron-interface/metron-config/src/app/service/sensor-parser-config.service.spec.ts b/metron-interface/metron-config/src/app/service/sensor-parser-config.service.spec.ts
index 4f2750ed21..9baf883b08 100644
--- a/metron-interface/metron-config/src/app/service/sensor-parser-config.service.spec.ts
+++ b/metron-interface/metron-config/src/app/service/sensor-parser-config.service.spec.ts
@@ -24,6 +24,8 @@ import {
   HttpClientTestingModule,
   HttpTestingController
 } from '@angular/common/http/testing';
+import {AppConfigService} from './app-config.service';
+import {MockAppConfigService} from './mock.app-config.service';
 
 describe('SensorParserConfigService', () => {
   let mockBackend: HttpTestingController;
@@ -34,7 +36,7 @@ describe('SensorParserConfigService', () => {
       imports: [HttpClientTestingModule],
       providers: [
         SensorParserConfigService,
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     mockBackend = TestBed.get(HttpTestingController);
diff --git a/metron-interface/metron-config/src/app/service/sensor-parser-config.service.ts b/metron-interface/metron-config/src/app/service/sensor-parser-config.service.ts
index b7107d3f89..add7cb57ae 100644
--- a/metron-interface/metron-config/src/app/service/sensor-parser-config.service.ts
+++ b/metron-interface/metron-config/src/app/service/sensor-parser-config.service.ts
@@ -23,12 +23,11 @@ import { SensorParserConfig } from '../model/sensor-parser-config';
 import { HttpUtil } from '../util/httpUtil';
 import { ParseMessageRequest } from '../model/parse-message-request';
 import { RestError } from '../model/rest-error';
-import { IAppConfig } from '../app.config.interface';
-import { APP_CONFIG } from '../app.config';
+import {AppConfigService} from './app-config.service';
 
 @Injectable()
 export class SensorParserConfigService {
-  url = this.config.apiEndpoint + '/sensor/parser/config';
+  url = this.appConfigService.getApiRoot() + '/sensor/parser/config';
   selectedSensorParserConfig: SensorParserConfig;
 
   dataChangedSource = new Subject<string[]>();
@@ -36,7 +35,7 @@ export class SensorParserConfigService {
 
   constructor(
     private http: HttpClient,
-    @Inject(APP_CONFIG) private config: IAppConfig
+    private appConfigService: AppConfigService
   ) {}
 
   public post(
diff --git a/metron-interface/metron-config/src/app/service/stellar.service.spec.ts b/metron-interface/metron-config/src/app/service/stellar.service.spec.ts
index da329dc5c3..ce9f34fd47 100644
--- a/metron-interface/metron-config/src/app/service/stellar.service.spec.ts
+++ b/metron-interface/metron-config/src/app/service/stellar.service.spec.ts
@@ -19,12 +19,13 @@ import { TestBed } from '@angular/core/testing';
 import { StellarService } from './stellar.service';
 import { SensorParserContext } from '../model/sensor-parser-context';
 import { SensorParserConfig } from '../model/sensor-parser-config';
-import { APP_CONFIG, METRON_REST_CONFIG } from '../app.config';
 import {
   HttpClientTestingModule,
   HttpTestingController
 } from '@angular/common/http/testing';
 import { StellarFunctionDescription } from '../model/stellar-function-description';
+import {AppConfigService} from './app-config.service';
+import {MockAppConfigService} from './mock.app-config.service';
 
 describe('StellarService', () => {
   let mockBackend: HttpTestingController;
@@ -35,7 +36,7 @@ describe('StellarService', () => {
       imports: [HttpClientTestingModule],
       providers: [
         StellarService,
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     mockBackend = TestBed.get(HttpTestingController);
diff --git a/metron-interface/metron-config/src/app/service/stellar.service.ts b/metron-interface/metron-config/src/app/service/stellar.service.ts
index 97cb019a27..23ecd5fb50 100644
--- a/metron-interface/metron-config/src/app/service/stellar.service.ts
+++ b/metron-interface/metron-config/src/app/service/stellar.service.ts
@@ -23,16 +23,15 @@ import { map, catchError } from 'rxjs/operators';
 import { SensorParserContext } from '../model/sensor-parser-context';
 import { HttpUtil } from '../util/httpUtil';
 import { StellarFunctionDescription } from '../model/stellar-function-description';
-import { IAppConfig } from '../app.config.interface';
-import { APP_CONFIG } from '../app.config';
+import {AppConfigService} from './app-config.service';
 
 @Injectable()
 export class StellarService {
-  url = this.config.apiEndpoint + '/stellar';
+  url = this.appConfigService.getApiRoot() + '/stellar';
 
   constructor(
     private http: HttpClient,
-    @Inject(APP_CONFIG) private config: IAppConfig
+    private appConfigService: AppConfigService
   ) {}
 
   public validateRules(rules: string[]): Observable<{}> {
diff --git a/metron-interface/metron-config/src/app/service/storm.service.spec.ts b/metron-interface/metron-config/src/app/service/storm.service.spec.ts
index 840aff8028..6a7a5f3bfe 100644
--- a/metron-interface/metron-config/src/app/service/storm.service.spec.ts
+++ b/metron-interface/metron-config/src/app/service/storm.service.spec.ts
@@ -18,12 +18,13 @@
 import { TestBed } from '@angular/core/testing';
 import { TopologyStatus } from '../model/topology-status';
 import { TopologyResponse } from '../model/topology-response';
-import { APP_CONFIG, METRON_REST_CONFIG } from '../app.config';
 import { StormService } from './storm.service';
 import {
   HttpTestingController,
   HttpClientTestingModule
 } from '@angular/common/http/testing';
+import {AppConfigService} from './app-config.service';
+import {MockAppConfigService} from './mock.app-config.service';
 
 describe('StormService', () => {
   let mockBackend: HttpTestingController;
@@ -34,7 +35,7 @@ describe('StormService', () => {
       imports: [HttpClientTestingModule],
       providers: [
         StormService,
-        { provide: APP_CONFIG, useValue: METRON_REST_CONFIG }
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     });
     mockBackend = TestBed.get(HttpTestingController);
diff --git a/metron-interface/metron-config/src/app/service/storm.service.ts b/metron-interface/metron-config/src/app/service/storm.service.ts
index b38525c2c7..a45f7d12ff 100644
--- a/metron-interface/metron-config/src/app/service/storm.service.ts
+++ b/metron-interface/metron-config/src/app/service/storm.service.ts
@@ -20,18 +20,17 @@ import { HttpClient } from '@angular/common/http';
 import { HttpUtil } from '../util/httpUtil';
 import { TopologyStatus } from '../model/topology-status';
 import { TopologyResponse } from '../model/topology-response';
-import { APP_CONFIG } from '../app.config';
-import { IAppConfig } from '../app.config.interface';
 import { Observable, interval } from 'rxjs';
 import { map, catchError, switchMap, onErrorResumeNext } from 'rxjs/operators';
+import {AppConfigService} from './app-config.service';
 
 @Injectable()
 export class StormService {
-  url = this.config.apiEndpoint + '/storm';
+  url = this.appConfigService.getApiRoot() + '/storm';
 
   constructor(
     private http: HttpClient,
-    @Inject(APP_CONFIG) private config: IAppConfig
+    private appConfigService: AppConfigService
   ) {}
 
   public pollGetAll(): Observable<TopologyStatus[]> {
diff --git a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts
index cf1c495056..84225446d5 100644
--- a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts
+++ b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts
@@ -47,7 +47,7 @@ export class AceEditorComponent implements AfterViewInit, ControlValueAccessor {
   private onChangeCallback;
 
   constructor() {
-    ace.config.set('basePath', '/assets/ace');
+    ace.config.set('basePath', 'assets/ace');
   }
 
   ngAfterViewInit() {
diff --git a/metron-interface/metron-config/src/app/shared/auth-guard.spec.ts b/metron-interface/metron-config/src/app/shared/auth-guard.spec.ts
index 78c246af01..bffb4a3e8c 100644
--- a/metron-interface/metron-config/src/app/shared/auth-guard.spec.ts
+++ b/metron-interface/metron-config/src/app/shared/auth-guard.spec.ts
@@ -20,11 +20,14 @@ import {EventEmitter}     from '@angular/core';
 import {AuthGuard} from './auth-guard';
 import {AuthenticationService} from '../service/authentication.service';
 import {Router} from '@angular/router';
+import {HttpUtil} from "../util/httpUtil";
+import {HttpClientTestingModule} from "@angular/common/http/testing";
+import {AppConfigService} from '../service/app-config.service';
+import {MockAppConfigService} from '../service/mock.app-config.service';
 
-class MockAuthenticationService {
+class MockAuthenticationService extends AuthenticationService{
   _isAuthenticationChecked: boolean;
   _isAuthenticated: boolean;
-  onLoginEvent: EventEmitter<boolean> = new EventEmitter<boolean>();
 
   public isAuthenticationChecked(): boolean {
     return this._isAuthenticationChecked;
@@ -43,10 +46,12 @@ describe('AuthGuard', () => {
 
   beforeEach(async(() => {
     TestBed.configureTestingModule({
+      imports: [HttpClientTestingModule],
       providers: [
         AuthGuard,
         {provide: AuthenticationService, useClass: MockAuthenticationService},
-        {provide: Router, useClass: MockRouter}
+        {provide: Router, useClass: MockRouter},
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     })
       .compileComponents();
@@ -74,19 +79,18 @@ describe('AuthGuard', () => {
                                                         authenticationService: MockAuthenticationService,
                                                         router: MockRouter) => {
       authenticationService._isAuthenticationChecked = false;
+      authenticationService.onLoginEvent.next(true);
       authGuard.canActivate(null, null).subscribe(isUserValid => {
         expect(isUserValid).toBe(true);
       });
-      authenticationService.onLoginEvent.emit(true);
 
-
-      spyOn(router, 'navigateByUrl');
+      spyOn(HttpUtil, 'navigateToLogin');
       authenticationService._isAuthenticationChecked = false;
+      authenticationService.onLoginEvent.next(false);
       authGuard.canActivate(null, null).subscribe(isUserValid => {
         expect(isUserValid).toBe(false);
       });
-      authenticationService.onLoginEvent.emit(false);
-      expect(router.navigateByUrl).toHaveBeenCalledWith('/login');
+      expect(HttpUtil.navigateToLogin).toHaveBeenCalledWith();
 
     }));
 });
diff --git a/metron-interface/metron-config/src/app/shared/auth-guard.ts b/metron-interface/metron-config/src/app/shared/auth-guard.ts
index 3271853cfa..0d74d897c0 100644
--- a/metron-interface/metron-config/src/app/shared/auth-guard.ts
+++ b/metron-interface/metron-config/src/app/shared/auth-guard.ts
@@ -24,6 +24,7 @@ import {
 } from '@angular/router';
 import { Observable } from 'rxjs';
 import { AuthenticationService } from '../service/authentication.service';
+import {HttpUtil} from "../util/httpUtil";
 
 @Injectable()
 export class AuthGuard implements CanActivate {
@@ -40,7 +41,7 @@ export class AuthGuard implements CanActivate {
           } else {
             observer.next(false);
             observer.complete();
-            this.router.navigateByUrl('/login');
+            HttpUtil.navigateToLogin();
           }
         });
       });
diff --git a/metron-interface/metron-config/src/app/shared/login-guard.spec.ts b/metron-interface/metron-config/src/app/shared/login-guard.spec.ts
index a49b124361..8b32d0ce26 100644
--- a/metron-interface/metron-config/src/app/shared/login-guard.spec.ts
+++ b/metron-interface/metron-config/src/app/shared/login-guard.spec.ts
@@ -19,8 +19,11 @@ import {async, inject, TestBed} from '@angular/core/testing';
 import {AuthenticationService} from '../service/authentication.service';
 import {Router} from '@angular/router';
 import {LoginGuard} from './login-guard';
+import {HttpClientTestingModule} from "@angular/common/http/testing";
+import {AppConfigService} from '../service/app-config.service';
+import {MockAppConfigService} from '../service/mock.app-config.service';
 
-class MockAuthenticationService {
+class MockAuthenticationService extends AuthenticationService {
   public logout(): void {}
 }
 
@@ -32,10 +35,12 @@ describe('LoginGuard', () => {
 
   beforeEach(async(() => {
     TestBed.configureTestingModule({
+      imports: [HttpClientTestingModule],
       providers: [
         LoginGuard,
         {provide: AuthenticationService, useClass: MockAuthenticationService},
-        {provide: Router, useClass: MockRouter}
+        {provide: Router, useClass: MockRouter},
+        { provide: AppConfigService, useClass: MockAppConfigService }
       ]
     })
       .compileComponents();
@@ -50,11 +55,11 @@ describe('LoginGuard', () => {
   it('test when login is checked',
     inject([LoginGuard, AuthenticationService], (loginGuard: LoginGuard, authenticationService: MockAuthenticationService) => {
 
-      spyOn(authenticationService, 'logout');
+      spyOn(authenticationService, 'clearAuthentication');
 
       expect(loginGuard.canActivate(null, null)).toBe(true);
 
-      expect(authenticationService.logout).toHaveBeenCalled();
+      expect(authenticationService.clearAuthentication).toHaveBeenCalled();
 
   }));
 
diff --git a/metron-interface/metron-config/src/app/shared/login-guard.ts b/metron-interface/metron-config/src/app/shared/login-guard.ts
index d8b8f0d052..414f590867 100644
--- a/metron-interface/metron-config/src/app/shared/login-guard.ts
+++ b/metron-interface/metron-config/src/app/shared/login-guard.ts
@@ -32,7 +32,7 @@ export class LoginGuard implements CanActivate {
 
   canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
 
-    this.authService.logout();
+    this.authService.clearAuthentication();
 
     return true;
 
diff --git a/metron-interface/metron-config/src/app/util/httpUtil.ts b/metron-interface/metron-config/src/app/util/httpUtil.ts
index d8a21a5769..6bc036bdca 100644
--- a/metron-interface/metron-config/src/app/util/httpUtil.ts
+++ b/metron-interface/metron-config/src/app/util/httpUtil.ts
@@ -19,6 +19,7 @@ import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
 import { throwError, Observable } from 'rxjs';
 
 import { RestError } from '../model/rest-error';
+import {AppConfigService} from "../service/app-config.service";
 
 export class HttpUtil {
   public static extractString(res: HttpResponse<any>): string {
@@ -36,7 +37,7 @@ export class HttpUtil {
     // We'd also dig deeper into the error to get a better message
     let restError: RestError;
     if (res.status === 401) {
-      window.location.assign('/login?sessionExpired=true');
+      HttpUtil.navigateToLogin();
     } else if (res.status !== 404) {
       restError = res;
     } else {
@@ -45,4 +46,9 @@ export class HttpUtil {
     }
     return throwError(restError);
   }
+
+  public static navigateToLogin() {
+    let loginPath = AppConfigService.getAppConfigStatic()['loginPath'];
+    location.href = loginPath;
+  }
 }
diff --git a/metron-interface/metron-config/src/assets/app-config.json b/metron-interface/metron-config/src/assets/app-config.json
new file mode 100644
index 0000000000..e48507170d
--- /dev/null
+++ b/metron-interface/metron-config/src/assets/app-config.json
@@ -0,0 +1,4 @@
+{
+  "apiRoot": "/api/v1",
+  "loginPath": "/login"
+}
\ No newline at end of file
diff --git a/metron-interface/metron-config/src/index.html b/metron-interface/metron-config/src/index.html
index 2c3f8c5b94..1c8885685f 100644
--- a/metron-interface/metron-config/src/index.html
+++ b/metron-interface/metron-config/src/index.html
@@ -18,7 +18,8 @@
 <head>
   <meta charset="utf-8">
   <title>Metron Configuration</title>
-  <base href="/">
+    <!-- The base href is relative so that static assets are properly resolved when behind a reverse proxy -->
+  <base href="./">
 
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="icon" type="image/x-icon" href="favicon.ico">
diff --git a/metron-interface/metron-rest/pom.xml b/metron-interface/metron-rest/pom.xml
index 67a6820267..7d544017e9 100644
--- a/metron-interface/metron-rest/pom.xml
+++ b/metron-interface/metron-rest/pom.xml
@@ -39,6 +39,7 @@
         <spring.version>5.0.5.RELEASE</spring.version>
         <eclipse.link.version>2.6.4</eclipse.link.version>
         <jsonpath.version>2.4.0</jsonpath.version>
+        <jwt.version>4.41.2</jwt.version>
     </properties>
     <dependencies>
       <dependency>
@@ -458,6 +459,11 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>com.nimbusds</groupId>
+            <artifactId>nimbus-jose-jwt</artifactId>
+            <version>${jwt.version}</version>
+        </dependency>
     </dependencies>
 
     <dependencyManagement>
diff --git a/metron-interface/metron-rest/src/main/config/knox/conf/topologies/metron.xml b/metron-interface/metron-rest/src/main/config/knox/conf/topologies/metron.xml
new file mode 100644
index 0000000000..80550b2396
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/config/knox/conf/topologies/metron.xml
@@ -0,0 +1,56 @@
+<?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.
+  -->
+<topology>
+
+  <gateway>
+    <provider>
+      <role>federation</role>
+      <name>SSOCookieProvider</name>
+      <enabled>true</enabled>
+      <param>
+        <name>sso.authentication.provider.url</name>
+        <value>https://node1:8443/gateway/metronsso/api/v1/websso</value>
+      </param>
+    </provider>
+
+    <provider>
+      <role>identity-assertion</role>
+      <name>Default</name>
+      <enabled>true</enabled>
+    </provider>
+
+    <provider>
+      <role>authorization</role>
+      <name>AclsAuthz</name>
+      <enabled>true</enabled>
+    </provider>
+
+  </gateway>
+
+  <service>
+    <role>METRON-REST</role>
+    <url>http://localhost:8082</url>
+  </service>
+
+  <service>
+    <role>METRON-ALERTS</role>
+    <url>http://localhost:4201</url>
+  </service>
+
+  <service>
+    <role>METRON-MANAGEMENT</role>
+    <url>http://localhost:4200</url>
+  </service>
+
+</topology>
\ No newline at end of file
diff --git a/metron-interface/metron-rest/src/main/config/knox/conf/topologies/metronsso.xml b/metron-interface/metron-rest/src/main/config/knox/conf/topologies/metronsso.xml
new file mode 100644
index 0000000000..34f035d3a7
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/config/knox/conf/topologies/metronsso.xml
@@ -0,0 +1,100 @@
+<?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.
+  -->
+<topology>
+  <gateway>
+    <provider>
+      <role>webappsec</role>
+      <name>WebAppSec</name>
+      <enabled>true</enabled>
+      <param><name>xframe.options.enabled</name><value>true</value></param>
+    </provider>
+
+    <provider>
+      <role>authentication</role>
+      <name>ShiroProvider</name>
+      <enabled>true</enabled>
+      <param>
+        <name>sessionTimeout</name>
+        <value>30</value>
+      </param>
+      <param>
+        <name>redirectToUrl</name>
+        <value>/gateway/metronsso/knoxauth/login.html</value>
+      </param>
+      <param>
+        <name>restrictedCookies</name>
+        <value>rememberme,WWW-Authenticate</value>
+      </param>
+      <param>
+        <name>main.ldapRealm</name>
+        <value>org.apache.hadoop.gateway.shirorealm.KnoxLdapRealm</value>
+      </param>
+      <param>
+        <name>main.ldapContextFactory</name>
+        <value>org.apache.hadoop.gateway.shirorealm.KnoxLdapContextFactory</value>
+      </param>
+      <param>
+        <name>main.ldapRealm.contextFactory</name>
+        <value>$ldapContextFactory</value>
+      </param>
+      <param>
+        <name>main.ldapRealm.userDnTemplate</name>
+        <value>uid={0},ou=people,dc=hadoop,dc=apache,dc=org</value>
+      </param>
+      <param>
+        <name>main.ldapRealm.contextFactory.url</name>
+        <value>ldap://localhost:33389</value>
+      </param>
+      <param>
+        <name>main.ldapRealm.authenticationCachingEnabled</name>
+        <value>false</value>
+      </param>
+      <param>
+        <name>main.ldapRealm.contextFactory.authenticationMechanism</name>
+        <value>simple</value>
+      </param>
+      <param>
+        <name>urls./**</name>
+        <value>authcBasic</value>
+      </param>
+    </provider>
+
+    <provider>
+      <role>identity-assertion</role>
+      <name>Default</name>
+      <enabled>true</enabled>
+    </provider>
+  </gateway>
+
+  <application>
+    <name>knoxauth</name>
+  </application>
+
+  <service>
+    <role>KNOXSSO</role>
+    <param>
+      <name>knoxsso.cookie.secure.only</name>
+      <value>false</value>
+    </param>
+    <param>
+      <name>knoxsso.token.ttl</name>
+      <value>30000</value>
+    </param>
+    <param>
+      <name>knoxsso.redirect.whitelist.regex</name>
+      <value>^https?:\/\/(localhost|127\.0\.0\.1|0:0:0:0:0:0:0:1|::1|node1):[0-9].*$</value>
+    </param>
+  </service>
+
+</topology>
\ No newline at end of file
diff --git a/metron-interface/metron-rest/src/main/config/knox/data/services/alerts/rewrite.xml b/metron-interface/metron-rest/src/main/config/knox/data/services/alerts/rewrite.xml
new file mode 100644
index 0000000000..f4e797b6bb
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/config/knox/data/services/alerts/rewrite.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+<rules>
+  <rule dir="IN" name="METRON-ALERTS/metron-alerts/inbound/root" pattern="*://*:*/**/metron-alerts/">
+    <rewrite template="{$serviceUrl[METRON-ALERTS]}/"/>
+  </rule>
+  <rule dir="IN" name="METRON-ALERTS/metron-alerts/inbound/path" pattern="*://*:*/**/metron-alerts/{**}">
+    <rewrite template="{$serviceUrl[METRON-ALERTS]}/{**}"/>
+  </rule>
+  <rule dir="IN" name="METRON-ALERTS/metron-alerts/inbound/query" pattern="*://*:*/**/metron-alerts/{**}?{**}">
+    <rewrite template="{$serviceUrl[METRON-ALERTS]}/{**}?{**}"/>
+  </rule>
+</rules>
\ No newline at end of file
diff --git a/metron-interface/metron-rest/src/main/config/knox/data/services/alerts/service.xml b/metron-interface/metron-rest/src/main/config/knox/data/services/alerts/service.xml
new file mode 100644
index 0000000000..a09be990de
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/config/knox/data/services/alerts/service.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<service role="METRON-ALERTS" name="metron-alerts" version="0.6.1">
+  <routes>
+    <route path="/metron-alerts/"/>
+    <route path="/metron-alerts/**"/>
+    <route path="/metron-alerts/**?**"/>
+  </routes>
+  <dispatch classname="org.apache.hadoop.gateway.dispatch.PassAllHeadersDispatch"/>
+</service>
\ No newline at end of file
diff --git a/metron-interface/metron-rest/src/main/config/knox/data/services/management/rewrite.xml b/metron-interface/metron-rest/src/main/config/knox/data/services/management/rewrite.xml
new file mode 100644
index 0000000000..be681a7ddd
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/config/knox/data/services/management/rewrite.xml
@@ -0,0 +1,24 @@
+<?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.
+  -->
+<rules>
+  <rule dir="IN" name="METRON-MANAGEMENT/metron-management/inbound/root" pattern="*://*:*/**/metron-management/">
+    <rewrite template="{$serviceUrl[METRON-MANAGEMENT]}/"/>
+  </rule>
+  <rule dir="IN" name="METRON-MANAGEMENT/metron-management/inbound/path" pattern="*://*:*/**/metron-management/{**}">
+    <rewrite template="{$serviceUrl[METRON-MANAGEMENT]}/{**}"/>
+  </rule>
+  <rule dir="IN" name="METRON-MANAGEMENT/metron-management/inbound/query" pattern="*://*:*/**/metron-management/{**}?{**}">
+    <rewrite template="{$serviceUrl[METRON-MANAGEMENT]}/{**}?{**}"/>
+  </rule>
+</rules>
\ No newline at end of file
diff --git a/metron-interface/metron-rest/src/main/config/knox/data/services/management/service.xml b/metron-interface/metron-rest/src/main/config/knox/data/services/management/service.xml
new file mode 100644
index 0000000000..02ddc7729c
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/config/knox/data/services/management/service.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<service role="METRON-MANAGEMENT" name="metron-management" version="0.6.1">
+  <routes>
+    <route path="/metron-management/"/>
+    <route path="/metron-management/**"/>
+    <route path="/metron-management/**?**"/>
+  </routes>
+  <dispatch classname="org.apache.hadoop.gateway.dispatch.PassAllHeadersDispatch"/>
+</service>
\ No newline at end of file
diff --git a/metron-interface/metron-rest/src/main/config/knox/data/services/rest/rewrite.xml b/metron-interface/metron-rest/src/main/config/knox/data/services/rest/rewrite.xml
new file mode 100644
index 0000000000..fabed06985
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/config/knox/data/services/rest/rewrite.xml
@@ -0,0 +1,18 @@
+<?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.
+  -->
+<rules>
+  <rule dir="IN" name="METRON-REST/metron-rest/inbound" pattern="*://*:*/**/metron-rest/{path=**}?{**}">
+    <rewrite template="{$serviceUrl[METRON-REST]}/{path=**}?{**}"/>
+  </rule>
+</rules>
\ No newline at end of file
diff --git a/metron-interface/metron-rest/src/main/config/knox/data/services/rest/service.xml b/metron-interface/metron-rest/src/main/config/knox/data/services/rest/service.xml
new file mode 100644
index 0000000000..d9bb56bced
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/config/knox/data/services/rest/service.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+<service role="METRON-REST" name="metron-rest" version="0.6.1">
+  <routes>
+    <route path="/metron-rest/**"/>
+  </routes>
+  <dispatch classname="org.apache.hadoop.gateway.dispatch.PassAllHeadersDispatch"/>
+</service>
\ No newline at end of file
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
index 80ac2bf3ab..b8a8306cdd 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/MetronRestConstants.java
@@ -25,6 +25,7 @@
   public static final String TEST_PROFILE = "test";
   public static final String LDAP_PROFILE = "ldap";
   public static final String DOCKER_PROFILE = "docker";
+  public static final String KNOX_PROFILE = "knox";
   public static final String CSRF_ENABLE_PROFILE = "csrf-enable";
 
   public static final String GROK_TEMP_PATH_SPRING_PROPERTY = "grok.path.temp";
@@ -71,6 +72,7 @@
   public static final String META_DAO_IMPL = "meta.dao.impl";
   public static final String META_DAO_SORT = "meta.dao.sort";
 
+  public static final String SECURITY_ROLE_PREFIX = "ROLE_";
   public static final String SECURITY_ROLE_USER = "USER";
   public static final String SECURITY_ROLE_ADMIN = "ADMIN";
 
@@ -87,4 +89,10 @@
   public static final String PCAP_PDML_SCRIPT_PATH_SPRING_PROPERTY = "pcap.pdml.script.path";
   public static final String PCAP_YARN_QUEUE_SPRING_PROPERTY = "pcap.yarn.queue";
   public static final String PCAP_FINALIZER_THREADPOOL_SIZE_SPRING_PROPERTY = "pcap.finalizer.threadpool.size";
+
+  public static final String LDAP_PROVIDER_URL_SPRING_PROPERTY = "ldap.provider.url";
+  public static final String LDAP_PROVIDER_USERDN_SPRING_PROPERTY = "ldap.provider.userdn";
+  public static final String LDAP_PROVIDER_PASSWORD_SPRING_PROPERTY = "ldap.provider.password";
+
+  public static final String KNOX_ROOT_SPRING_PROPERTY = "knox.root";
 }
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/KnoxSSOAuthenticationFilter.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/KnoxSSOAuthenticationFilter.java
new file mode 100644
index 0000000000..d140a2ff8f
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/KnoxSSOAuthenticationFilter.java
@@ -0,0 +1,278 @@
+/**
+ * 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.metron.rest.config;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSObject;
+import com.nimbusds.jose.JWSVerifier;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jwt.SignedJWT;
+import org.apache.metron.rest.security.SecurityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.ldap.core.AttributesMapper;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.support.LdapNameBuilder;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetails;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_PREFIX;
+import static org.springframework.ldap.query.LdapQueryBuilder.query;
+
+/**
+ * This class is a Servlet Filter that authenticates a Knox SSO token.  The token is stored in a cookie and is
+ * verified against a public Knox key.  The token expiration and begin time are also validated.  Upon successful validation,
+ * a Spring Authentication object is built from the user name and user groups queried from LDAP.  Currently, user groups are
+ * mapped directly to Spring roles and prepended with "ROLE_".
+ */
+public class KnoxSSOAuthenticationFilter implements Filter {
+  private static final Logger LOG = LoggerFactory.getLogger(KnoxSSOAuthenticationFilter.class);
+
+  private String userSearchBase;
+  private Path knoxKeyFile;
+  private String knoxKeyString;
+  private String knoxCookie;
+  private LdapTemplate ldapTemplate;
+
+  public KnoxSSOAuthenticationFilter(String userSearchBase,
+                                     Path knoxKeyFile,
+                                     String knoxKeyString,
+                                     String knoxCookie,
+                                     LdapTemplate ldapTemplate) {
+    this.userSearchBase = userSearchBase;
+    this.knoxKeyFile = knoxKeyFile;
+    this.knoxKeyString = knoxKeyString;
+    this.knoxCookie = knoxCookie;
+    if (ldapTemplate == null) {
+      throw new IllegalStateException("KnoxSSO requires LDAP. You must add 'ldap' to the active profiles.");
+    }
+    this.ldapTemplate = ldapTemplate;
+  }
+
+  @Override
+  public void init(FilterConfig filterConfig) {
+  }
+
+  @Override
+  public void destroy() {
+  }
+
+  /**
+   * Extracts the Knox token from the configured cookie.  If basic authentication headers are present, SSO authentication
+   * is skipped.
+   * @param request ServletRequest
+   * @param response ServletResponse
+   * @param chain FilterChain
+   * @throws IOException
+   * @throws ServletException
+   */
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+          throws IOException, ServletException {
+    HttpServletRequest httpRequest = (HttpServletRequest) request;
+
+    // If a basic authentication header is present, use that to authenticate and skip SSO
+    String authHeader = httpRequest.getHeader("Authorization");
+    if (authHeader == null || !authHeader.startsWith("Basic")) {
+      String serializedJWT = getJWTFromCookie(httpRequest);
+      if (serializedJWT != null) {
+        SignedJWT jwtToken;
+        try {
+          jwtToken = SignedJWT.parse(serializedJWT);
+          String userName = jwtToken.getJWTClaimsSet().getSubject();
+          LOG.info("SSO login user : {} ", userName);
+          if (isValid(jwtToken, userName)) {
+            Authentication authentication = getAuthentication(userName, httpRequest);
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+          }
+        } catch (ParseException e) {
+          LOG.warn("Unable to parse the JWT token", e);
+        }
+      }
+    }
+    chain.doFilter(request, response);
+  }
+
+  /**
+   * Validates a Knox token with expiration and begin times and verifies the token with a public Knox key.
+   * @param jwtToken Knox token
+   * @param userName User name associated with the token
+   * @return Whether a token is valid or not
+   * @throws ParseException JWT Token could not be parsed.
+   */
+  protected boolean isValid(SignedJWT jwtToken, String userName) throws ParseException {
+    // Verify the user name is present
+    if (userName == null || userName.isEmpty()) {
+      LOG.info("Could not find user name in SSO token");
+      return false;
+    }
+
+    Date now = new Date();
+
+    // Verify the token has not expired
+    Date expirationTime = jwtToken.getJWTClaimsSet().getExpirationTime();
+    if (expirationTime != null && now.after(expirationTime)) {
+      LOG.info("SSO token expired: {} ", userName);
+      return false;
+    }
+
+    // Verify the token is not before time
+    Date notBeforeTime = jwtToken.getJWTClaimsSet().getNotBeforeTime();
+    if (notBeforeTime != null && now.before(notBeforeTime)) {
+      LOG.info("SSO token not yet valid: {} ", userName);
+      return false;
+    }
+
+    return validateSignature(jwtToken);
+  }
+
+  /**
+   * Verify the signature of the JWT token in this method. This method depends on
+   * the public key that was established during init based upon the provisioned
+   * public key. Override this method in subclasses in order to customize the
+   * signature verification behavior.
+   *
+   * @param jwtToken The token that contains the signature to be validated.
+   * @return valid true if signature verifies successfully; false otherwise
+   */
+  protected boolean validateSignature(SignedJWT jwtToken) {
+    // Verify the token signature algorithm was as expected
+    String receivedSigAlg = jwtToken.getHeader().getAlgorithm().getName();
+
+    if (!receivedSigAlg.equals(JWSAlgorithm.RS256.getName())) {
+      return false;
+    }
+
+    // Verify the token has been properly signed
+    if (JWSObject.State.SIGNED == jwtToken.getState()) {
+      LOG.debug("SSO token is in a SIGNED state");
+      if (jwtToken.getSignature() != null) {
+        LOG.debug("SSO token signature is not null");
+        try {
+          JWSVerifier verifier = new RSASSAVerifier(SecurityUtils.parseRSAPublicKey(getKnoxKey()));
+          if (jwtToken.verify(verifier)) {
+            LOG.debug("SSO token has been successfully verified");
+            return true;
+          } else {
+            LOG.warn("SSO signature verification failed. Please check the public key.");
+          }
+        } catch (Exception e) {
+          LOG.warn("Error while validating signature", e);
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Encapsulate the acquisition of the JWT token from HTTP cookies within the
+   * request.
+   *
+   * @param req ServletRequest to get the JWT token from
+   * @return serialized JWT token
+   */
+  protected String getJWTFromCookie(HttpServletRequest req) {
+    String serializedJWT = null;
+    Cookie[] cookies = req.getCookies();
+    if (cookies != null) {
+      for (Cookie cookie : cookies) {
+        LOG.debug(String.format("Found cookie: %s [%s]", cookie.getName(), cookie.getValue()));
+        if (knoxCookie.equals(cookie.getName())) {
+          if (LOG.isDebugEnabled()) {
+            LOG.debug(knoxCookie + " cookie has been found and is being processed");
+          }
+          serializedJWT = cookie.getValue();
+          break;
+        }
+      }
+    } else {
+      if (LOG.isDebugEnabled()) {
+        LOG.debug(knoxCookie + " not found");
+      }
+    }
+    return serializedJWT;
+  }
+
+  /**
+   * A public Knox key can either be passed in directly or read from a file.
+   * @return Public Knox key
+   * @throws IOException There was a problem reading the Knox key file.
+   */
+  protected String getKnoxKey() throws IOException {
+    String knoxKey;
+    if ((this.knoxKeyString == null || this.knoxKeyString.isEmpty()) && this.knoxKeyFile != null) {
+      List<String> keyLines = Files.readAllLines(knoxKeyFile, StandardCharsets.UTF_8);
+      knoxKey = String.join("", keyLines);
+    } else {
+      knoxKey = this.knoxKeyString;
+    }
+    return knoxKey;
+  }
+
+  /**
+   * Builds the Spring Authentication object using the supplied user name and groups looked up from LDAP.  Groups are currently
+   * mapped directly to Spring roles by converting to upper case and prepending the name with "ROLE_".
+   * @param userName The username to build the Authentication object with.
+   * @param httpRequest HttpServletRequest
+   * @return Authentication object for the given user.
+   */
+  protected Authentication getAuthentication(String userName, HttpServletRequest httpRequest) {
+    String ldapName = LdapNameBuilder.newInstance().add(userSearchBase).add("uid", userName).build().toString();
+
+    // Search ldap for a user's groups and convert to a Spring role
+    List<GrantedAuthority> grantedAuths = ldapTemplate.search(query()
+            .where("objectclass")
+            .is("groupOfNames")
+            .and("member")
+            .is(ldapName), (AttributesMapper<String>) attrs -> (String) attrs.get("cn").get())
+            .stream()
+            .map(group -> String.format("%s%s", SECURITY_ROLE_PREFIX, group.toUpperCase()))
+            .map(SimpleGrantedAuthority::new).collect(Collectors.toList());
+
+    final UserDetails principal = new User(userName, "", grantedAuths);
+    final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
+            principal, "", grantedAuths);
+    WebAuthenticationDetails webDetails = new WebAuthenticationDetails(httpRequest);
+    authentication.setDetails(webDetails);
+    return authentication;
+  }
+
+}
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/LdapConfig.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/LdapConfig.java
new file mode 100644
index 0000000000..3bb4cd1195
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/LdapConfig.java
@@ -0,0 +1,52 @@
+/**
+ * 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.metron.rest.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.core.env.Environment;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.core.support.LdapContextSource;
+
+import static org.apache.metron.rest.MetronRestConstants.LDAP_PROFILE;
+import static org.apache.metron.rest.MetronRestConstants.LDAP_PROVIDER_PASSWORD_SPRING_PROPERTY;
+import static org.apache.metron.rest.MetronRestConstants.LDAP_PROVIDER_URL_SPRING_PROPERTY;
+import static org.apache.metron.rest.MetronRestConstants.LDAP_PROVIDER_USERDN_SPRING_PROPERTY;
+
+@Configuration
+@Profile(LDAP_PROFILE)
+public class LdapConfig {
+
+  @Autowired
+  private Environment environment;
+
+  @Bean
+  public LdapTemplate ldapTemplate() {
+    LdapContextSource contextSource = new LdapContextSource();
+
+    contextSource.setUrl(environment.getProperty(LDAP_PROVIDER_URL_SPRING_PROPERTY));
+    contextSource.setUserDn(environment.getProperty(LDAP_PROVIDER_USERDN_SPRING_PROPERTY));
+    contextSource.setPassword(environment.getProperty(LDAP_PROVIDER_PASSWORD_SPRING_PROPERTY));
+    contextSource.afterPropertiesSet();
+
+    return new LdapTemplate(contextSource);
+  }
+
+}
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/SwaggerConfig.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/SwaggerConfig.java
index 564ff32896..d0da8cc861 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/SwaggerConfig.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/SwaggerConfig.java
@@ -17,22 +17,64 @@
  */
 package org.apache.metron.rest.config;
 
+import org.apache.metron.rest.MetronRestConstants;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
 import org.springframework.web.bind.annotation.RestController;
 import springfox.documentation.builders.PathSelectors;
 import springfox.documentation.builders.RequestHandlerSelectors;
 import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.paths.RelativePathProvider;
 import springfox.documentation.spring.web.plugins.Docket;
 import springfox.documentation.swagger2.annotations.EnableSwagger2;
 
+import javax.servlet.ServletContext;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.apache.metron.rest.MetronRestConstants.KNOX_PROFILE;
+
 @Configuration
 @EnableSwagger2
 public class SwaggerConfig {
+
+  @Autowired
+  private Environment environment;
+
+  @Autowired
+  ServletContext servletContext;
+
   @Bean
   public Docket api() {
-    return new Docket(DocumentationType.SWAGGER_2)
-            .select()
+    List<String> activeProfiles = Arrays.asList(environment.getActiveProfiles());
+    Docket docket = new Docket(DocumentationType.SWAGGER_2);
+    if (activeProfiles.contains(KNOX_PROFILE)) {
+      String knoxRoot = environment.getProperty(MetronRestConstants.KNOX_ROOT_SPRING_PROPERTY, String.class, "");
+      docket = docket.pathProvider(new RelativePathProvider (servletContext) {
+        @Override
+        protected String applicationPath() {
+          return knoxRoot;
+        }
+
+        @Override
+        protected String getDocumentationPath() {
+          return knoxRoot;
+        }
+
+        @Override
+        public String getApplicationBasePath() {
+          return knoxRoot;
+        }
+
+        @Override
+        public String getOperationPath(String operationPath) {
+          return knoxRoot + super.getOperationPath(operationPath);
+        }
+      });
+    }
+    return docket.select()
             .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
             .paths(PathSelectors.any())
             .build();
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
index 7ca3a46e4b..835889c6a9 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/config/WebSecurityConfig.java
@@ -20,8 +20,16 @@
 import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_ADMIN;
 import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_USER;
 
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.interfaces.RSAPublicKey;
 import java.util.Arrays;
 import java.util.List;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import javax.sql.DataSource;
 import org.apache.metron.rest.MetronRestConstants;
 import org.slf4j.Logger;
@@ -31,16 +39,32 @@
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.env.Environment;
+import org.springframework.http.HttpMethod;
+import org.springframework.ldap.core.AttributesMapper;
+import org.springframework.ldap.core.DirContextOperations;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.core.support.LdapContextSource;
+import org.springframework.security.authentication.ProviderManager;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.Authentication;
 import org.springframework.security.crypto.password.LdapShaPasswordEncoder;
 import org.springframework.security.crypto.password.NoOpPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
+import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
+import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
+import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
+import org.springframework.security.web.authentication.logout.LogoutHandler;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
 import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
@@ -58,27 +82,49 @@
     @Autowired
     private DataSource dataSource;
 
+    @Autowired(required = false)
+    private LdapTemplate ldapTemplate;
+
     @Value("${ldap.provider.url}")
     private String providerUrl;
+
     @Value("${ldap.provider.userdn}")
     private String providerUserDn;
+
     @Value("${ldap.provider.password}")
     private String providerPassword;
+
     @Value("${ldap.user.dn.patterns}")
     private String userDnPatterns;
+
     @Value("${ldap.user.passwordAttribute}")
     private String passwordAttribute;
+
     @Value("${ldap.user.searchBase}")
     private String userSearchBase;
+
     @Value("${ldap.user.searchFilter}")
     private String userSearchFilter;
+
     @Value("${ldap.group.searchBase}")
     private String groupSearchBase;
+
     @Value("${ldap.group.roleAttribute}")
     private String groupRoleAttribute;
+
     @Value("${ldap.group.searchFilter}")
     private String groupSearchFilter;
 
+    @Value("${knox.sso.pubkeyFile:}")
+    private Path knoxKeyFile;
+
+    @Value("${knox.sso.pubkey:}")
+    private String knoxKeyString;
+
+    @Value("${knox.sso.cookie:hadoop-jwt}")
+    private String knoxCookie;
+
+
     @RequestMapping(value = {"/login", "/logout", "/sensors", "/sensors*/**"}, method = RequestMethod.GET)
     public String handleNGRequests() {
         return "forward:/index.html";
@@ -100,14 +146,21 @@ protected void configure(HttpSecurity http) throws Exception {
                 .and().httpBasic()
                 .and()
                 .logout()
+                .logoutUrl("/api/v1/logout")
                 .logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
                 .invalidateHttpSession(true)
-                .deleteCookies("JSESSIONID");
-        if (Arrays.asList(environment.getActiveProfiles()).contains(MetronRestConstants.CSRF_ENABLE_PROFILE)) {
+                .deleteCookies("JSESSIONID", knoxCookie);
+
+        List<String> activeProfiles = Arrays.asList(environment.getActiveProfiles());
+        if (activeProfiles.contains(MetronRestConstants.CSRF_ENABLE_PROFILE)) {
             http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
         } else {
             http.csrf().disable();
         }
+        if (activeProfiles.contains(MetronRestConstants.KNOX_PROFILE)) {
+          http.addFilterAt(new KnoxSSOAuthenticationFilter(userSearchBase, knoxKeyFile, knoxKeyString,
+                  knoxCookie, ldapTemplate), UsernamePasswordAuthenticationFilter.class);
+        }
     }
 
     @Autowired
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsUIController.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsUIController.java
index fe2968fe67..6125017d61 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsUIController.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/controller/AlertsUIController.java
@@ -18,6 +18,7 @@
 package org.apache.metron.rest.controller;
 
 import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_ADMIN;
+import static org.apache.metron.rest.MetronRestConstants.SECURITY_ROLE_PREFIX;
 
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -74,7 +75,7 @@
     }
   }
 
-  @Secured({"ROLE_" + SECURITY_ROLE_ADMIN})
+  @Secured({SECURITY_ROLE_PREFIX + SECURITY_ROLE_ADMIN})
   @ApiOperation(value = "Retrieves all users' settings.  Only users that are part of "
           + "the \"ROLE_ADMIN\" role are allowed to get all user settings.")
   @ApiResponses(value = {@ApiResponse(message = "List of all user settings", code = 200),
@@ -103,7 +104,7 @@
     return responseEntity;
   }
 
-  @Secured({"ROLE_" + SECURITY_ROLE_ADMIN})
+  @Secured({SECURITY_ROLE_PREFIX + SECURITY_ROLE_ADMIN})
   @ApiOperation(value = "Deletes a user's settings.  Only users that are part of "
           + "the \"ROLE_ADMIN\" role are allowed to delete user settings.")
   @ApiResponses(value = {@ApiResponse(message = "User settings were deleted", code = 200),
diff --git a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java
index ed10f145f9..5da1a06b30 100644
--- a/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java
+++ b/metron-interface/metron-rest/src/main/java/org/apache/metron/rest/security/SecurityUtils.java
@@ -21,6 +21,15 @@
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetails;
 
+import java.io.ByteArrayInputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+
 public class SecurityUtils {
 
   /** Returns the username of the currently logged in user.
@@ -37,4 +46,28 @@ public static String getCurrentUser() {
     }
     return user;
   }
+
+  public static RSAPublicKey parseRSAPublicKey(String pem)
+          throws CertificateException, UnsupportedEncodingException {
+    String PEM_HEADER = "-----BEGIN CERTIFICATE-----\n";
+    String PEM_FOOTER = "\n-----END CERTIFICATE-----";
+    String fullPem = (pem.startsWith(PEM_HEADER) && pem.endsWith(PEM_FOOTER)) ? pem : PEM_HEADER + pem + PEM_FOOTER;
+    PublicKey key = null;
+    try {
+      CertificateFactory fact = CertificateFactory.getInstance("X.509");
+      ByteArrayInputStream is = new ByteArrayInputStream(fullPem.getBytes(StandardCharsets.UTF_8));
+      X509Certificate cer = (X509Certificate) fact.generateCertificate(is);
+      key = cer.getPublicKey();
+    } catch (CertificateException ce) {
+      String message = null;
+      if (pem.startsWith(PEM_HEADER)) {
+        message = "CertificateException - be sure not to include PEM header "
+                + "and footer in the PEM configuration element.";
+      } else {
+        message = "CertificateException - PEM may be corrupt";
+      }
+      throw new CertificateException(message, ce);
+    }
+    return (RSAPublicKey) key;
+  }
 }
diff --git a/metron-interface/metron-rest/src/main/scripts/install_metron_knox.sh b/metron-interface/metron-rest/src/main/scripts/install_metron_knox.sh
new file mode 100755
index 0000000000..7e87393b9a
--- /dev/null
+++ b/metron-interface/metron-rest/src/main/scripts/install_metron_knox.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+#
+# 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.
+#
+METRON_VERSION=${project.version}
+METRON_HOME=${METRON_HOME:-/usr/metron/${METRON_VERSION}}
+KNOX_HOME=${KNOX_HOME:-/usr/hdp/current/knox-server}
+KNOX_METRON_REST_DIR=$KNOX_HOME/data/services/metron-rest/$METRON_VERSION
+KNOX_METRON_ALERTS_DIR=$KNOX_HOME/data/services/metron-alerts/$METRON_VERSION
+KNOX_METRON_MANAGEMENT_DIR=$KNOX_HOME/data/services/metron-management/$METRON_VERSION
+
+mkdir -p $KNOX_METRON_REST_DIR
+mkdir -p $KNOX_METRON_ALERTS_DIR
+mkdir -p $KNOX_METRON_MANAGEMENT_DIR
+
+cp $METRON_HOME/config/knox/data/services/rest/* $KNOX_METRON_REST_DIR
+cp $METRON_HOME/config/knox/data/services/alerts/* $KNOX_METRON_ALERTS_DIR
+cp $METRON_HOME/config/knox/data/services/management/* $KNOX_METRON_MANAGEMENT_DIR
+cp $METRON_HOME/config/knox/conf/topologies/metron.xml $KNOX_HOME/conf/topologies
+cp $METRON_HOME/config/knox/conf/topologies/metronsso.xml $KNOX_HOME/conf/topologies
diff --git a/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/KnoxSSOAuthenticationFilterTest.java b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/KnoxSSOAuthenticationFilterTest.java
new file mode 100644
index 0000000000..e00bc70f89
--- /dev/null
+++ b/metron-interface/metron-rest/src/test/java/org/apache/metron/rest/config/KnoxSSOAuthenticationFilterTest.java
@@ -0,0 +1,388 @@
+/**
+ * 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.metron.rest.config;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.JWSObject;
+import com.nimbusds.jose.crypto.RSASSAVerifier;
+import com.nimbusds.jose.util.Base64URL;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import org.apache.commons.io.FileUtils;
+import org.apache.metron.rest.security.SecurityUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.springframework.ldap.core.AttributesMapper;
+import org.springframework.ldap.core.LdapTemplate;
+import org.springframework.ldap.query.LdapQuery;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.interfaces.RSAPublicKey;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.Date;
+
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({KnoxSSOAuthenticationFilter.class, SignedJWT.class, SecurityContextHolder.class, SecurityUtils.class, RSASSAVerifier.class})
+public class KnoxSSOAuthenticationFilterTest {
+
+  @Rule
+  public final ExpectedException exception = ExpectedException.none();
+
+  @Test
+  public void shouldThrowExceptionOnMissingLdapTemplate() {
+    exception.expect(IllegalStateException.class);
+    exception.expectMessage("KnoxSSO requires LDAP. You must add 'ldap' to the active profiles.");
+
+    new KnoxSSOAuthenticationFilter("userSearchBase",
+            mock(Path.class),
+            "knoxKeyString",
+            "knoxCookie",
+            null
+            );
+  }
+
+  @Test
+  public void doFilterShouldProperlySetAuthentication() throws Exception {
+    KnoxSSOAuthenticationFilter knoxSSOAuthenticationFilter = spy(new KnoxSSOAuthenticationFilter("userSearchBase",
+            mock(Path.class),
+            "knoxKeyString",
+            "knoxCookie",
+            mock(LdapTemplate.class)
+    ));
+    HttpServletRequest request = mock(HttpServletRequest.class);
+    ServletResponse response = mock(ServletResponse.class);
+    FilterChain chain = mock(FilterChain.class);
+    SignedJWT signedJWT = mock(SignedJWT.class);
+    mockStatic(SignedJWT.class);
+    JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().subject("userName").build();
+    Authentication authentication = mock(Authentication.class);
+    SecurityContext securityContext = mock(SecurityContext.class);
+    mockStatic(SecurityContextHolder.class);
+
+    when(request.getHeader("Authorization")).thenReturn(null);
+    doReturn("serializedJWT").when(knoxSSOAuthenticationFilter).getJWTFromCookie(request);
+    when(SignedJWT.parse("serializedJWT")).thenReturn(signedJWT);
+    when(signedJWT.getJWTClaimsSet()).thenReturn(jwtClaimsSet);
+    doReturn(true).when(knoxSSOAuthenticationFilter).isValid(signedJWT, "userName");
+    doReturn(authentication).when(knoxSSOAuthenticationFilter).getAuthentication("userName", request);
+    when(SecurityContextHolder.getContext()).thenReturn(securityContext);
+
+    knoxSSOAuthenticationFilter.doFilter(request, response, chain);
+
+    verify(securityContext).setAuthentication(authentication);
+    verify(chain).doFilter(request, response);
+    verifyNoMoreInteractions(chain, securityContext);
+  }
+
+  @Test
+  public void doFilterShouldContinueOnBasicAuthenticationHeader() throws Exception {
+    KnoxSSOAuthenticationFilter knoxSSOAuthenticationFilter = spy(new KnoxSSOAuthenticationFilter("userSearchBase",
+            mock(Path.class),
+            "knoxKeyString",
+            "knoxCookie",
+            mock(LdapTemplate.class)
+    ));
+    HttpServletRequest request = mock(HttpServletRequest.class);
+    ServletResponse response = mock(ServletResponse.class);
+    FilterChain chain = mock(FilterChain.class);
+
+    when(request.getHeader("Authorization")).thenReturn("Basic ");
+
+    knoxSSOAuthenticationFilter.doFilter(request, response, chain);
+
+    verify(knoxSSOAuthenticationFilter, times(0)).getJWTFromCookie(request);
+    verify(chain).doFilter(request, response);
+    verifyNoMoreInteractions(chain);
+  }
+
+  @Test
+  public void doFilterShouldContinueOnParseException() throws Exception {
+    KnoxSSOAuthenticationFilter knoxSSOAuthenticationFilter = spy(new KnoxSSOAuthenticationFilter("userSearchBase",
+            mock(Path.class),
+            "knoxKeyString",
+            "knoxCookie",
+            mock(LdapTemplate.class)
+    ));
+    HttpServletRequest request = mock(HttpServletRequest.class);
+    ServletResponse response = mock(ServletResponse.class);
+    FilterChain chain = mock(FilterChain.class);
+    SignedJWT signedJWT = mock(SignedJWT.class);
+    mockStatic(SignedJWT.class);
+
+    when(request.getHeader("Authorization")).thenReturn(null);
+    doReturn("serializedJWT").when(knoxSSOAuthenticationFilter).getJWTFromCookie(request);
+    when(SignedJWT.parse("serializedJWT")).thenThrow(new ParseException("parse exception", 0));
+
+    knoxSSOAuthenticationFilter.doFilter(request, response, chain);
+
+    verify(knoxSSOAuthenticationFilter, times(0)).getAuthentication("userName", request);
+    verify(chain).doFilter(request, response);
+    verifyNoMoreInteractions(chain);
+  }
+
+  @Test
+  public void doFilterShouldContinueOnInvalidToken() throws Exception {
+    KnoxSSOAuthenticationFilter knoxSSOAuthenticationFilter = spy(new KnoxSSOAuthenticationFilter("userSearchBase",
+            mock(Path.class),
+            "knoxKeyString",
+            "knoxCookie",
+            mock(LdapTemplate.class)
+    ));
+    HttpServletRequest request = mock(HttpServletRequest.class);
+    ServletResponse response = mock(ServletResponse.class);
+    FilterChain chain = mock(FilterChain.class);
+    SignedJWT signedJWT = mock(SignedJWT.class);
+    mockStatic(SignedJWT.class);
+    JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().subject("userName").build();
+
+    when(request.getHeader("Authorization")).thenReturn(null);
+    doReturn("serializedJWT").when(knoxSSOAuthenticationFilter).getJWTFromCookie(request);
+    when(SignedJWT.parse("serializedJWT")).thenReturn(signedJWT);
+    when(signedJWT.getJWTClaimsSet()).thenReturn(jwtClaimsSet);
+    doReturn(false).when(knoxSSOAuthenticationFilter).isValid(signedJWT, "userName");
+
+    knoxSSOAuthenticationFilter.doFilter(request, response, chain);
+
+    verify(knoxSSOAuthenticationFilter, times(0)).getAuthentication("userName", request);
+    verify(chain).doFilter(request, response);
+    verifyNoMoreInteractions(chain);
+  }
+
+  @Test
+  public void isValidShouldProperlyValidateToken() throws Exception {
+    KnoxSSOAuthenticationFilter knoxSSOAuthenticationFilter = spy(new KnoxSSOAuthenticationFilter("userSearchBase",
+            mock(Path.class),
+            "knoxKeyString",
+            "knoxCookie",
+            mock(LdapTemplate.class)
+    ));
+
+    SignedJWT jwtToken = mock(SignedJWT.class);
+
+    {
+      // Should be invalid on emtpy user name
+      assertFalse(knoxSSOAuthenticationFilter.isValid(jwtToken, null));
+    }
+
+    {
+      // Should be invalid on expired token
+      Date expiredDate = new Date(System.currentTimeMillis() - 10000);
+      JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().expirationTime(expiredDate).build();
+      when(jwtToken.getJWTClaimsSet()).thenReturn(jwtClaimsSet);
+
+      assertFalse(knoxSSOAuthenticationFilter.isValid(jwtToken, "userName"));
+    }
+
+    {
+      // Should be invalid when date is before notBeforeTime
+      Date notBeforeDate = new Date(System.currentTimeMillis() + 10000);
+      JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().notBeforeTime(notBeforeDate).build();
+      when(jwtToken.getJWTClaimsSet()).thenReturn(jwtClaimsSet);
+
+      assertFalse(knoxSSOAuthenticationFilter.isValid(jwtToken, "userName"));
+    }
+
+    {
+      // Should be valid if user name is present and token is within time constraints
+      Date expiredDate = new Date(System.currentTimeMillis() + 10000);
+      Date notBeforeDate = new Date(System.currentTimeMillis() - 10000);
+      JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().expirationTime(expiredDate).notBeforeTime(notBeforeDate).build();
+      when(jwtToken.getJWTClaimsSet()).thenReturn(jwtClaimsSet);
+      doReturn(true).when(knoxSSOAuthenticationFilter).validateSignature(jwtToken);
+
+      assertTrue(knoxSSOAuthenticationFilter.isValid(jwtToken, "userName"));
+    }
+
+  }
+
+  @Test
+  public void validateSignatureShouldProperlyValidateToken() throws Exception {
+    KnoxSSOAuthenticationFilter knoxSSOAuthenticationFilter = spy(new KnoxSSOAuthenticationFilter("userSearchBase",
+            mock(Path.class),
+            "knoxKeyString",
+            "knoxCookie",
+            mock(LdapTemplate.class)
+    ));
+
+    SignedJWT jwtToken = mock(SignedJWT.class);
+
+    {
+      // Should be invalid if algorithm is not ES256
+      JWSHeader jwsHeader = new JWSHeader(JWSAlgorithm.ES384);
+      when(jwtToken.getHeader()).thenReturn(jwsHeader);
+
+      assertFalse(knoxSSOAuthenticationFilter.validateSignature(jwtToken));
+    }
+    {
+      // Should be invalid if state is not SIGNED
+      JWSHeader jwsHeader = new JWSHeader(JWSAlgorithm.RS256);
+      when(jwtToken.getHeader()).thenReturn(jwsHeader);
+      when(jwtToken.getState()).thenReturn(JWSObject.State.UNSIGNED);
+
+      assertFalse(knoxSSOAuthenticationFilter.validateSignature(jwtToken));
+    }
+    {
+      // Should be invalid if signature is null
+      JWSHeader jwsHeader = new JWSHeader(JWSAlgorithm.RS256);
+      when(jwtToken.getHeader()).thenReturn(jwsHeader);
+      when(jwtToken.getState()).thenReturn(JWSObject.State.SIGNED);
+
+      assertFalse(knoxSSOAuthenticationFilter.validateSignature(jwtToken));
+    }
+    {
+      Base64URL signature = mock(Base64URL.class);
+      when(jwtToken.getSignature()).thenReturn(signature);
+      RSAPublicKey rsaPublicKey = mock(RSAPublicKey.class);
+      RSASSAVerifier rsaSSAVerifier = mock(RSASSAVerifier.class);
+      mockStatic(SecurityUtils.class);
+      when(SecurityUtils.parseRSAPublicKey("knoxKeyString")).thenReturn(rsaPublicKey);
+      whenNew(RSASSAVerifier.class).withArguments(rsaPublicKey).thenReturn(rsaSSAVerifier);
+      {
+        // Should be invalid if token verify throws an exception
+        when(jwtToken.verify(rsaSSAVerifier)).thenThrow(new JOSEException("verify exception"));
+
+        assertFalse(knoxSSOAuthenticationFilter.validateSignature(jwtToken));
+      }
+      {
+        // Should be invalid if RSA verification fails
+        doReturn(false).when(jwtToken).verify(rsaSSAVerifier);
+
+        assertFalse(knoxSSOAuthenticationFilter.validateSignature(jwtToken));
+      }
+      {
+        // Should be valid if RSA verification succeeds
+        doReturn(true).when(jwtToken).verify(rsaSSAVerifier);
+
+        assertTrue(knoxSSOAuthenticationFilter.validateSignature(jwtToken));
+      }
+    }
+  }
+
+  @Test
+  public void getJWTFromCookieShouldProperlyReturnToken() {
+    KnoxSSOAuthenticationFilter knoxSSOAuthenticationFilter = spy(new KnoxSSOAuthenticationFilter("userSearchBase",
+            mock(Path.class),
+            "knoxKeyString",
+            "knoxCookie",
+            mock(LdapTemplate.class)
+    ));
+
+    HttpServletRequest request = mock(HttpServletRequest.class);
+
+    {
+      // Should be null if cookies are empty
+      assertNull(knoxSSOAuthenticationFilter.getJWTFromCookie(request));
+    }
+    {
+      // Should be null if Knox cookie is missing
+      Cookie cookie = new Cookie("someCookie", "someValue");
+      when(request.getCookies()).thenReturn(new Cookie[]{cookie});
+
+      assertNull(knoxSSOAuthenticationFilter.getJWTFromCookie(request));
+    }
+    {
+      // Should return token from knoxCookie
+      Cookie cookie = new Cookie("knoxCookie", "token");
+      when(request.getCookies()).thenReturn(new Cookie[]{cookie});
+
+      assertEquals("token", knoxSSOAuthenticationFilter.getJWTFromCookie(request));
+    }
+
+  }
+
+  @Test
+  public void getKnoxKeyShouldProperlyReturnKnoxKey() throws Exception {
+    {
+      KnoxSSOAuthenticationFilter knoxSSOAuthenticationFilter = spy(new KnoxSSOAuthenticationFilter("userSearchBase",
+              mock(Path.class),
+              "knoxKeyString",
+              "knoxCookie",
+              mock(LdapTemplate.class)
+      ));
+
+      assertEquals("knoxKeyString", knoxSSOAuthenticationFilter.getKnoxKey());
+    }
+    {
+
+      FileUtils.writeStringToFile(new File("./target/knoxKeyFile"), "knoxKeyFileKeyString", StandardCharsets.UTF_8);
+      KnoxSSOAuthenticationFilter knoxSSOAuthenticationFilter = spy(new KnoxSSOAuthenticationFilter("userSearchBase",
+              Paths.get("./target/knoxKeyFile"),
+              null,
+              "knoxCookie",
+              mock(LdapTemplate.class)
+      ));
+
+      assertEquals("knoxKeyFileKeyString", knoxSSOAuthenticationFilter.getKnoxKey());
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void getAuthenticationShouldProperlyPopulateAuthentication() throws Exception {
+    LdapTemplate ldapTemplate = mock(LdapTemplate.class);
+    KnoxSSOAuthenticationFilter knoxSSOAuthenticationFilter = spy(new KnoxSSOAuthenticationFilter("ou=people,dc=hadoop,dc=apache,dc=org",
+            mock(Path.class),
+            "knoxKeyString",
+            "knoxCookie",
+            ldapTemplate
+    ));
+
+    HttpServletRequest request = mock(HttpServletRequest.class);
+
+    when(ldapTemplate.search(any(LdapQuery.class), any(AttributesMapper.class))).thenReturn(Arrays.asList("USER", "ADMIN"));
+
+    Authentication authentication = knoxSSOAuthenticationFilter.getAuthentication("userName", request);
+    Object[] grantedAuthorities = authentication.getAuthorities().toArray();
+    assertEquals("ROLE_USER", grantedAuthorities[0].toString());
+    assertEquals("ROLE_ADMIN", grantedAuthorities[1].toString());
+    assertEquals("userName", authentication.getName());
+  }
+
+}


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services