You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dlab.apache.org by bh...@apache.org on 2019/07/25 11:31:30 UTC

[incubator-dlab] 08/12: DLAB-2 added backend for web terminal

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

bhliva pushed a commit to branch v2.1.1
in repository https://gitbox.apache.org/repos/asf/incubator-dlab.git

commit 84ee64251853fe78e0f7339fcd661e8a09194d91
Author: bhliva <bo...@epam.com>
AuthorDate: Thu May 2 15:21:23 2019 +0300

    DLAB-2 added backend for web terminal
---
 services/self-service/pom.xml                      |  6 +++
 services/self-service/self-service.yml             | 22 +++++++--
 .../dlab/backendapi/SelfServiceApplication.java    | 13 +++++
 .../SelfServiceApplicationConfiguration.java       | 16 +++++++
 .../epam/dlab/backendapi/modules/DevModule.java    | 17 +++++--
 .../dlab/backendapi/modules/ProductionModule.java  |  1 +
 .../dlab/backendapi/service/GuacamoleService.java  | 10 ++++
 .../service/impl/GuacamoleServiceImpl.java         | 56 ++++++++++++++++++++++
 .../servlet/guacamole/GuacamoleSecurityFilter.java | 56 ++++++++++++++++++++++
 .../servlet/guacamole/GuacamoleServlet.java        | 32 +++++++++++++
 10 files changed, 219 insertions(+), 10 deletions(-)

diff --git a/services/self-service/pom.xml b/services/self-service/pom.xml
index e1190a6..5521958 100644
--- a/services/self-service/pom.xml
+++ b/services/self-service/pom.xml
@@ -161,6 +161,12 @@
             </exclusions>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.guacamole</groupId>
+            <artifactId>guacamole-common</artifactId>
+            <version>1.0.0</version>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/services/self-service/self-service.yml b/services/self-service/self-service.yml
index 8a2fc60..8ae3c02 100644
--- a/services/self-service/self-service.yml
+++ b/services/self-service/self-service.yml
@@ -67,6 +67,8 @@ maxSessionDurabilityMilliseconds: 288000000
 </#if>
 
 server:
+  gzip:
+    enabled: false
   requestLog:
     appenders:
     - type: file
@@ -76,8 +78,8 @@ server:
       archivedFileCount: 10
   rootPath: "/api"
   applicationConnectors:
-#    - type: http
-#      port: 8080
+  #    - type: http
+  #      port: 8080
   - type: https
     port: 8443
     certAlias: dlab
@@ -87,8 +89,8 @@ server:
     trustStorePath: ${TRUST_STORE_PATH}
     trustStorePassword: ${TRUST_STORE_PASSWORD}
   adminConnectors:
-#    - type: http
-#      port: 8081
+  #    - type: http
+  #      port: 8081
   - type: https
     port: 8444
     certAlias: dlab
@@ -105,6 +107,7 @@ logging:
   loggers:
     com.epam: TRACE
     com.novemberain: ERROR
+    org.apache.guacamole: TRACE
   appenders:
 <#if DEV_MODE == "true">
   - type: console
@@ -158,4 +161,13 @@ schedulers:
     cron: "0 0 * ? * * *"
   checkUserQuoteScheduler:
     enabled: true
-    cron: "0 0 * ? * * *"
\ No newline at end of file
+    cron: "0 0 * ? * * *"
+
+guacamole:
+  connectionProtocol: ssh
+  serverHost: localhost
+  serverPort: 4822
+  port: 22
+  username: dlab-user
+
+
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/SelfServiceApplication.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/SelfServiceApplication.java
index 7df4e47..1a8f59b 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/SelfServiceApplication.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/SelfServiceApplication.java
@@ -29,6 +29,8 @@ import com.epam.dlab.backendapi.modules.ModuleFactory;
 import com.epam.dlab.backendapi.resources.*;
 import com.epam.dlab.backendapi.resources.callback.*;
 import com.epam.dlab.backendapi.schedulers.internal.ManagedScheduler;
