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

cbickel pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git


The following commit(s) were added to refs/heads/master by this push:
     new a8476ab  Enable ssl on the path between edge and controllers. (#3077)
a8476ab is described below

commit a8476ab970b4f8804d0d26fa319fe4aaa7c9ab04
Author: Vadim Raskin <raskinva...@gmail.com>
AuthorDate: Mon Mar 12 10:15:39 2018 +0100

    Enable ssl on the path between edge and controllers. (#3077)
---
 .gitignore                                         |  4 ++
 ansible/group_vars/all                             | 19 ++++++
 ansible/roles/controller/tasks/deploy.yml          | 34 +++++++++-
 ansible/roles/nginx/tasks/deploy.yml               |  9 +++
 ansible/roles/nginx/templates/nginx.conf.j2        | 12 +++-
 ansible/setup.yml                                  | 12 ++++
 ansible/templates/whisk.properties.j2              |  1 +
 .../scala/src/main/scala/whisk/common/Https.scala  | 75 ++++++++++++++++++++++
 .../main/scala/whisk/http/BasicHttpService.scala   | 35 ++++++----
 .../controller/src/main/resources/application.conf |  3 +
 .../scala/whisk/core/controller/Controller.scala   |  8 ++-
 .../scala/whisk/core/controller/Triggers.scala     | 32 ++++++++-
 .../main/scala/whisk/core/invoker/Invoker.scala    |  4 +-
 tests/src/test/resources/application.conf.j2       | 13 ++++
 tests/src/test/scala/common/rest/WskRest.scala     | 62 ++++++++++++++++--
 .../src/test/scala/ha/CacheInvalidationTests.scala | 34 ++++++----
 tests/src/test/scala/ha/ShootComponentsTests.scala | 13 +++-
 tests/src/test/scala/services/HeadersTests.scala   | 14 ++--
 tools/travis/setup.sh                              |  2 +-
 19 files changed, 338 insertions(+), 48 deletions(-)

diff --git a/.gitignore b/.gitignore
index 710e6f5..6a97330 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,9 +60,13 @@ ansible/db_local.ini*
 ansible/tmp/*
 ansible/roles/nginx/files/openwhisk-client*
 ansible/roles/nginx/files/*.csr
+ansible/roles/nginx/files/*.p12
 ansible/roles/nginx/files/*cert.pem
 ansible/roles/nginx/files/*p12
 ansible/roles/kafka/files/*
+ansible/roles/controller/files/*.pem
+ansible/roles/controller/files/*.key
+ansible/roles/controller/files/*.p12
 
 # .zip files must be explicited whitelisted
 *.zip
diff --git a/ansible/group_vars/all b/ansible/group_vars/all
index 3105c5b..f0dca5f 100644
--- a/ansible/group_vars/all
+++ b/ansible/group_vars/all
@@ -66,6 +66,25 @@ controller:
   loadbalancer:
     spi: "{{ controller_loadbalancer_spi | default('') }}"
   loglevel: "{{ controller_loglevel | default(whisk_loglevel) | 
default('INFO') }}"
+  protocol: "{{ controllerProtocolForSetup }}"
+  ssl:
+    cn: openwhisk-controllers
+    cert: "{{ controller_ca_cert | 
default('controller-openwhisk-server-cert.pem') }}"
+    key: "{{ controller_key | default('controller-openwhisk-server-key.pem') 
}}"
+    clientAuth: "{{ controller_client_auth | default('true') }}"
+    storeFlavor: PKCS12
+    keystore:
+      password: "{{ controllerKeystorePassword }}"
+      path: "/conf/{{ controllerKeystoreName }}"
+# keystore and truststore are the same as long as controller and nginx share 
the certificate
+    truststore:
+      password: "{{ controllerKeystorePassword }}"
+      path: "/conf/{{ controllerKeystoreName }}"
+# move controller protocol outside to not evaluate controller variables during 
execution of setup.yml
+controllerProtocolForSetup: "{{ controller_protocol | default('https') }}"
+controllerKeystoreName: "{{ controllerKeyPrefix }}openwhisk-keystore.p12"
+controllerKeyPrefix: "controller-"
+controllerKeystorePassword: openwhisk
 
 jmx:
   basePortController: 15000
diff --git a/ansible/roles/controller/tasks/deploy.yml 
b/ansible/roles/controller/tasks/deploy.yml
index 9dc2de7..68d7efa 100644
--- a/ansible/roles/controller/tasks/deploy.yml
+++ b/ansible/roles/controller/tasks/deploy.yml
@@ -51,6 +51,25 @@
     src: "{{ openwhisk_home }}/ansible/roles/kafka/files/{{ 
kafka.ssl.keystore.name }}"
     dest: "{{ controller.confdir }}/controller{{ 
groups['controllers'].index(inventory_hostname) }}"
 
+- name: copy nginx certificate keystore
+  when: controller.protocol == 'https'
+  copy:
+    src: files/{{ controllerKeystoreName }}
+    mode: 0666
+    dest: "{{ controller.confdir }}/controller{{ 
groups['controllers'].index(inventory_hostname) }}"
+  become: "{{ controller.dir.become }}"
+
+- name: copy certificates
+  when: controller.protocol == 'https'
+  copy:
+    src: "{{ openwhisk_home }}/ansible/roles/controller/files/{{ item }}"
+    mode: 0666
+    dest: "{{ controller.confdir }}/controller{{ 
groups['controllers'].index(inventory_hostname) }}"
+  with_items:
+  - "{{ controller.ssl.cert }}"
+  - "{{ controller.ssl.key }}"
+  become: "{{ controller.dir.become }}"
+
 - name: check, that required databases exist
   include: "{{ openwhisk_home }}/ansible/tasks/db/checkDb.yml"
   vars:
@@ -154,11 +173,17 @@
 
       "CONTROLLER_LOCALBOOKKEEPING": "{{ controller.localBookkeeping }}"
       "AKKA_CLUSTER_SEED_NODES": "{{seed_nodes_list | join(' ') }}"
-
       "METRICS_KAMON": "{{ metrics.kamon.enabled }}"
       "METRICS_KAMON_TAGS": "{{ metrics.kamon.tags }}"
       "METRICS_LOG": "{{ metrics.log.enabled }}"
-
+      "CONFIG_whisk_controller_protocol": "{{ controller.protocol }}"
+      "CONFIG_whisk_controller_https_keystorePath": "{{ 
controller.ssl.keystore.path }}"
+      "CONFIG_whisk_controller_https_keystorePassword": "{{ 
controller.ssl.keystore.password }}"
+      "CONFIG_whisk_controller_https_keystoreFlavor": "{{ 
controller.ssl.storeFlavor }}"
+      "CONFIG_whisk_controller_https_truststorePath": "{{ 
controller.ssl.truststore.path }}"
+      "CONFIG_whisk_controller_https_truststorePassword": "{{ 
controller.ssl.truststore.password }}"
+      "CONFIG_whisk_controller_https_truststoreFlavor": "{{ 
controller.ssl.storeFlavor }}"
+      "CONFIG_whisk_controller_https_clientAuth": "{{ 
controller.ssl.clientAuth }}"
       "CONFIG_whisk_loadbalancer_invokerBusyThreshold": "{{ 
invoker.busyThreshold }}"
       "CONFIG_whisk_loadbalancer_blackboxFraction": "{{ 
controller.blackboxFraction }}"
 
@@ -183,7 +208,10 @@
 
 - name: wait until the Controller in this host is up and running
   uri:
-    url: "http://{{ ansible_host }}:{{ controller.basePort + (controller_index 
| int) }}/ping"
+    url: "{{ controller.protocol }}://{{ ansible_host }}:{{ 
controller.basePort + groups['controllers'].index(inventory_hostname) }}/ping"
+    validate_certs: no
+    client_key: "{{ controller.confdir }}/controller{{ 
groups['controllers'].index(inventory_hostname) }}/{{ controller.ssl.key }}"
+    client_cert: "{{ controller.confdir }}/controller{{ 
groups['controllers'].index(inventory_hostname) }}/{{ controller.ssl.cert }}"
   register: result
   until: result.status == 200
   retries: 12
diff --git a/ansible/roles/nginx/tasks/deploy.yml 
b/ansible/roles/nginx/tasks/deploy.yml
index 0a6531f..d343e2f 100644
--- a/ansible/roles/nginx/tasks/deploy.yml
+++ b/ansible/roles/nginx/tasks/deploy.yml
@@ -27,6 +27,15 @@
     dest: "{{ nginx.confdir }}"
   when: nginx.ssl.password_enabled == true
 
+- name: copy controller cert for authentication
+  copy:
+    src: "{{ openwhisk_home }}/ansible/roles/controller/files/{{ item }}"
+    dest: "{{ nginx.confdir }}"
+  with_items:
+        - "{{ controller.ssl.cert }}"
+        - "{{ controller.ssl.key }}"
+  when: "{{ controller.protocol == 'https' }}"
+
 - name: ensure nginx log directory is created with permissions
   file:
     path: "{{ whisk_logs_dir }}/nginx"
diff --git a/ansible/roles/nginx/templates/nginx.conf.j2 
b/ansible/roles/nginx/templates/nginx.conf.j2
index c31026c..427b48e 100644
--- a/ansible/roles/nginx/templates/nginx.conf.j2
+++ b/ansible/roles/nginx/templates/nginx.conf.j2
@@ -23,6 +23,14 @@ http {
     proxy_http_version 1.1;
     proxy_set_header Connection "";
 
+{% if controller.protocol == 'https' %}
+    proxy_ssl_session_reuse on;
+    proxy_ssl_name {{ controller.ssl.cn }};
+    proxy_ssl_protocols TLSv1.1 TLSv1.2;
+    proxy_ssl_certificate /etc/nginx/{{ controller.ssl.cert }};
+    proxy_ssl_certificate_key /etc/nginx/{{ controller.ssl.key }};
+{% endif %}
+
     upstream controllers {
         # fail_timeout: period of time the server will be considered 
unavailable
         # Mark the controller as unavailable for at least 60 seconds, to not 
get any requests during restart.
@@ -80,7 +88,7 @@ http {
             if ($namespace) {
               rewrite    /(.*) /api/v1/web/${namespace}/$1 break;
             }
-            proxy_pass http://controllers;
+            proxy_pass {{ controller.protocol }}://controllers;
             proxy_read_timeout 75s; # 70+5 additional seconds to allow 
controller to terminate request
         }
 
@@ -89,7 +97,7 @@ http {
             if ($namespace) {
               rewrite    ^ /api/v1/web/${namespace}/public/index.html break;
             }
-            proxy_pass http://controllers;
+            proxy_pass {{ controller.protocol }}://controllers;
             proxy_read_timeout 75s; # 70+5 additional seconds to allow 
controller to terminate request
         }
 
diff --git a/ansible/setup.yml b/ansible/setup.yml
index 6a20d99..c9769d0 100644
--- a/ansible/setup.yml
+++ b/ansible/setup.yml
@@ -48,3 +48,15 @@
   - name: generate kafka certificates
     local_action: shell "{{ playbook_dir }}/files/genssl.sh" "openwhisk-kafka" 
"server_with_JKS_keystore" "{{ playbook_dir }}/roles/kafka/files" openwhisk 
"generateKey" "kafka-"
     when: kafka_protocol_for_setup == 'SSL'
+
+  - name: ensure controller files directory exists
+    file:
+      path: "{{ playbook_dir }}/roles/controller/files/"
+      state: directory
+      mode: 0777
+    become: "{{ logs.dir.become }}"
+    when: controllerProtocolForSetup == 'https'
+
+  - name: generate controller certificates
+    when: controllerProtocolForSetup == 'https'
+    local_action: shell "{{ playbook_dir }}/files/genssl.sh" 
"openwhisk-controllers" "server" "{{ playbook_dir }}/roles/controller/files" {{ 
controllerKeystorePassword }} "generateKey" {{ controllerKeyPrefix }}
diff --git a/ansible/templates/whisk.properties.j2 
b/ansible/templates/whisk.properties.j2
index f8d6a1b..bc7ff36 100644
--- a/ansible/templates/whisk.properties.j2
+++ b/ansible/templates/whisk.properties.j2
@@ -56,6 +56,7 @@ invoker.hosts.basePort={{ invoker.port }}
 controller.hosts={{ groups["controllers"] | map('extract', hostvars, 
'ansible_host') | list | join(",") }}
 controller.host.basePort={{ controller.basePort }}
 controller.instances={{ controller.instances }}
+controller.protocol={{ controller.protocol }}
 
 invoker.container.network=bridge
 invoker.container.policy={{ invoker_container_policy_name | default()}}
diff --git a/common/scala/src/main/scala/whisk/common/Https.scala 
b/common/scala/src/main/scala/whisk/common/Https.scala
new file mode 100644
index 0000000..2751498
--- /dev/null
+++ b/common/scala/src/main/scala/whisk/common/Https.scala
@@ -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.
+ */
+
+package whisk.common
+
+import java.io.{FileInputStream, InputStream}
+import java.security.{KeyStore, SecureRandom}
+import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}
+
+import akka.http.scaladsl.ConnectionContext
+import akka.stream.TLSClientAuth
+import com.typesafe.sslconfig.akka.AkkaSSLConfig
+import whisk.core.WhiskConfig
+import pureconfig._
+
+object Https {
+  case class HttpsConfig(keystorePassword: String,
+                         keystoreFlavor: String,
+                         keystorePath: String,
+                         truststorePath: String,
+                         truststorePassword: String,
+                         truststoreFlavor: String,
+                         clientAuth: String)
+  private val httpsConfig = 
loadConfigOrThrow[HttpsConfig]("whisk.controller.https")
+
+  def getCertStore(password: Array[Char], flavor: String, path: String): 
KeyStore = {
+    val certStorePassword: Array[Char] = password
+    val cs: KeyStore = KeyStore.getInstance(flavor)
+    val certStore: InputStream = new FileInputStream(path)
+    cs.load(certStore, certStorePassword)
+    cs
+  }
+
+  def connectionContext(config: WhiskConfig, sslConfig: Option[AkkaSSLConfig] 
= None) = {
+
+    val keyFactoryType = "SunX509"
+    val clientAuth = {
+      if (httpsConfig.clientAuth.toBoolean)
+        Some(TLSClientAuth.need)
+      else
+        Some(TLSClientAuth.none)
+    }
+
+// configure keystore
+    val keystorePassword = httpsConfig.keystorePassword.toCharArray
+    val ks: KeyStore = getCertStore(keystorePassword, 
httpsConfig.keystoreFlavor, httpsConfig.keystorePath)
+    val keyManagerFactory: KeyManagerFactory = 
KeyManagerFactory.getInstance(keyFactoryType)
+    keyManagerFactory.init(ks, keystorePassword)
+
+// configure truststore
+    val truststorePassword = httpsConfig.truststorePassword.toCharArray
+    val ts: KeyStore = getCertStore(truststorePassword, 
httpsConfig.truststoreFlavor, httpsConfig.keystorePath)
+    val trustManagerFactory: TrustManagerFactory = 
TrustManagerFactory.getInstance(keyFactoryType)
+    trustManagerFactory.init(ts)
+
+    val sslContext: SSLContext = SSLContext.getInstance("TLS")
+    sslContext.init(keyManagerFactory.getKeyManagers, 
trustManagerFactory.getTrustManagers, new SecureRandom)
+
+    ConnectionContext.https(sslContext, sslConfig, clientAuth = clientAuth)
+  }
+}
diff --git a/common/scala/src/main/scala/whisk/http/BasicHttpService.scala 
b/common/scala/src/main/scala/whisk/http/BasicHttpService.scala
index e0c570c..ece541b 100644
--- a/common/scala/src/main/scala/whisk/http/BasicHttpService.scala
+++ b/common/scala/src/main/scala/whisk/http/BasicHttpService.scala
@@ -18,7 +18,7 @@
 package whisk.http
 
 import scala.collection.immutable.Seq
-import scala.concurrent.Await
+import scala.concurrent.{Await, Future}
 import scala.concurrent.duration.DurationInt
 import akka.actor.ActorSystem
 import akka.event.Logging
@@ -30,12 +30,8 @@ import akka.http.scaladsl.server.RouteResult.Rejected
 import akka.http.scaladsl.server.directives._
 import akka.stream.ActorMaterializer
 import spray.json._
-import whisk.common.LogMarker
-import whisk.common.LogMarkerToken
-import whisk.common.LoggingMarkers
-import whisk.common.TransactionCounter
-import whisk.common.TransactionId
-import whisk.common.MetricEmitter
+import whisk.common._
+import whisk.core.WhiskConfig
 
 /**
  * This trait extends the Akka Directives and Actor with logging and 
transaction counting
@@ -146,14 +142,31 @@ trait BasicHttpService extends Directives with 
TransactionCounter {
 object BasicHttpService {
 
   /**
-   * Starts an HTTP route handler on given port and registers a shutdown hook.
+   * Starts an HTTPS route handler on given port and registers a shutdown hook.
    */
-  def startService(route: Route, port: Int)(implicit actorSystem: ActorSystem,
-                                            materializer: ActorMaterializer): 
Unit = {
+  def startHttpsService(route: Route, port: Int, config: WhiskConfig)(implicit 
actorSystem: ActorSystem,
+                                                                      
materializer: ActorMaterializer): Unit = {
+
     implicit val executionContext = actorSystem.dispatcher
+
+    val httpsBinding = Http().bindAndHandle(route, "0.0.0.0", port, 
connectionContext = Https.connectionContext(config))
+    addShutdownHook(httpsBinding)
+  }
+
+  /**
+   * Starts an HTTP route handler on given port and registers a shutdown hook.
+   */
+  def startHttpService(route: Route, port: Int)(implicit actorSystem: 
ActorSystem,
+                                                materializer: 
ActorMaterializer): Unit = {
     val httpBinding = Http().bindAndHandle(route, "0.0.0.0", port)
+    addShutdownHook(httpBinding)
+  }
+
+  def addShutdownHook(binding: Future[Http.ServerBinding])(implicit 
actorSystem: ActorSystem,
+                                                           materializer: 
ActorMaterializer): Unit = {
+    implicit val executionContext = actorSystem.dispatcher
     sys.addShutdownHook {
-      Await.result(httpBinding.map(_.unbind()), 30.seconds)
+      Await.result(binding.map(_.unbind()), 30.seconds)
       actorSystem.terminate()
       Await.result(actorSystem.whenTerminated, 30.seconds)
     }
diff --git a/core/controller/src/main/resources/application.conf 
b/core/controller/src/main/resources/application.conf
index e627e48..bbc298b 100644
--- a/core/controller/src/main/resources/application.conf
+++ b/core/controller/src/main/resources/application.conf
@@ -7,6 +7,9 @@ whisk {
     invoker-busy-threshold: 4
     blackbox-fraction: 10%
   }
+  controller {
+    protocol: http
+  }
 }
 
 # http://doc.akka.io/docs/akka-http/current/scala/http/configuration.html
diff --git 
a/core/controller/src/main/scala/whisk/core/controller/Controller.scala 
b/core/controller/src/main/scala/whisk/core/controller/Controller.scala
index bb2086a..095a8b6 100644
--- a/core/controller/src/main/scala/whisk/core/controller/Controller.scala
+++ b/core/controller/src/main/scala/whisk/core/controller/Controller.scala
@@ -49,6 +49,7 @@ import whisk.http.BasicRasService
 import whisk.spi.SpiLoader
 import whisk.core.containerpool.logging.LogStoreProvider
 import akka.event.Logging.InfoLevel
+import pureconfig.loadConfigOrThrow
 
 /**
  * The Controller is the service that provides the REST API for OpenWhisk.
@@ -159,6 +160,8 @@ class Controller(val instance: InstanceId,
  */
 object Controller {
 
+  protected val protocol = 
loadConfigOrThrow[String]("whisk.controller.protocol")
+
   // requiredProperties is a Map whose keys define properties that must be 
bound to
   // a value, and whose values are default values.   A null value in the Map 
means there is
   // no default value specified, so it must appear in the properties file
@@ -233,7 +236,10 @@ object Controller {
           actorSystem,
           ActorMaterializer.create(actorSystem),
           logger)
-        BasicHttpService.startService(controller.route, port)(actorSystem, 
controller.materializer)
+        if (Controller.protocol == "https")
+          BasicHttpService.startHttpsService(controller.route, port, 
config)(actorSystem, controller.materializer)
+        else
+          BasicHttpService.startHttpService(controller.route, 
port)(actorSystem, controller.materializer)
 
       case Failure(t) =>
         abort(s"Invalid runtimes manifest: $t")
diff --git 
a/core/controller/src/main/scala/whisk/core/controller/Triggers.scala 
b/core/controller/src/main/scala/whisk/core/controller/Triggers.scala
index 7a24286..93dd988 100644
--- a/core/controller/src/main/scala/whisk/core/controller/Triggers.scala
+++ b/core/controller/src/main/scala/whisk/core/controller/Triggers.scala
@@ -34,8 +34,10 @@ import akka.http.scaladsl.server.{RequestContext, 
RouteResult}
 import akka.http.scaladsl.unmarshalling.{Unmarshal, Unmarshaller}
 import akka.stream.ActorMaterializer
 import spray.json.DefaultJsonProtocol._
+import com.typesafe.sslconfig.akka.AkkaSSLConfig
+import pureconfig.loadConfigOrThrow
 import spray.json._
-import whisk.common.TransactionId
+import whisk.common.{Https, TransactionId}
 import whisk.core.controller.RestApiCommons.ListLimit
 import whisk.core.database.CacheChangeNotification
 import whisk.core.entitlement.Collection
@@ -56,6 +58,29 @@ trait WhiskTriggersApi extends WhiskCollectionAPI {
   /** Database service to CRUD triggers. */
   protected val entityStore: EntityStore
 
+  /** Connection context for HTTPS */
+  protected lazy val httpsConnectionContext = {
+    val sslConfig = AkkaSSLConfig().mapSettings { s =>
+      s.withLoose(s.loose.withDisableHostnameVerification(true))
+    }
+    Https.connectionContext(whiskConfig, Some(sslConfig))
+
+  }
+
+  protected val controllerProtocol = 
loadConfigOrThrow[String]("whisk.controller.protocol")
+
+  /**
+   * Sends a request either over http or https depending on the configuration
+   * @param request http request to send
+   * @return http response packed in a future
+   */
+  private def singleRequest(request: HttpRequest): Future[HttpResponse] = {
+    if (controllerProtocol == "https")
+      Http().singleRequest(request, connectionContext = httpsConnectionContext)
+    else
+      Http().singleRequest(request)
+  }
+
   /** Notification service for cache invalidation. */
   protected implicit val cacheChangeNotification: Some[CacheChangeNotification]
 
@@ -65,7 +90,7 @@ trait WhiskTriggersApi extends WhiskCollectionAPI {
   /** JSON response formatter. */
   /** Path to Triggers REST API. */
   protected val triggersPath = "triggers"
-  protected val url = Uri(s"http://localhost:${whiskConfig.servicePort}";)
+  protected val url = 
Uri(s"${controllerProtocol}://localhost:${whiskConfig.servicePort}")
 
   protected implicit val materializer: ActorMaterializer
 
@@ -372,7 +397,7 @@ trait WhiskTriggersApi extends WhiskCollectionAPI {
       headers = 
List(Authorization(BasicHttpCredentials(user.authkey.uuid.asString, 
user.authkey.key.asString))),
       entity = HttpEntity(MediaTypes.`application/json`, args.compactPrint))
 
-    Http().singleRequest(request)
+    singleRequest(request)
   }
 
   /** Contains the result of invoking a rule */
@@ -395,4 +420,5 @@ trait WhiskTriggersApi extends WhiskCollectionAPI {
 
   /** Custom unmarshaller for query parameters "limit" for "list" operations. 
*/
   private implicit val stringToListLimit: Unmarshaller[String, ListLimit] = 
RestApiCommons.stringToListLimit(collection)
+
 }
diff --git a/core/invoker/src/main/scala/whisk/core/invoker/Invoker.scala 
b/core/invoker/src/main/scala/whisk/core/invoker/Invoker.scala
index b6bcccd..e8e1dae 100644
--- a/core/invoker/src/main/scala/whisk/core/invoker/Invoker.scala
+++ b/core/invoker/src/main/scala/whisk/core/invoker/Invoker.scala
@@ -189,6 +189,8 @@ object Invoker {
     })
 
     val port = config.servicePort.toInt
-    BasicHttpService.startService(new InvokerServer().route, 
port)(actorSystem, ActorMaterializer.create(actorSystem))
+    BasicHttpService.startHttpService(new InvokerServer().route, port)(
+      actorSystem,
+      ActorMaterializer.create(actorSystem))
   }
 }
diff --git a/tests/src/test/resources/application.conf.j2 
b/tests/src/test/resources/application.conf.j2
index 7bf9421..e235bd7 100644
--- a/tests/src/test/resources/application.conf.j2
+++ b/tests/src/test/resources/application.conf.j2
@@ -45,4 +45,17 @@ whisk {
           WhiskActivation = {{ db.whisk.activations }}
         }
     }
