You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by cb...@apache.org on 2018/03/12 09:15:42 UTC
[incubator-openwhisk] branch master updated: Enable ssl on the path
between edge and controllers. (#3077)
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 <ra...@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
cbickel@apache.org.