You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pulsar.apache.org by si...@apache.org on 2020/02/10 17:35:04 UTC

[pulsar-manager] branch master updated: Support https forward (#258)

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

sijie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-manager.git


The following commit(s) were added to refs/heads/master by this push:
     new 73f185c  Support https forward (#258)
73f185c is described below

commit 73f185c7852d45cbfe6ed386972ffe9058ffffe5
Author: Guangning <gu...@apache.org>
AuthorDate: Tue Feb 11 01:34:56 2020 +0800

    Support https forward (#258)
    
    Fixed
    #14
    #224
    #253
    
    ### Motivation
    Support https forward.
    
    ### Modifications
    
    * Add https forward
    * Update readme
    * Add option for TLS configurations
    * Default enable the user management
---
 README.md                                          |  13 ++-
 front-end/src/router/index.js                      |   9 +-
 .../management/subscriptions/subscription.vue      |   9 ++
 src/README.md                                      |  40 ++++++++
 .../manager/controller/TopicsController.java       |  14 +++
 .../org/apache/pulsar/manager/utils/HttpUtil.java  | 101 +++++++++++++++++++--
 .../pulsar/manager/zuul/EnvironmentForward.java    |   1 -
 .../manager/zuul/HttpsClientConfiguration.java     |  76 ++++++++++++++++
 src/main/resources/application.properties          |  10 +-
 9 files changed, 255 insertions(+), 18 deletions(-)

diff --git a/README.md b/README.md
index 837abaf..f9dc381 100644
--- a/README.md
+++ b/README.md
@@ -140,12 +140,21 @@ The pulsar-manager can monitor topics and subscriptions.
 
 3. Access Pulsar manager website.
 
-    Use the account and the password to log in to the Pulsar manager website as below.  
-          
+If you are deploying Pulsar Manager 0.1.0 using the released container, you can log in the Pulsar Manager UI using the following credentials.
+
    * Account: `pulsar`  
    * Password: `pulsar`  
+   
+If you are deploying Pulsar Manager using the latest code, you can create a super-user using the following command.
+
+    ```$xslt
+    curl -H "Content-Type: application/json" -X PUT http://backend-service:7750/pulsar-manager/users/superuser -d '{"name": "admin", "password": "apachepulsar", "description": "test", "email": "username@test.org"}'
+    ```
+   `backend-service`: The IP address or domain name of the backend service.
+   
    * Pulsar manager website: http://localhost:9527/
 
+
 ### Default Test database HerdDB
 
 #### Introduction
diff --git a/front-end/src/router/index.js b/front-end/src/router/index.js
index e3f878c..64f6339 100644
--- a/front-end/src/router/index.js
+++ b/front-end/src/router/index.js
@@ -206,7 +206,8 @@ export const asyncRouterMap = [
           title: 'TenantInfo',
           noCache: true,
           roles: ['super', 'admin']
-        }
+        },
+        hidden: true
       },
       {
         path: 'tenants/tenantInfo/public?tab=namespaces',
@@ -333,7 +334,7 @@ export const asyncRouterMap = [
           noCache: true,
           roles: ['super']
         },
-        hidden: false
+        hidden: true
       },
       {
         path: '/roles',
@@ -344,7 +345,7 @@ export const asyncRouterMap = [
           noCache: true,
           roles: ['super', 'admin']
         },
-        hidden: false
+        hidden: true
       },
       {
         path: '/roleBinding',
@@ -355,7 +356,7 @@ export const asyncRouterMap = [
           noCache: true,
           roles: ['super', 'admin']
         },
-        hidden: false
+        hidden: true
       },
       {
         path: '/tokens',
diff --git a/front-end/src/views/management/subscriptions/subscription.vue b/front-end/src/views/management/subscriptions/subscription.vue
index afb4a32..2feab85 100644
--- a/front-end/src/views/management/subscriptions/subscription.vue
+++ b/front-end/src/views/management/subscriptions/subscription.vue
@@ -478,6 +478,15 @@ export default {
         this.postForm.subscription,
         this.form.peekNumMessages).then(response => {
         if (!response.data) return
+        if (response.data.hasOwnProperty('error')) {
+          this.$notify({
+            title: 'error',
+            message: response.data.error,
+            type: 'error',
+            duration: 3000
+          })
+          return
+        }
         if (!response.data.data) return
         if (response.data.data.hasOwnProperty('error')) {
           this.$notify({
diff --git a/src/README.md b/src/README.md
index 858f52d..a296c13 100644
--- a/src/README.md
+++ b/src/README.md
@@ -45,6 +45,46 @@ spring.datasource.password=postgres
 ./gradlew -x build -x test
 ```
 
+### Enable Https forward
+
+First, please get the client certificate file for connecting to your Pulsar cluster. Please check out this [document](http://pulsar.apache.org/docs/en/security-tls-transport/) on how to create the client certificate file for Pulsar clients. Assume your client certificate is stored in `certs/ca.cert.pem`.
+
+Secondly, following the instructions below to generate a keystore file to be used by Pulsar Manager. Please replace `[/path/to/client/certs]` with the path to your client certificate file.
+
+```$xslt
+keytool -import -trustcacerts -keystore keystore-file -alias test-keystore -file [/path/to/client/certs]
+```
+
+Thirdly, configure Pulsar Manager to enable TLS by adding the following configuration settings.
+
+```$xslt
+tls.enabled=false
+tls.keystore=keystore-file
+tls.keystore.password=keystore-file-password
+tls.hostname.verifier=false
+```
+
+| Name | Default |Description
+| ------- | ------- | ------- |
+| tls.enabled | false | If broker enable tls configuration, set to true, otherwise set to false |
+| tls.keystore | "" | Keystore file path, need to use tool keytool to convert pem file to b file |
+| tls.keystore.password | "" | Keystore file password |
+| tls.hostname.verifier | false | Turn on hostname check, default false to test |
+
+### Enable peek message
+
+If you want to enable feature `peek-message`, please set the following option to true in file `application.properties`.
+
+```$xslt
+pulsar.peek.message=true
+```
+
+If your Pulsar cluster enable TLS, you also need to add the path of the ca certificate for option `tls.pulsar.admin.ca-certs` in file `application.properties`.
+
+```$xslt
+tls.pulsar.admin.ca-certs=ca-client-path
+```
+
 ### Enable JWT Auth
 
 If you want to turn on JWT authentication, configure the following parameters:
diff --git a/src/main/java/org/apache/pulsar/manager/controller/TopicsController.java b/src/main/java/org/apache/pulsar/manager/controller/TopicsController.java
index 56531fb..fd1db13 100644
--- a/src/main/java/org/apache/pulsar/manager/controller/TopicsController.java
+++ b/src/main/java/org/apache/pulsar/manager/controller/TopicsController.java
@@ -66,6 +66,15 @@ public class TopicsController {
     @Value("${pulsar.peek.message}")
     private boolean peekMessage;
 
+    @Value("${tls.enabled}")
+    private boolean tlsEnabled;
+
+    @Value("${tls.hostname.verifier}")
+    private boolean tlsHostnameVerifier;
+
+    @Value("${tls.pulsar.admin.ca-certs}")
+    private String tlsPulsarAdminCaCerts;
+
     @Autowired
     public TopicsController(
             TopicsService topicsService,
@@ -156,6 +165,11 @@ public class TopicsController {
         // to do check permission for non super, waiting for https://github.com/apache/pulsar-manager/pull/238
         try {
             PulsarAdminBuilder pulsarAdminBuilder = PulsarAdmin.builder();
+            if (tlsEnabled) {
+                pulsarAdminBuilder
+                        .tlsTrustCertsFilePath(tlsPulsarAdminCaCerts)
+                        .enableTlsHostnameVerification(tlsHostnameVerifier);
+            }
             if (token != null && token.length() > 0) {
                 pulsarAdminBuilder.authentication(AuthenticationFactory.token(token));
             }
diff --git a/src/main/java/org/apache/pulsar/manager/utils/HttpUtil.java b/src/main/java/org/apache/pulsar/manager/utils/HttpUtil.java
index cf0ba45..1350ae8 100644
--- a/src/main/java/org/apache/pulsar/manager/utils/HttpUtil.java
+++ b/src/main/java/org/apache/pulsar/manager/utils/HttpUtil.java
@@ -20,25 +20,48 @@ import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
 import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.ssl.SSLContexts;
 import org.apache.http.util.EntityUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.stereotype.Component;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import java.io.File;
 import java.io.UnsupportedEncodingException;
 import java.util.Map;
 
 /**
  * http client class, to get info from pulsar broker
  */
+@Component
 public class HttpUtil {
 
+    private static boolean tlsEnabled;
+
+    private static String tlsKeystore;
+
+    private static String tlsKeystorePassword;
+
+    private static boolean tlsHostnameVerifier;
+
     private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);
 
-    private static final CloseableHttpClient httpClient;
+    private static CloseableHttpClient httpClient;
 
     private static int CONNECTION_TIMEOUT = 28 * 1000;
 
@@ -46,13 +69,67 @@ public class HttpUtil {
 
     public static PoolingHttpClientConnectionManager cm = null;
 
-    static {
-        cm = new PoolingHttpClientConnectionManager();
-        cm.setDefaultMaxPerRoute(10);
-        cm.setMaxTotal(100);
-        RequestConfig config = RequestConfig.custom().setConnectTimeout(CONNECTION_TIMEOUT)
-                .setSocketTimeout(SO_TIMEOUT).build();
-        httpClient = HttpClients.custom().setConnectionManager(cm).setDefaultRequestConfig(config).build();
+    @Value("${tls.enabled}")
+    public void setTlsEnabled(boolean tlsEnabled) {
+        HttpUtil.tlsEnabled = tlsEnabled;
+    }
+
+    @Value("${tls.keystore}")
+    public void setTlsKeystore(String brokerKeystore) {
+        HttpUtil.tlsKeystore = brokerKeystore;
+    }
+
+    @Value("${tls.keystore.password}")
+    public void setTlsKeystorePassword(String brokerKeystorePassword) {
+        HttpUtil.tlsKeystorePassword = brokerKeystorePassword;
+    }
+
+    @Value("${tls.hostname.verifier}")
+    public void setTlsHostnameVerifier(boolean tlsHostnameVerifier) {
+        HttpUtil.tlsHostnameVerifier = tlsHostnameVerifier;
+    }
+
+    public static void initHttpClient() {
+        try {
+            if (tlsEnabled) {
+                Resource resource = new FileSystemResource(tlsKeystore);
+                File trustStoreFile = resource.getFile();
+                HostnameVerifier hostnameVerifier = (s, sslSession) -> {
+                    // Custom logic to verify host name, tlsHostnameVerifier is false for test
+                    if (!tlsHostnameVerifier) {
+                        return true;
+                    } else {
+                        HostnameVerifier hv= HttpsURLConnection.getDefaultHostnameVerifier();
+                        return hv.verify(s, sslSession);
+                    }
+                };
+
+                SSLContext sslcontext = SSLContexts.custom()
+                        .loadTrustMaterial(
+                                trustStoreFile,
+                                tlsKeystorePassword.toCharArray(),
+                                new TrustSelfSignedStrategy())
+                        .build();
+
+                SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, hostnameVerifier);
+                cm = new PoolingHttpClientConnectionManager(RegistryBuilder.<ConnectionSocketFactory>create()
+                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                        .register("https", sslsf)
+                        .build());
+            } else {
+                cm = new PoolingHttpClientConnectionManager();
+            }
+
+            cm.setDefaultMaxPerRoute(10);
+            cm.setMaxTotal(100);
+            RequestConfig config = RequestConfig.custom().setConnectTimeout(CONNECTION_TIMEOUT)
+                    .setSocketTimeout(SO_TIMEOUT).build();
+            httpClient = HttpClients.custom()
+                    .setConnectionManager(cm).setDefaultRequestConfig(config).build();
+
+        } catch (Exception e) {
+            log.error("Failed init http client error message: {}, error stack trace: {}", e.getMessage(), e.getCause());
+        }
     }
 
     public static String doGet(String url, Map<String, String> header){
@@ -88,6 +165,9 @@ public class HttpUtil {
             for (Map.Entry<String, String> entry: header.entrySet()) {
                 request.setHeader(entry.getKey(), entry.getValue());
             }
+            if (httpClient == null ) {
+                initHttpClient();
+            }
             response = httpClient.execute(request);
 
             if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
@@ -98,7 +178,8 @@ public class HttpUtil {
                 request.abort();
             }
         } catch (Throwable cause) {
-            log.error("http request exception:{}", cause.getMessage());
+            log.error("http request exception message: {}, http request error stack: {}",
+                    cause.getMessage(), cause.getCause());
         } finally {
             try{
                 if (response != null) {
diff --git a/src/main/java/org/apache/pulsar/manager/zuul/EnvironmentForward.java b/src/main/java/org/apache/pulsar/manager/zuul/EnvironmentForward.java
index 0965f3d..d1fb323 100644
--- a/src/main/java/org/apache/pulsar/manager/zuul/EnvironmentForward.java
+++ b/src/main/java/org/apache/pulsar/manager/zuul/EnvironmentForward.java
@@ -123,7 +123,6 @@ public class EnvironmentForward extends ZuulFilter {
             return null;
         }
         String serviceUrl = environmentCacheService.getServiceUrl(request);
-        System.out.println(serviceUrl);
         return forwardRequest(ctx, request, serviceUrl);
     }
 
diff --git a/src/main/java/org/apache/pulsar/manager/zuul/HttpsClientConfiguration.java b/src/main/java/org/apache/pulsar/manager/zuul/HttpsClientConfiguration.java
new file mode 100644
index 0000000..0881764
--- /dev/null
+++ b/src/main/java/org/apache/pulsar/manager/zuul/HttpsClientConfiguration.java
@@ -0,0 +1,76 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pulsar.manager.zuul;
+
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContexts;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import java.io.File;
+
+@Configuration
+public class HttpsClientConfiguration {
+
+    @Value("${tls.enabled}")
+    private boolean tlsEnabled;
+
+    @Value("${tls.keystore}")
+    private String tlsKeystore;
+
+    @Value("${tls.keystore.password}")
+    private String tlsKeystorePassword;
+
+    @Value("${tls.hostname.verifier}")
+    private boolean tlsHostnameVerifier;
+
+    @Bean
+    public CloseableHttpClient httpClient() throws Exception {
+        if (tlsEnabled) {
+            Resource resource = new FileSystemResource(tlsKeystore);
+            File trustStoreFile = resource.getFile();
+            SSLContext sslcontext = SSLContexts.custom()
+                    .loadTrustMaterial(trustStoreFile, tlsKeystorePassword.toCharArray(),
+                            new TrustSelfSignedStrategy())
+                    .build();
+            HostnameVerifier hostnameVerifier = (s, sslSession) -> {
+                // Custom logic to verify host name, tlsHostnameVerifier is false for test
+                if (!tlsHostnameVerifier) {
+                    return true;
+                } else {
+                    HostnameVerifier hv= HttpsURLConnection.getDefaultHostnameVerifier();
+                    return hv.verify(s, sslSession);
+                }
+            };
+
+            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
+                    sslcontext,
+                    hostnameVerifier);
+
+            return HttpClients.custom()
+                    .setSSLSocketFactory(sslsf)
+                    .build();
+        }
+        return HttpClients.custom().build();
+    }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 777891e..1e6be65 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -80,7 +80,7 @@ jwt.sessionTime=2592000
 pulsar-manager.account=pulsar
 pulsar-manager.password=pulsar
 # If true, the database is used for user management
-user.management.enable=false
+user.management.enable=true
 
 # Optional -> SECRET, PRIVATE, default -> PRIVATE, empty -> disable auth
 # SECRET mode -> bin/pulsar tokens create --secret-key file:///path/to/my-secret.key --subject test-user
@@ -135,5 +135,13 @@ spring.thymeleaf.mode=HTML5
 default.environment.name=
 default.environment.service_url=
 
+# enable tls encryption
+# keytool -import -alias test-keystore -keystore ca-certs -file certs/ca.cert.pem
+tls.enabled=false
+tls.keystore=keystore-file-path
+tls.keystore.password=keystore-password
+tls.hostname.verifier=false
+tls.pulsar.admin.ca-certs=ca-client-path
+
 # support peek message, default false
 pulsar.peek.message=false