+import com.epam.dlab.backendapi.servlet.guacamole.GuacamoleSecurityFilter;
+import com.epam.dlab.backendapi.servlet.guacamole.GuacamoleServlet;
 import com.epam.dlab.cloud.CloudModule;
 import com.epam.dlab.constants.ServiceConsts;
 import com.epam.dlab.migration.mongo.DlabMongoMigration;
@@ -52,6 +54,9 @@ import io.federecio.dropwizard.swagger.SwaggerBundle;
 import io.federecio.dropwizard.swagger.SwaggerBundleConfiguration;
 import lombok.extern.slf4j.Slf4j;
 
+import javax.servlet.DispatcherType;
+import java.util.EnumSet;
+
 /**
  * Self Service based on Dropwizard application.
  */
@@ -113,6 +118,14 @@ public class SelfServiceApplication extends Application<SelfServiceApplicationCo
 		environment.healthChecks().register(
 				ServiceConsts.PROVISIONING_SERVICE_NAME, injector.getInstance(ProvisioningServiceHealthCheck.class));
 
+		final String guacamoleServletName = "GuacamoleServlet";
+		environment.servlets().addServlet(guacamoleServletName, injector.getInstance(GuacamoleServlet.class))
+				.addMapping("/api/tunnel");
+		environment.servlets().addFilter("GuacamoleSecurityFilter",
+				injector.getInstance(GuacamoleSecurityFilter.class))
+				.addMappingForServletNames(EnumSet.allOf(DispatcherType.class), true, guacamoleServletName);
+
+
 		JerseyEnvironment jersey = environment.jersey();
 		jersey.register(new RuntimeExceptionMapper());
 		jersey.register(new JsonProcessingExceptionMapper());
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/SelfServiceApplicationConfiguration.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/SelfServiceApplicationConfiguration.java
index 78a074b..6499b67 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/SelfServiceApplicationConfiguration.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/SelfServiceApplicationConfiguration.java
@@ -121,6 +121,22 @@ public class SelfServiceApplicationConfiguration extends ServiceConfiguration {
 	@JsonProperty(ServiceConsts.MAVEN_SEARCH_API)
 	private RESTServiceFactory mavenApiFactory;
 
+	@Valid
+	@NotNull
+	private Map<String, String> guacamole;
+
+	public Map<String, String> getGuacamole() {
+		return guacamole;
+	}
+
+	public String getGuacamoleHost() {
+		return guacamole.get("serverHost");
+	}
+
+	public Integer getGuacamolePort() {
+		return Integer.valueOf(guacamole.get("serverPort"));
+	}
+
 	@JsonProperty("jerseyClient")
 	public JerseyClientConfiguration getJerseyClientConfiguration() {
 		return jerseyClient;
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/DevModule.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/DevModule.java
index 27c38e4..bd4a5ab 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/DevModule.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/DevModule.java
@@ -109,6 +109,7 @@ public class DevModule extends ModuleBase<SelfServiceApplicationConfiguration> i
 		bind(UserGroupDao.class).to(UserGroupDaoImpl.class);
 		bind(ApplicationSettingService.class).to(ApplicationSettingServiceImpl.class);
 		bind(UserSettingService.class).to(UserSettingServiceImpl.class);
+		bind(GuacamoleService.class).to(GuacamoleServiceImpl.class);
 	}
 
 	/**
@@ -133,6 +134,8 @@ public class DevModule extends ModuleBase<SelfServiceApplicationConfiguration> i
 					return authorize((UserCredentialDTO) parameter);
 				} else if (GET_USER_INFO.equals(path) && TOKEN.equals(parameter) && clazz.equals(UserInfo.class)) {
 					return (T) getUserInfo();
+				} else if (GET_USER_INFO.equals(path) && !TOKEN.equals(parameter) && clazz.equals(UserInfo.class)) {
+					return null;
 				} else if (LOGOUT.equals(path)) {
 					return (T) Response.ok().build();
 				}
@@ -144,14 +147,18 @@ public class DevModule extends ModuleBase<SelfServiceApplicationConfiguration> i
 				if (LOGIN_NAME.equals(credential.getUsername())) {
 					return (T) Response.ok(TOKEN).build();
 				} else {
-					return (T) Response.status(Response.Status.UNAUTHORIZED)
-							.entity(new ErrorDTO(Response.Status.UNAUTHORIZED.getStatusCode(), "Username or password" +
-									" is invalid"))
-							.type(MediaType.APPLICATION_JSON_TYPE)
-							.build();
+					return (T) unauthorized();
 				}
 			}
 
+			private Response unauthorized() {
+				return Response.status(Response.Status.UNAUTHORIZED)
+						.entity(new ErrorDTO(Response.Status.UNAUTHORIZED.getStatusCode(), "Username or password" +
+								" is invalid"))
+						.type(MediaType.APPLICATION_JSON_TYPE)
+						.build();
+			}
+
 			@Override
 			public <T> T get(String path, Class<T> clazz) {
 				throw new UnsupportedOperationException(OPERATION_IS_NOT_SUPPORTED);
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/ProductionModule.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/ProductionModule.java
index e3ed4dc..7db5b26 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/ProductionModule.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/modules/ProductionModule.java
@@ -86,5 +86,6 @@ public class ProductionModule extends ModuleBase<SelfServiceApplicationConfigura
 		bind(InactivityService.class).to(InactivityServiceImpl.class);
 		bind(ApplicationSettingService.class).to(ApplicationSettingServiceImpl.class);
 		bind(UserSettingService.class).to(UserSettingServiceImpl.class);
+		bind(GuacamoleService.class).to(GuacamoleServiceImpl.class);
 	}
 }
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/GuacamoleService.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/GuacamoleService.java
new file mode 100644
index 0000000..760e701
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/GuacamoleService.java
@@ -0,0 +1,10 @@
+package com.epam.dlab.backendapi.service;
+
+import com.epam.dlab.auth.UserInfo;
+import org.apache.guacamole.net.GuacamoleTunnel;
+
+public interface GuacamoleService {
+
+	GuacamoleTunnel getTunnel(UserInfo userInfo, String host);
+
+}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/GuacamoleServiceImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/GuacamoleServiceImpl.java
new file mode 100644
index 0000000..7292646
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/GuacamoleServiceImpl.java
@@ -0,0 +1,56 @@
+package com.epam.dlab.backendapi.service.impl;
+
+import com.epam.dlab.auth.UserInfo;
+import com.epam.dlab.backendapi.SelfServiceApplicationConfiguration;
+import com.epam.dlab.backendapi.service.GuacamoleService;
+import com.epam.dlab.exceptions.DlabException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.net.InetGuacamoleSocket;
+import org.apache.guacamole.net.SimpleGuacamoleTunnel;
+import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket;
+import org.apache.guacamole.protocol.GuacamoleConfiguration;
+
+import java.util.Map;
+
+@Slf4j
+@Singleton
+public class GuacamoleServiceImpl implements GuacamoleService {
+
+	private static final String PRIVATE_KEY_PARAM_NAME = "private-key";
+	private static final String HOSTNAME_PARAM = "hostname";
+	private static final String CONNECTION_PROTOCOL_PARAM = "connectionProtocol";
+	private final SelfServiceApplicationConfiguration conf;
+
+	@Inject
+	public GuacamoleServiceImpl(SelfServiceApplicationConfiguration conf) {
+		this.conf = conf;
+	}
+
+	@Override
+	public GuacamoleTunnel getTunnel(UserInfo userInfo, String host) {
+		try {
+			final String privateKeyContent = "";// TODO figure out from which place private key should be taken
+			final InetGuacamoleSocket socket = new InetGuacamoleSocket(conf.getGuacamoleHost(),
+					conf.getGuacamolePort());
+			final GuacamoleConfiguration guacamoleConfig = getGuacamoleConfig(privateKeyContent, conf.getGuacamole(),
+					host);
+			return new SimpleGuacamoleTunnel(new ConfiguredGuacamoleSocket(socket, guacamoleConfig));
+		} catch (Exception e) {
+			log.error("Can not create guacamole tunnel due to: " + e.getMessage());
+			throw new DlabException("Can not create guacamole tunnel due to: " + e.getMessage(), e);
+		}
+	}
+
+	private GuacamoleConfiguration getGuacamoleConfig(String privateKeyContent, Map<String, String> guacamoleParams,
+													  String host) {
+		GuacamoleConfiguration guacamoleConfiguration = new GuacamoleConfiguration();
+		guacamoleConfiguration.setProtocol(guacamoleParams.get(CONNECTION_PROTOCOL_PARAM));
+		guacamoleConfiguration.setParameters(guacamoleParams);
+		guacamoleConfiguration.setParameter(HOSTNAME_PARAM, host);
+		guacamoleConfiguration.setParameter(PRIVATE_KEY_PARAM_NAME, privateKeyContent);
+		return guacamoleConfiguration;
+	}
+}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/servlet/guacamole/GuacamoleSecurityFilter.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/servlet/guacamole/GuacamoleSecurityFilter.java
new file mode 100644
index 0000000..4545ed7
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/servlet/guacamole/GuacamoleSecurityFilter.java
@@ -0,0 +1,56 @@
+package com.epam.dlab.backendapi.servlet.guacamole;
+
+import com.epam.dlab.auth.UserInfo;
+import com.epam.dlab.backendapi.auth.SelfServiceSecurityAuthenticator;
+import com.google.inject.Inject;
+import io.dropwizard.auth.AuthenticationException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.HttpHeaders;
+import java.io.IOException;
+import java.util.Optional;
+
+@Slf4j
+public class GuacamoleSecurityFilter implements Filter {
+	private static final String AUTH_HEADER_PREFIX = "Bearer ";
+	private final SelfServiceSecurityAuthenticator authenticator;
+
+	@Inject
+	public GuacamoleSecurityFilter(SelfServiceSecurityAuthenticator authenticator) {
+		this.authenticator = authenticator;
+	}
+
+	@Override
+	public void init(FilterConfig filterConfig) {
+
+	}
+
+	@Override
+	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+		HttpServletRequest request = (HttpServletRequest) servletRequest;
+		HttpServletResponse response = (HttpServletResponse) servletResponse;
+		final String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
+		try {
+			final String credentials = StringUtils.substringAfter(authorization, AUTH_HEADER_PREFIX);
+			final Optional<UserInfo> user = authenticator.authenticate(credentials);
+			if (user.isPresent()) {
+				request.setAttribute(GuacamoleServlet.USER_ATTRIBUTE, user.get());
+				filterChain.doFilter(servletRequest, servletResponse);
+			} else {
+				response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+			}
+		} catch (AuthenticationException e) {
+			log.error("Authentication error occurred: {}", e.getMessage());
+		}
+
+	}
+
+	@Override
+	public void destroy() {
+
+	}
+}
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/servlet/guacamole/GuacamoleServlet.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/servlet/guacamole/GuacamoleServlet.java
new file mode 100644
index 0000000..d0025ba
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/servlet/guacamole/GuacamoleServlet.java
@@ -0,0 +1,32 @@
+package com.epam.dlab.backendapi.servlet.guacamole;
+
+import com.epam.dlab.auth.UserInfo;
+import com.epam.dlab.backendapi.service.GuacamoleService;
+import com.epam.dlab.exceptions.DlabException;
+import com.google.inject.Inject;
+import org.apache.guacamole.net.GuacamoleTunnel;
+import org.apache.guacamole.servlet.GuacamoleHTTPTunnelServlet;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+public class GuacamoleServlet extends GuacamoleHTTPTunnelServlet {
+	static final String USER_ATTRIBUTE = "user";
+	private final GuacamoleService guacamoleService;
+
+	@Inject
+	public GuacamoleServlet(GuacamoleService guacamoleService) {
+		this.guacamoleService = guacamoleService;
+	}
+
+	@Override
+	protected GuacamoleTunnel doConnect(HttpServletRequest request) {
+		try {
+			final UserInfo userInfo = (UserInfo) request.getAttribute(USER_ATTRIBUTE);
+			final String host = request.getReader().readLine();
+			return guacamoleService.getTunnel(userInfo, host);
+		} catch (IOException e) {
+			throw new DlabException("Can not read request body: " + e.getMessage(), e);
+		}
+	}
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@dlab.apache.org
For additional commands, e-mail: commits-help@dlab.apache.org