+
+    controller {
+      protocol = {{ controller.protocol }}
+      https {
+        keystore-flavor = "{{ controller.ssl.storeFlavor }}"
+        keystore-path = "{{ openwhisk_home 
}}/ansible/roles/controller/files/{{ controllerKeystoreName }}"
+        keystore-password = "{{ controller.ssl.keystore.password }}"
+        truststore-flavor = "{{ controller.ssl.storeFlavor }}"
+        truststore-path = "{{ openwhisk_home 
}}/ansible/roles/controller/files/{{ controllerKeystoreName }}"
+        truststore-password = "{{ controller.ssl.keystore.password }}"
+        client-auth = "{{ controller.ssl.clientAuth }}"
+      }
+    }
 }
diff --git a/tests/src/test/scala/common/rest/WskRest.scala 
b/tests/src/test/scala/common/rest/WskRest.scala
index 0b057ac..71ec04a 100644
--- a/tests/src/test/scala/common/rest/WskRest.scala
+++ b/tests/src/test/scala/common/rest/WskRest.scala
@@ -17,7 +17,7 @@
 
 package common.rest
 
-import java.io.File
+import java.io.{File, FileInputStream}
 import java.time.Instant
 import java.util.Base64
 import java.security.cert.X509Certificate
@@ -78,17 +78,42 @@ import common.WskActorSystem
 import common.WskProps
 import whisk.core.entity.ByteSize
 import whisk.utils.retry
