blob: 8fe2a5d51a8e09e44be16cd13a7729babc618407 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openwhisk.core.entity.test
import java.security.InvalidAlgorithmParameterException
import org.apache.openwhisk.core.entity._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.{BeforeAndAfter, FlatSpec, Matchers}
import spray.json.DefaultJsonProtocol._
import spray.json._
@RunWith(classOf[JUnitRunner])
class ParameterEncryptionTests extends FlatSpec with Matchers with BeforeAndAfter {
val k128 = "ra1V6AfOYAv0jCzEdufIFA=="
val k256 = "j5rLzhtxwzPyUVUy8/p8XJmBoKeDoSzNJP1SITJEY9E="
// default is no-op but keys are available to decode encoded params
val noop = ParameterEncryption(ParameterStorageConfig(aes128 = Some(k128), aes256 = Some(k256)))
val aes128decoder = ParameterEncryption(ParameterStorageConfig("aes-128", aes128 = Some(k128)))
val aes128encoder = aes128decoder.default
val aes256decoder = ParameterEncryption(ParameterStorageConfig("aes-256", aes256 = Some(k256)))
val aes256encoder = aes256decoder.default
val parameters = new Parameters(
Map(
new ParameterName("one") -> new ParameterValue("secret".toJson, false),
new ParameterName("two") -> new ParameterValue("secret".toJson, true)))
behavior of "ParameterEncryption"
it should "not have a default coder when turned off" in {
ParameterEncryption(ParameterStorageConfig("")).default shouldBe empty
ParameterEncryption(ParameterStorageConfig("off")).default shouldBe empty
ParameterEncryption(ParameterStorageConfig("noop")).default shouldBe empty
ParameterEncryption(ParameterStorageConfig("OFF")).default shouldBe empty
ParameterEncryption(ParameterStorageConfig("NOOP")).default shouldBe empty
}
behavior of "Parameters"
it should "handle decryption of json objects" in {
val originalValue =
"""
|[
| { "key": "paramName1", "init": false, "value": "from-action" },
| { "key": "paramName2", "init": false, "value": "from-pack" }
|]
|""".stripMargin
val p = Parameters.serdes.read(originalValue.parseJson)
p.get("paramName1").get.convertTo[String] shouldBe "from-action"
p.get("paramName2").get.convertTo[String] shouldBe "from-pack"
p.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe empty
}
}
it should "handle decryption of json objects with null field" in {
val originalValue =
"""
|[
| { "key": "paramName1", "encryption":null, "init": false, "value": "from-action" },
| { "key": "paramName2", "encryption":null, "init": false, "value": "from-pack" }
|]
|""".stripMargin
val p = Parameters.serdes.read(originalValue.parseJson)
p.get("paramName1").get.convertTo[String] shouldBe "from-action"
p.get("paramName2").get.convertTo[String] shouldBe "from-pack"
p.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe empty
}
}
it should "drop encryption propery when no longer encrypted" in {
val originalValue =
"""
|[
| { "key": "paramName1", "encryption":null, "init": false, "value": "from-action" },
| { "key": "paramName2", "encryption":null, "init": false, "value": "from-pack" }
|]
|""".stripMargin
val p = Parameters.serdes.read(originalValue.parseJson)
Parameters.serdes.write(p).compactPrint should not include "encryption"
p.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe empty
}
}
it should "read the merged message payload from kafka into parameters" in {
val locked = parameters.lock(aes128encoder)
val mixedParams = locked.merge(Some(Parameters("plain", "test-plain").toJsObject))
mixedParams shouldBe defined
mixedParams.get.fields("one") shouldBe locked.get("one").get
mixedParams.get.fields("two") shouldBe locked.get("two").get
mixedParams.get.fields("two") should not be locked.get("one").get
mixedParams.get.fields("plain") shouldBe JsString("test-plain")
}
behavior of "AesParameterEncryption"
it should "correctly mark the encrypted parameters after lock" in {
val locked = parameters.lock(aes128encoder)
locked.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe Some("aes-128")
paramValue.value.convertTo[String] should not be "secret"
}
}
it should "serialize to json correctly" in {
val locked = parameters.lock(aes128encoder)
locked.toJsObject.toString should fullyMatch regex """\Q{"one":"\E.*\Q","two":"\E.*\Q"}""".stripMargin.r
locked.lockedParameters() shouldBe Map("one" -> "aes-128", "two" -> "aes-128")
}
it should "serialize to json correctly when a locked parameter is overriden" in {
val locked = parameters.lock(aes128encoder)
locked
.merge(Some(JsObject("one" -> JsString("override"))))
.get
.compactPrint should fullyMatch regex """\Q{"one":"override","two":"\E.*\Q"}""".stripMargin.r
locked.lockedParameters(Set("one")) shouldBe Map("two" -> "aes-128")
}
it should "correctly decrypt encrypted values" in {
val locked = parameters.lock(aes128encoder)
locked.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe Some("aes-128")
paramValue.value.convertTo[String] should not be "secret"
}
val unlocked = locked.unlock(aes128decoder)
unlocked.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe empty
paramValue.value.convertTo[String] shouldBe "secret"
}
}
it should "correctly decrypt encrypted JsObject values" in {
val obj = Map("key" -> "xyz".toJson, "value" -> "v1".toJson).toJson
val complexParam = new Parameters(Map(new ParameterName("one") -> new ParameterValue(obj, false)))
val locked = complexParam.lock(aes128encoder)
locked.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe Some("aes-128")
paramValue.value.convertTo[String] should not be "secret"
}
val unlocked = locked.unlock(aes128decoder)
unlocked.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe empty
paramValue.value shouldBe obj
}
}
it should "correctly decrypt encrypted multiline values" in {
val lines = "line1\nline2\nline3\nline4"
val multiline = new Parameters(Map(new ParameterName("one") -> new ParameterValue(JsString(lines), false)))
val locked = multiline.lock(aes128encoder)
locked.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe Some("aes-128")
paramValue.value.convertTo[String] should not be "secret"
}
val unlocked = locked.unlock(aes128decoder)
unlocked.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe empty
paramValue.value.convertTo[String] shouldBe lines
}
}
// Not sure having cancelled tests is a good idea either, need to work on aes256 packaging.
it should "work if with aes256 if policy allows it" in {
try {
val locked = parameters.lock(aes256encoder)
locked.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe Some("aes-256")
paramValue.value.convertTo[String] should not be "secret"
}
val unlocked = locked.unlock(noop)
unlocked.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe empty
paramValue.value.convertTo[String] shouldBe "secret"
}
} catch {
case e: InvalidAlgorithmParameterException =>
cancel(e.toString)
}
}
it should "support reverting back to Noop encryption" in {
try {
val locked = parameters.lock(aes128encoder)
locked.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe Some("aes-128")
paramValue.value.convertTo[String] should not be "secret"
}
val lockedJson = Parameters.serdes.write(locked).compactPrint
val toDecrypt = Parameters.serdes.read(lockedJson.parseJson)
// defaults to no-op
val unlocked = toDecrypt.unlock(noop)
unlocked.params.foreach {
case (_, paramValue) =>
paramValue.encryption shouldBe empty
paramValue.value.convertTo[String] shouldBe "secret"
}
unlocked.toJsObject shouldBe JsObject("one" -> "secret".toJson, "two" -> "secret".toJson)
} catch {
case e: InvalidAlgorithmParameterException =>
cancel(e.toString)
}
}
behavior of "No-op Encryption"
it should "not mark parameters as encrypted" in {
val locked = parameters.lock()
locked.params.foreach {
case (_, paramValue) =>
paramValue.value.convertTo[String] shouldBe "secret"
}
}
}