-import javax.net.ssl.{HostnameVerifier, KeyManager, SSLContext, SSLSession, 
X509TrustManager}
+import javax.net.ssl._
 
 import com.typesafe.sslconfig.akka.AkkaSSLConfig
 import java.nio.charset.StandardCharsets
+import java.security.KeyStore
+
+import akka.actor.ActorSystem
+import pureconfig.loadConfigOrThrow
+import whisk.common.Https.HttpsConfig
 
 class AcceptAllHostNameVerifier extends HostnameVerifier {
   override def verify(s: String, sslSession: SSLSession): Boolean = true
 }
 
 object SSL {
-  lazy val nonValidatingContext: SSLContext = {
+
+  lazy val httpsConfig = 
loadConfigOrThrow[HttpsConfig]("whisk.controller.https")
+
+  def keyManagers(clientAuth: Boolean) = {
+    if (clientAuth)
+      keyManagersForClientAuth
+    else
+      Array[KeyManager]()
+  }
+
+  def keyManagersForClientAuth: Array[KeyManager] = {
+    val keyFactoryType = "SunX509"
+    val keystorePassword = httpsConfig.keystorePassword.toCharArray
+    val ks: KeyStore = KeyStore.getInstance(httpsConfig.keystoreFlavor)
+    ks.load(new FileInputStream(httpsConfig.keystorePath), 
httpsConfig.keystorePassword.toCharArray)
+    val keyManagerFactory: KeyManagerFactory = 
KeyManagerFactory.getInstance(keyFactoryType)
+    keyManagerFactory.init(ks, keystorePassword)
+    keyManagerFactory.getKeyManagers
+  }
+
+  def nonValidatingContext(clientAuth: Boolean = false): SSLContext = {
     class IgnoreX509TrustManager extends X509TrustManager {
       def checkClientTrusted(chain: Array[X509Certificate], authType: String) 
= ()
       def checkServerTrusted(chain: Array[X509Certificate], authType: String) 
= ()
@@ -96,9 +121,35 @@ object SSL {
     }
 
     val context = SSLContext.getInstance("TLS")
-    context.init(Array[KeyManager](), Array(new IgnoreX509TrustManager), null)
+
+    context.init(keyManagers(clientAuth), Array(new IgnoreX509TrustManager), 
null)
     context
   }
+
+  def httpsConnectionContext(implicit system: ActorSystem) = {
+    val sslConfig = AkkaSSLConfig().mapSettings { s =>
+      
s.withHostnameVerifierClass(classOf[AcceptAllHostNameVerifier].asInstanceOf[Class[HostnameVerifier]])
+    }
+    new 
HttpsConnectionContext(SSL.nonValidatingContext(httpsConfig.clientAuth.toBoolean),
 Some(sslConfig))
+  }
+}
+
+object HttpConnection {
+
+  /**
+   * Returns either the https context that is tailored for self-signed 
certificates on the controller, or
+   * a default connection context used in Http.SingleRequest
+   * @param protocol protocol used to communicate with controller API
+   * @param system actor system
+   * @return https connection context
+   */
+  def getContext(protocol: String)(implicit system: ActorSystem) = {
+    if (protocol == "https")
+      SSL.httpsConnectionContext
+    else
+//    supports http
+      Http().defaultClientHttpsContext
+  }
 }
 
 class WskRest() extends RunWskRestCmd with BaseWsk {
@@ -1170,6 +1221,7 @@ class RunWskRestCmd() extends FlatSpec with RunWskCmd 
with Matchers with ScalaFu
 
   implicit val config = PatienceConfig(100 seconds, 15 milliseconds)
   implicit val materializer = ActorMaterializer()
+  val protocol = loadConfigOrThrow[String]("whisk.controller.protocol")
   val idleTimeout = 90 seconds
   val queueSize = 10
   val maxOpenRequest = 1024
@@ -1180,7 +1232,7 @@ class RunWskRestCmd() extends FlatSpec with RunWskCmd 
with Matchers with ScalaFu
     
s.withHostnameVerifierClass(classOf[AcceptAllHostNameVerifier].asInstanceOf[Class[HostnameVerifier]])
   }
 
-  val connectionContext = new HttpsConnectionContext(SSL.nonValidatingContext, 
Some(sslConfig))
+  val connectionContext = new 
HttpsConnectionContext(SSL.nonValidatingContext(), Some(sslConfig))
 
   def isStatusCodeExpected(expectedExitCode: Int, statusCode: Int): Boolean = {
     if ((expectedExitCode != DONTCARE_EXIT) && (expectedExitCode != 
ANY_ERROR_EXIT))
diff --git a/tests/src/test/scala/ha/CacheInvalidationTests.scala 
b/tests/src/test/scala/ha/CacheInvalidationTests.scala
index 59bc36d..9841a5e 100644
--- a/tests/src/test/scala/ha/CacheInvalidationTests.scala
+++ b/tests/src/test/scala/ha/CacheInvalidationTests.scala
@@ -19,12 +19,10 @@ package ha
 
 import scala.concurrent.Await
 import scala.concurrent.duration.DurationInt
-
 import org.junit.runner.RunWith
 import org.scalatest.FlatSpec
 import org.scalatest.Matchers
 import org.scalatest.junit.JUnitRunner
-
 import akka.http.scaladsl.Http
 import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
 import akka.http.scaladsl.marshalling.Marshal
@@ -36,9 +34,11 @@ import akka.stream.ActorMaterializer
 import common.WhiskProperties
 import common.WskActorSystem
 import common.WskTestHelpers
+import common.rest.HttpConnection
 import spray.json._
 import spray.json.DefaultJsonProtocol._
 import whisk.core.WhiskConfig
+import pureconfig.loadConfigOrThrow
 
 @RunWith(classOf[JUnitRunner])
 class CacheInvalidationTests extends FlatSpec with Matchers with 
WskTestHelpers with WskActorSystem {
@@ -47,11 +47,17 @@ class CacheInvalidationTests extends FlatSpec with Matchers 
with WskTestHelpers
 
   val hosts = WhiskProperties.getProperty("controller.hosts").split(",")
 
+  val controllerProtocol = 
loadConfigOrThrow[String]("whisk.controller.protocol")
+  val connectionContext = HttpConnection.getContext(controllerProtocol)
+
   def ports(instance: Int) = WhiskProperties.getControllerBasePort + instance
 
   def controllerUri(instance: Int) = {
     require(instance >= 0 && instance < hosts.length, "Controller instance not 
known.")
-    
Uri().withScheme("http").withHost(hosts(instance)).withPort(ports(instance))
+    Uri()
+      .withScheme(controllerProtocol)
+      .withHost(hosts(instance))
+      .withPort(ports(instance))
   }
 
   def actionPath(name: String) = 
Uri.Path(s"/api/v1/namespaces/_/actions/$name")
@@ -71,13 +77,15 @@ class CacheInvalidationTests extends FlatSpec with Matchers 
with WskTestHelpers
 
     val request = Marshal(body).to[RequestEntity].flatMap { entity =>
       Http()
-        .singleRequest(HttpRequest(
-          method = HttpMethods.PUT,
-          uri = controllerUri(controllerInstance)
-            .withPath(actionPath(name))
-            .withQuery(Uri.Query("overwrite" -> true.toString)),
-          headers = List(authHeader),
-          entity = entity))
+        .singleRequest(
+          HttpRequest(
+            method = HttpMethods.PUT,
+            uri = controllerUri(controllerInstance)
+              .withPath(actionPath(name))
+              .withQuery(Uri.Query("overwrite" -> true.toString)),
+            headers = List(authHeader),
+            entity = entity),
+          connectionContext = connectionContext)
         .flatMap { response =>
           Unmarshal(response).to[JsObject].map { resBody =>
             withClue(s"Error in Body: $resBody")(response.status shouldBe 
StatusCodes.OK)
@@ -95,7 +103,8 @@ class CacheInvalidationTests extends FlatSpec with Matchers 
with WskTestHelpers
         HttpRequest(
           method = HttpMethods.GET,
           uri = controllerUri(controllerInstance).withPath(actionPath(name)),
-          headers = List(authHeader)))
+          headers = List(authHeader)),
+        connectionContext = connectionContext)
       .flatMap { response =>
         Unmarshal(response).to[JsObject].map { resBody =>
           withClue(s"Wrong statuscode from controller. Body is: 
$resBody")(response.status shouldBe expectedCode)
@@ -114,7 +123,8 @@ class CacheInvalidationTests extends FlatSpec with Matchers 
with WskTestHelpers
         HttpRequest(
           method = HttpMethods.DELETE,
           uri = controllerUri(controllerInstance).withPath(actionPath(name)),
-          headers = List(authHeader)))
+          headers = List(authHeader)),
+        connectionContext = connectionContext)
       .flatMap { response =>
         Unmarshal(response).to[JsObject].map { resBody =>
           expectedCode.map { code =>
diff --git a/tests/src/test/scala/ha/ShootComponentsTests.scala 
b/tests/src/test/scala/ha/ShootComponentsTests.scala
index a3ea2b5..e3480bc 100644
--- a/tests/src/test/scala/ha/ShootComponentsTests.scala
+++ b/tests/src/test/scala/ha/ShootComponentsTests.scala
@@ -33,7 +33,7 @@ import akka.http.scaladsl.model.StatusCodes
 import akka.http.scaladsl.unmarshalling.Unmarshal
 import akka.stream.ActorMaterializer
 import common._
-import common.rest.WskRest
+import common.rest.{HttpConnection, WskRest}
 import pureconfig._
 import spray.json._
 import spray.json.DefaultJsonProtocol._
@@ -60,6 +60,8 @@ class ShootComponentsTests
   implicit val materializer = ActorMaterializer()
   implicit val testConfig = PatienceConfig(1.minute)
 
+  val controllerProtocol = 
loadConfigOrThrow[String]("whisk.controller.protocol")
+
   // Throttle requests to the remaining controllers to avoid getting 429s. (60 
req/min)
   val amountOfControllers = 
WhiskProperties.getProperty(WhiskConfig.controllerInstances).toInt
   val limit = 
WhiskProperties.getProperty(WhiskConfig.actionInvokePerMinuteLimit).toDouble
@@ -79,8 +81,15 @@ class ShootComponentsTests
   val dbWhiskAuth = dbConfig.databases.get("WhiskAuth").get
 
   def ping(host: String, port: Int, path: String = "/") = {
+
+    val connectionContext = HttpConnection.getContext(controllerProtocol)
+
     val response = Try {
-      Http().singleRequest(HttpRequest(uri = 
s"http://$host:$port$path";)).futureValue
+      Http()
+        .singleRequest(
+          HttpRequest(uri = s"$controllerProtocol://$host:$port$path"),
+          connectionContext = connectionContext)
+        .futureValue
     }.toOption
 
     response.map { res =>
diff --git a/tests/src/test/scala/services/HeadersTests.scala 
b/tests/src/test/scala/services/HeadersTests.scala
index ab3792c..44240d7 100644
--- a/tests/src/test/scala/services/HeadersTests.scala
+++ b/tests/src/test/scala/services/HeadersTests.scala
@@ -21,20 +21,17 @@ import scala.concurrent.Future
 import scala.concurrent.duration.DurationInt
 import scala.language.postfixOps
 import scala.collection.immutable.Seq
-
 import org.junit.runner.RunWith
 import org.scalatest.FlatSpec
 import org.scalatest.Matchers
 import org.scalatest.concurrent.ScalaFutures
 import org.scalatest.junit.JUnitRunner
 import org.scalatest.time.Span.convertDurationToSpan
-
 import common.TestUtils
 import common.WhiskProperties
-import common.rest.WskRest
+import common.rest.{HttpConnection, WskRest}
 import common.WskProps
 import common.WskTestHelpers
-
 import akka.http.scaladsl.model.Uri
 import akka.http.scaladsl.model.Uri.Path
 import akka.http.scaladsl.model.headers.BasicHttpCredentials
@@ -52,8 +49,8 @@ import akka.http.scaladsl.model.headers._
 import akka.http.scaladsl.model.HttpMethod
 import akka.http.scaladsl.model.HttpHeader
 import akka.stream.ActorMaterializer
-
 import common.WskActorSystem
+import pureconfig.loadConfigOrThrow
 
 @RunWith(classOf[JUnitRunner])
 class HeadersTests extends FlatSpec with Matchers with ScalaFutures with 
WskActorSystem with WskTestHelpers {
@@ -62,12 +59,14 @@ class HeadersTests extends FlatSpec with Matchers with 
ScalaFutures with WskActo
 
   implicit val materializer = ActorMaterializer()
 
+  val controllerProtocol = 
loadConfigOrThrow[String]("whisk.controller.protocol")
+  println(loadConfigOrThrow[String]("whisk"))
   val whiskAuth = WhiskProperties.getBasicAuth
   val creds = BasicHttpCredentials(whiskAuth.fst, whiskAuth.snd)
   val allMethods = Some(Set(DELETE.name, GET.name, POST.name, PUT.name))
   val allowOrigin = `Access-Control-Allow-Origin`.*
   val allowHeaders = `Access-Control-Allow-Headers`("Authorization", 
"Content-Type")
-  val url = Uri(s"http://${WhiskProperties.getBaseControllerAddress()}")
+  val url = 
Uri(s"$controllerProtocol://${WhiskProperties.getBaseControllerAddress()}")
 
   def request(method: HttpMethod, uri: Uri, headers: Option[Seq[HttpHeader]] = 
None): Future[HttpResponse] = {
     val httpRequest = headers match {
@@ -75,7 +74,8 @@ class HeadersTests extends FlatSpec with Matchers with 
ScalaFutures with WskActo
       case None          => HttpRequest(method, uri)
     }
 
-    Http().singleRequest(httpRequest)
+    val connectionContext = HttpConnection.getContext(controllerProtocol)
+    Http().singleRequest(httpRequest, connectionContext = connectionContext)
   }
 
   implicit val config = PatienceConfig(10 seconds, 0 milliseconds)
diff --git a/tools/travis/setup.sh b/tools/travis/setup.sh
index 26c5df1..ab35954 100755
--- a/tools/travis/setup.sh
+++ b/tools/travis/setup.sh
@@ -32,4 +32,4 @@ docker info
 pip install --user couchdb
 
 # Ansible
-pip install --user ansible==2.3.0.0
+pip install --user ansible==2.4.2.0

-- 
To stop receiving notification emails like this one, please contact
cbic...@apache.org.

Reply via email to