Improvements to parameter encryption to support per-namespace keys (#4855)
* Review notes and refactoring. No intended semantic change.
* Remove 'strange construction' of param as json.
Simplify expression.
* Remove unnecessary cons of Encrypter class.
* Refactoring of encryptor names.
* Move lock/unlock to Parameters. Refactor tests.
* Partition params into locked and unlocked sets.
* Remove getter, make field protected/accessible for test.
* Comments.
* Revert changes to test suite.
* Exclude overriden parameters from decryption.
* Tighten tests.
Add test for unlocking args in container proxy.
* Fix test.
diff --git a/ansible/group_vars/all b/ansible/group_vars/all
index 0c54c92..342ad1d 100644
--- a/ansible/group_vars/all
+++ b/ansible/group_vars/all
@@ -49,8 +49,8 @@
version:
date: "{{ansible_date_time.iso8601}}"
feature_flags:
- require_api_key_annotation: "{{ require_api_key_annotation | default(true) | lower }}"
- require_response_payload: "{{ require_response_payload | default(true) | lower }}"
+ require_api_key_annotation: "{{ require_api_key_annotation | default(true) | lower }}"
+ require_response_payload: "{{ require_response_payload | default(true) | lower }}"
##
# configuration parameters related to support runtimes (see org.apache.openwhisk.core.entity.ExecManifest for schema of the manifest).
diff --git a/common/scala/src/main/resources/application.conf b/common/scala/src/main/resources/application.conf
index 50a748a..ea96e92 100644
--- a/common/scala/src/main/resources/application.conf
+++ b/common/scala/src/main/resources/application.conf
@@ -567,13 +567,17 @@
# it will slowly migrate all the actions that have been 'updated' to use encrypted parameters but going back would
# require a currently non-existing migration step.
parameter-storage {
- # Base64 encoded 256 bit key
- #aes-256 = ""
- # Base64 encoded 128 bit key
- #aes-128 = ""
# The current algorithm to use for parameter encryption, this can be changed but you have to leave all the keys
# configured for any algorithm you used previously.
- #current = "aes-128|aes-256"
+ # Allowed values:
+ # "off|noop" -> no op/no encryption
+ # "aes-128" -> AES with 128 bit key (given as base64 encoded string)
+ # "aes-256" -> AES with 256 bit key (given as base64 encoded string)
+ current = "off"
+ # Base64 encoded 128 bit key
+ #aes-128 = ""
+ # Base64 encoded 256 bit key
+ #aes-256 = ""
}
}
#placeholder for test overrides so that tests can override defaults in application.conf (todo: move all defaults to reference.conf)
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/connector/Message.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/connector/Message.scala
index 5ca2a71..c86426d 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/connector/Message.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/connector/Message.scala
@@ -57,6 +57,7 @@
blocking: Boolean,
content: Option[JsObject],
initArgs: Set[String] = Set.empty,
+ lockedArgs: Map[String, String] = Map.empty,
cause: Option[ActivationId] = None,
traceContext: Option[Map[String, String]] = None)
extends Message {
@@ -171,7 +172,7 @@
def parse(msg: String) = Try(serdes.read(msg.parseJson))
private implicit val fqnSerdes = FullyQualifiedEntityName.serdes
- implicit val serdes = jsonFormat11(ActivationMessage.apply)
+ implicit val serdes = jsonFormat12(ActivationMessage.apply)
}
object CombinedCompletionAndResultMessage extends DefaultJsonProtocol {
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/Parameter.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/Parameter.scala
index 89494fd..4a66867 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/Parameter.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/Parameter.scala
@@ -17,13 +17,13 @@
package org.apache.openwhisk.core.entity
-import org.apache.openwhisk.core.entity.size.{SizeInt, SizeString}
+import scala.util.{Failure, Success, Try}
import spray.json.DefaultJsonProtocol._
import spray.json._
-import scala.collection.immutable.ListMap
import scala.language.postfixOps
-import scala.util.{Failure, Success, Try}
+import org.apache.openwhisk.core.entity.size.SizeInt
+import org.apache.openwhisk.core.entity.size.SizeString
/**
* Parameters is a key-value map from parameter names to parameter values. The value of a
@@ -32,7 +32,7 @@
* @param key the parameter name, assured to be non-null because it is a value
* @param value the parameter value, assured to be non-null because it is a value
*/
-protected[core] class Parameters protected[entity] (private val params: Map[ParameterName, ParameterValue])
+protected[core] class Parameters protected[entity] (protected[entity] val params: Map[ParameterName, ParameterValue])
extends AnyVal {
/**
@@ -46,13 +46,6 @@
.foldLeft(0 B)(_ + _)
}
- protected[entity] def +(p: (ParameterName, ParameterValue)) = {
-
- Option(p) map { p =>
- new Parameters(params + (p._1 -> p._2))
- } getOrElse this
- }
-
protected[entity] def +(p: ParameterName, v: ParameterValue) = {
new Parameters(params + (p -> v))
}
@@ -71,43 +64,35 @@
Try(new Parameters(params - new ParameterName(p))) getOrElse this
}
- /** Gets list all defined parameters. */
+ /** Gets set of all defined parameters. */
protected[core] def definedParameters: Set[String] = {
params.keySet filter (params(_).isDefined) map (_.name)
}
- /** Gets list all defined parameters. */
+ /** Gets set of all defined parameters. */
protected[core] def initParameters: Set[String] = {
params.keySet filter (params(_).init) map (_.name)
}
- protected[core] def getMap = {
- params
+ /**
+ * Gets map of all locked (encrypted) parameters, excluding parameters from given set.
+ */
+ protected[core] def lockedParameters(exclude: Set[String] = Set.empty): Map[String, String] = {
+ params.collect {
+ case p if p._2.encryption.isDefined && !exclude.contains(p._1.name) => (p._1.name -> p._2.encryption.get)
+ }
}
+
protected[core] def toJsArray = {
JsArray(params map { p =>
- val init = p._2.init match {
- case true => Some("init" -> p._2.init.toJson)
- case _ => None
- }
- val encrypt = p._2.encryption match {
- case (JsNull) => None
- case _ => Some("encryption" -> p._2.encryption)
- }
- // Have do use this slightly strange construction to get the json object order identical.
- JsObject(ListMap() ++ encrypt ++ init ++ Map("key" -> p._1.name.toJson, "value" -> p._2.value.toJson))
+ val init = if (p._2.init) Some("init" -> JsTrue) else None
+ val encrypt = p._2.encryption.map(e => ("encryption" -> JsString(e)))
+
+ JsObject(Map("key" -> p._1.name.toJson, "value" -> p._2.value) ++ init ++ encrypt)
} toSeq: _*)
}
- protected[core] def toJsObject =
- JsObject(params.map(p => {
- val newValue =
- if (p._2.encryption == JsNull)
- p._2.value.toJson
- else
- JsObject("value" -> p._2.value.toJson, "encryption" -> p._2.encryption, "init" -> p._2.init.toJson)
- (p._1.name, newValue)
- }))
+ protected[core] def toJsObject = JsObject(params.map(p => (p._1.name -> p._2.value.toJson)))
override def toString = toJsArray.compactPrint
@@ -144,6 +129,40 @@
case _ => true
} getOrElse valueForNonExistent
}
+
+ /**
+ * Encrypts any parameters that are not yet encoded.
+ *
+ * @param encoder the encoder to transform parameter values with
+ * @return parameters with all values encrypted
+ */
+ def lock(encoder: Option[Encrypter] = None): Parameters = {
+ encoder
+ .map { coder =>
+ new Parameters(params.map {
+ case (paramName, paramValue) if paramValue.encryption.isEmpty =>
+ paramName -> coder.encrypt(paramValue)
+ case p => p
+ })
+ }
+ .getOrElse(this)
+ }
+
+ /**
+ * Decodes parameters. If the encryption scheme for a parameter is not recognized, it is not modified.
+ *
+ * @param decoder the decoder to use to transform locked values
+ * @return parameters will all values decoded (where scheme is known)
+ */
+ def unlock(decoder: ParameterEncryption): Parameters = {
+ new Parameters(params.map {
+ case p @ (paramName, paramValue) =>
+ paramValue.encryption
+ .map(paramName -> decoder.encryptor(_).decrypt(paramValue))
+ .getOrElse(p)
+ })
+ }
+
}
/**
@@ -175,11 +194,11 @@
*
* @param v the value of the parameter, may be null
* @param init if true, this parameter value is only offered to the action during initialization
- * @param encryptionDetails the name of the encrypter used to store the parameter.
+ * @param encryption the name of the encryption algorithm used to store the parameter or none (plain text)
*/
protected[entity] case class ParameterValue protected[entity] (private val v: JsValue,
val init: Boolean,
- val encryptionDetails: Option[JsString] = None) {
+ val encryption: Option[String] = None) {
/** @return JsValue if defined else JsNull. */
protected[entity] def value = Option(v) getOrElse JsNull
@@ -187,9 +206,6 @@
/** @return true iff value is not JsNull. */
protected[entity] def isDefined = value != JsNull
- /** @return JsValue if defined else JsNull. */
- protected[entity] def encryption = encryptionDetails getOrElse JsNull
-
/**
* The size of the ParameterValue entity as ByteSize.
*/
@@ -208,8 +224,8 @@
* Creates a parameter tuple from a pair of strings.
* A convenience method for tests.
*
- * @param p the parameter name
- * @param v the parameter value
+ * @param p the parameter name
+ * @param v the parameter value
* @param init the parameter is for initialization
* @return (ParameterName, ParameterValue)
* @throws IllegalArgumentException if key is not defined
@@ -224,8 +240,8 @@
/**
* Creates a parameter tuple from a parameter name and JsValue.
*
- * @param p the parameter name
- * @param v the parameter value
+ * @param p the parameter name
+ * @param v the parameter value
* @param init the parameter is for initialization
* @return (ParameterName, ParameterValue)
* @throws IllegalArgumentException if key is not defined
@@ -252,29 +268,6 @@
ParameterValue(Option(v).getOrElse(JsNull), false, None))
}
- def readMergedList(value: JsValue): Parameters =
- Try {
-
- val JsObject(obj) = value
- new Parameters(
- obj
- .map((tuple: (String, JsValue)) => {
- val key = new ParameterName(tuple._1)
- val paramVal: ParameterValue = tuple._2 match {
- case o: JsObject =>
- o.getFields("value", "init", "encryption") match {
- case Seq(v: JsValue, JsBoolean(i), e: JsString) =>
- ParameterValue(v, i, Some(e))
- case _ => ParameterValue(o, false, None)
- }
- case v: JsValue => ParameterValue(v, false, None)
- }
- (key, paramVal)
- })
- .toMap)
- } getOrElse deserializationError(
- "parameters malformed, could not get a JsObject from: " + (if (value != null) value.toString() else ""))
-
override protected[core] implicit val serdes = new RootJsonFormat[Parameters] {
def write(p: Parameters) = p.toJsArray
@@ -285,35 +278,12 @@
* @param parameters the JSON representation of an parameter array
* @return Parameters instance if parameters conforms to schema
*/
- def read(value: JsValue) =
- Try {
- val JsArray(params) = value
- params
- } flatMap {
- read(_)
- } getOrElse {
- Try {
- var converted = new ListMap[ParameterName, ParameterValue]()
- val JsObject(o) = value
- o.foreach(i =>
- i._2.asJsObject.getFields("value", "init", "encryption") match {
- case Seq(v: JsValue, JsBoolean(init), e: JsValue) if e != JsNull =>
- val key = new ParameterName(i._1)
- val value = ParameterValue(v, init, Some(JsString(e.convertTo[String])))
- converted = converted + (key -> value)
- case Seq(v: JsValue, JsBoolean(init), e: JsValue) =>
- val key = new ParameterName(i._1)
- val value = ParameterValue(v, init, None)
- converted = converted + (key -> value)
- })
- if (converted.size == 0) {
- deserializationError("parameters malformed no parameters available: " + value.toString())
- } else {
- new Parameters(converted)
- }
- } getOrElse deserializationError(
- "parameters malformed could not read directly: " + (if (value != null) value.toString() else ""))
+ def read(value: JsValue): Parameters = {
+ value match {
+ case JsArray(params) => read(params).getOrElse(deserializationError("parameters malformed!"))
+ case _ => deserializationError("parameters malformed!")
}
+ }
/**
* Gets parameters as a Parameters instances.
@@ -323,29 +293,33 @@
* @return Parameters instance if parameters conforms to schema
*/
def read(params: Vector[JsValue]) = Try {
- new Parameters(
- params
- .map(i => {
- i.asJsObject.getFields("key", "value", "init", "encryption") match {
- case Seq(JsString(k), v: JsValue) =>
- val key = new ParameterName(k)
- val value = ParameterValue(v, false)
- (key, value)
- case Seq(JsString(k), v: JsValue, JsBoolean(i), e: JsString) =>
- val key = new ParameterName(k)
- val value = ParameterValue(v, i, Some(e))
- (key, value)
- case Seq(JsString(k), v: JsValue, JsBoolean(i)) =>
- val key = new ParameterName(k)
- val value = ParameterValue(v, i)
- (key, value)
- case Seq(JsString(k), v: JsValue, e: JsString) if (i.asJsObject.fields.contains("encryption")) =>
- val key = new ParameterName(k)
- val value = ParameterValue(v, false, Some(e))
- (key, value)
- }
- })
- .toMap)
+ new Parameters(params.map {
+ case o @ JsObject(fields) =>
+ o.getFields("key", "value", "init", "encryption") match {
+ case Seq(JsString(k), v: JsValue) if fields.contains("value") =>
+ val key = new ParameterName(k)
+ val value = ParameterValue(v, false)
+ (key, value)
+ case Seq(JsString(k), v: JsValue, JsBoolean(i)) =>
+ val key = new ParameterName(k)
+ val value = ParameterValue(v, i)
+ (key, value)
+ case Seq(JsString(k), v: JsValue, JsBoolean(i), JsString(e)) =>
+ val key = new ParameterName(k)
+ val value = ParameterValue(v, i, Some(e))
+ (key, value)
+ case Seq(JsString(k), v: JsValue, JsBoolean(i), JsNull) =>
+ val key = new ParameterName(k)
+ val value = ParameterValue(v, i, None)
+ (key, value)
+ case Seq(JsString(k), v: JsValue, JsString(e))
+ if fields.contains("value") && fields.contains("encryption") =>
+ val key = new ParameterName(k)
+ val value = ParameterValue(v, false, Some(e))
+ (key, value)
+ }
+ case _ => deserializationError("invalid parameter")
+ }.toMap)
}
}
}
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/ParameterEncryption.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/ParameterEncryption.scala
index 482579c..2169eaf 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/ParameterEncryption.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/ParameterEncryption.scala
@@ -26,79 +26,85 @@
import org.apache.openwhisk.core.ConfigKeys
import pureconfig.loadConfig
import spray.json.DefaultJsonProtocol._
-import spray.json.{JsNull, JsString}
+import spray.json._
import pureconfig.generic.auto._
import spray.json._
-case class ParameterStorageConfig(current: String = "", aes128: String = "", aes256: String = "")
-object ParameterEncryption {
- private val storageConfigLoader = loadConfig[ParameterStorageConfig](ConfigKeys.parameterStorage)
- var storageConfig = storageConfigLoader.getOrElse(ParameterStorageConfig.apply())
- def lock(params: Parameters): Parameters = {
- val configuredEncryptors = new encrypters(storageConfig)
- new Parameters(
- params.getMap
- .map(({
- case (paramName, paramValue) if paramValue.encryption == JsNull =>
- paramName -> configuredEncryptors.getCurrentEncrypter().encrypt(paramValue)
- case (paramName, paramValue) => paramName -> paramValue
- })))
+protected[core] case class ParameterStorageConfig(current: String = ParameterEncryption.NO_ENCRYPTION,
+ aes128: Option[String] = None,
+ aes256: Option[String] = None)
+
+protected[core] class ParameterEncryption(val default: Option[Encrypter], encryptors: Map[String, Encrypter]) {
+
+ /**
+ * Gets the coder for the given scheme name.
+ *
+ * @param name the name of the encryption algorithm (defaults to current from last configuration)
+ * @return the coder if there is one else no-op encryptor
+ */
+ def encryptor(name: String): Encrypter = {
+ encryptors.get(name).getOrElse(ParameterEncryption.noop)
}
- def unlock(params: Parameters): Parameters = {
- val configuredEncryptors = new encrypters(storageConfig)
- new Parameters(
- params.getMap
- .map(({
- case (paramName, paramValue)
- if paramValue.encryption != JsNull && !configuredEncryptors
- .getEncrypter(paramValue.encryption.convertTo[String])
- .isEmpty =>
- paramName -> configuredEncryptors
- .getEncrypter(paramValue.encryption.convertTo[String])
- .get
- .decrypt(paramValue)
- case (paramName, paramValue) => paramName -> paramValue
- })))
+
+}
+
+protected[core] object ParameterEncryption {
+
+ val NO_ENCRYPTION = "noop"
+ val AES128_ENCRYPTION = "aes-128"
+ val AES256_ENCRYPTION = "aes-256"
+
+ val noop = new Encrypter {
+ override val name = NO_ENCRYPTION
+ }
+
+ val singleton: ParameterEncryption = {
+ val configLoader = loadConfig[ParameterStorageConfig](ConfigKeys.parameterStorage)
+ val config = configLoader.getOrElse(ParameterStorageConfig(noop.name))
+ ParameterEncryption(config)
+ }
+
+ def apply(config: ParameterStorageConfig): ParameterEncryption = {
+ val availableEncoders = Map(noop.name -> noop) ++
+ config.aes128.map(k => AES128_ENCRYPTION -> new Aes128(k)) ++
+ config.aes256.map(k => AES256_ENCRYPTION -> new Aes256(k))
+
+ val current = config.current.toLowerCase match {
+ case "" | "off" | NO_ENCRYPTION => NO_ENCRYPTION
+ case s => s
+ }
+
+ val defaultEncoder: Encrypter = availableEncoders.get(current).getOrElse(noop)
+ new ParameterEncryption(Option(defaultEncoder).filter(_ != noop), availableEncoders)
}
}
-private trait encrypter {
- def encrypt(p: ParameterValue): ParameterValue
- def decrypt(p: ParameterValue): ParameterValue
+protected[core] trait Encrypter {
val name: String
+ def encrypt(p: ParameterValue): ParameterValue = p
+ def decrypt(p: ParameterValue): ParameterValue = p
+ def decrypt(v: JsString): JsValue = v
}
-private class encrypters(val storageConfig: ParameterStorageConfig) {
- private val availableEncrypters = Map("" -> new NoopCrypt()) ++
- (if (!storageConfig.aes256.isEmpty) Some(Aes256.name -> new Aes256(getKeyBytes(storageConfig.aes256))) else None) ++
- (if (!storageConfig.aes128.isEmpty) Some(Aes128.name -> new Aes128(getKeyBytes(storageConfig.aes128))) else None)
-
- protected[entity] def getCurrentEncrypter(): encrypter = {
- availableEncrypters.get(ParameterEncryption.storageConfig.current).get
- }
- protected[entity] def getEncrypter(name: String) = {
- availableEncrypters.get(name)
- }
-
- def getKeyBytes(key: String): Array[Byte] = {
+protected[core] object Encrypter {
+ protected[entity] def getKeyBytes(key: String): Array[Byte] = {
if (key.length == 0) {
- Array[Byte]()
+ Array.empty
} else {
- Base64.getDecoder().decode(key)
+ Base64.getDecoder.decode(key)
}
}
}
-private trait AesEncryption extends encrypter {
+protected[core] trait AesEncryption extends Encrypter {
val key: Array[Byte]
val ivLen: Int
val name: String
private val tLen = 128
- private val secretKey = new SecretKeySpec(key, "AES")
-
private val secureRandom = new SecureRandom()
+ private lazy val secretKey = new SecretKeySpec(key, "AES")
- def encrypt(value: ParameterValue): ParameterValue = {
+ override def encrypt(value: ParameterValue): ParameterValue = {
val iv = new Array[Byte](ivLen)
secureRandom.nextBytes(iv)
val gcmSpec = new GCMParameterSpec(tLen, iv)
@@ -112,11 +118,18 @@
byteBuffer.put(iv)
byteBuffer.put(cipherText)
val cipherMessage = byteBuffer.array
- ParameterValue(JsString(Base64.getEncoder.encodeToString(cipherMessage)), value.init, Some(JsString(name)))
+ ParameterValue(JsString(Base64.getEncoder.encodeToString(cipherMessage)), value.init, Some(name))
}
- def decrypt(value: ParameterValue): ParameterValue = {
- val cipherMessage = value.value.convertTo[String].getBytes(StandardCharsets.UTF_8)
+ override def decrypt(p: ParameterValue): ParameterValue = {
+ p.value match {
+ case s: JsString => p.copy(v = decrypt(s), encryption = None)
+ case _ => p
+ }
+ }
+
+ override def decrypt(value: JsString): JsValue = {
+ val cipherMessage = value.convertTo[String].getBytes(StandardCharsets.UTF_8)
val byteBuffer = ByteBuffer.wrap(Base64.getDecoder.decode(cipherMessage))
val ivLength = byteBuffer.getInt
if (ivLength != ivLen) {
@@ -132,32 +145,19 @@
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmSpec)
val plainTextBytes = cipher.doFinal(cipherText)
val plainText = new String(plainTextBytes, StandardCharsets.UTF_8)
- ParameterValue(plainText.parseJson, value.init)
+ plainText.parseJson
}
}
-private object Aes128 {
- val name: String = "aes-128"
+protected[core] class Aes128(val k: String) extends AesEncryption with Encrypter {
+ override val key = Encrypter.getKeyBytes(k)
+ override val name = ParameterEncryption.AES128_ENCRYPTION
+ override val ivLen = 12
}
-private case class Aes128(val key: Array[Byte], val ivLen: Int = 12, val name: String = Aes128.name)
- extends AesEncryption
- with encrypter
-private object Aes256 {
- val name: String = "aes-256"
-}
-private case class Aes256(val key: Array[Byte], val ivLen: Int = 128, val name: String = Aes256.name)
- extends AesEncryption
- with encrypter
-
-private class NoopCrypt extends encrypter {
- val name = ""
- def encrypt(p: ParameterValue): ParameterValue = {
- p
- }
-
- def decrypt(p: ParameterValue): ParameterValue = {
- p
- }
+protected[core] class Aes256(val k: String) extends AesEncryption with Encrypter {
+ override val key = Encrypter.getKeyBytes(k)
+ override val name = ParameterEncryption.AES256_ENCRYPTION
+ override val ivLen = 128
}
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala
index 6dd60a1..4370a17 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala
@@ -348,6 +348,7 @@
object WhiskAction extends DocumentFactory[WhiskAction] with WhiskEntityQueries[WhiskAction] with DefaultJsonProtocol {
import WhiskActivation.instantSerdes
+
val execFieldName = "exec"
val requireWhiskAuthHeader = "x-require-whisk-auth"
@@ -384,7 +385,9 @@
val stream = new ByteArrayInputStream(bytes)
super.putAndAttach(
db,
- doc.copy(parameters = ParameterEncryption.lock(doc.parameters)).revision[WhiskAction](doc.rev),
+ doc
+ .copy(parameters = doc.parameters.lock(ParameterEncryption.singleton.default))
+ .revision[WhiskAction](doc.rev),
attachmentUpdater,
attachmentType,
stream,
@@ -406,7 +409,9 @@
case _ =>
super.put(
db,
- doc.copy(parameters = ParameterEncryption.lock(doc.parameters)).revision[WhiskAction](doc.rev),
+ doc
+ .copy(parameters = doc.parameters.lock(ParameterEncryption.singleton.default))
+ .revision[WhiskAction](doc.rev),
old)
}
} match {
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala
index ec9e0ec..3acac54 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala
@@ -198,14 +198,19 @@
}
jsonFormat8(WhiskPackage.apply)
}
+
override val cacheEnabled = true
lazy val publicPackagesView: View = WhiskQueries.entitiesView(collection = s"$collectionName-public")
+
// overriden to store encrypted parameters.
override def put[A >: WhiskPackage](db: ArtifactStore[A], doc: WhiskPackage, old: Option[WhiskPackage])(
implicit transid: TransactionId,
notifier: Option[CacheChangeNotification]): Future[DocInfo] = {
- super.put(db, doc.copy(parameters = ParameterEncryption.lock(doc.parameters)).revision[WhiskPackage](doc.rev), old)
+ super.put(
+ db,
+ doc.copy(parameters = doc.parameters.lock(ParameterEncryption.singleton.default)).revision[WhiskPackage](doc.rev),
+ old)
}
}
diff --git a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/PrimitiveActions.scala b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/PrimitiveActions.scala
index 1ac1151..621a10e 100644
--- a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/PrimitiveActions.scala
+++ b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/PrimitiveActions.scala
@@ -179,6 +179,7 @@
waitForResponse.isDefined,
args,
action.parameters.initParameters,
+ action.parameters.lockedParameters(payload.map(_.fields.keySet).getOrElse(Set.empty)),
cause = cause,
WhiskTracerProvider.tracer.getTraceContext(transid))
diff --git a/core/controller/src/main/scala/org/apache/openwhisk/core/loadBalancer/InvokerSupervision.scala b/core/controller/src/main/scala/org/apache/openwhisk/core/loadBalancer/InvokerSupervision.scala
index e1b7c64..5a9d367 100644
--- a/core/controller/src/main/scala/org/apache/openwhisk/core/loadBalancer/InvokerSupervision.scala
+++ b/core/controller/src/main/scala/org/apache/openwhisk/core/loadBalancer/InvokerSupervision.scala
@@ -416,7 +416,8 @@
rootControllerIndex = controllerInstance,
blocking = false,
content = None,
- initArgs = Set.empty)
+ initArgs = Set.empty,
+ lockedArgs = Map.empty)
context.parent ! ActivationRequest(activationMessage, invokerInstance)
}
diff --git a/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/ContainerProxy.scala b/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/ContainerProxy.scala
index eebcc7c..d84947b 100644
--- a/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/ContainerProxy.scala
+++ b/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/ContainerProxy.scala
@@ -754,6 +754,7 @@
}
hpa ! HealthPingEnabled(true)
}
+
private def disableHealthPing() = {
healthPingActor.foreach(_ ! HealthPingEnabled(false))
}
@@ -774,14 +775,10 @@
def initializeAndRun(container: Container, job: Run, reschedule: Boolean = false)(
implicit tid: TransactionId): Future[WhiskActivation] = {
val actionTimeout = job.action.limits.timeout.duration
- val unlockedContent = job.msg.content match {
- case Some(js) => {
- Some(ParameterEncryption.unlock(Parameters.readMergedList(js)).toJsObject)
- }
- case _ => job.msg.content
- }
+ val unlockedArgs =
+ ContainerProxy.unlockArguments(job.msg.content, job.msg.lockedArgs, ParameterEncryption.singleton)
- val (env, parameters) = ContainerProxy.partitionArguments(unlockedContent, job.msg.initArgs)
+ val (env, parameters) = ContainerProxy.partitionArguments(unlockedArgs, job.msg.initArgs)
val environment = Map(
"namespace" -> job.msg.user.namespace.name.toJson,
@@ -1094,6 +1091,18 @@
(env, JsObject(args))
}
}
+
+ def unlockArguments(content: Option[JsObject],
+ lockedArgs: Map[String, String],
+ decoder: ParameterEncryption): Option[JsObject] = {
+ content.map {
+ case JsObject(fields) =>
+ JsObject(fields.map {
+ case (k, v: JsString) if lockedArgs.contains(k) => (k -> decoder.encryptor(lockedArgs(k)).decrypt(v))
+ case p => p
+ })
+ }
+ }
}
object TCPPingClient {
diff --git a/tests/src/test/resources/application.conf.j2 b/tests/src/test/resources/application.conf.j2
index 5f61695..e2eaa0f 100644
--- a/tests/src/test/resources/application.conf.j2
+++ b/tests/src/test/resources/application.conf.j2
@@ -95,7 +95,7 @@
}
parameter-storage {
- key = ""
+ current = "off"
}
elasticsearch {
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerPoolTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerPoolTests.scala
index 8c2fea5..63930fe 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerPoolTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerPoolTests.scala
@@ -87,7 +87,8 @@
ControllerInstanceId("0"),
blocking = false,
content = None,
- initArgs = Set.empty)
+ initArgs = Set.empty,
+ lockedArgs = Map.empty)
Run(action, message)
}
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerProxyTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerProxyTests.scala
index 968a5ea..dc8a33f 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerProxyTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerProxyTests.scala
@@ -125,7 +125,8 @@
ControllerInstanceId("0"),
blocking = false,
content = Some(activationArguments),
- initArgs = Set("ENV_VAR"))
+ initArgs = Set("ENV_VAR"),
+ lockedArgs = Map.empty)
/*
* Helpers for assertions and actor lifecycles
@@ -296,6 +297,18 @@
}
}
+ it should "unlock arguments" in {
+ val k128 = "ra1V6AfOYAv0jCzEdufIFA=="
+ val coder = ParameterEncryption(ParameterStorageConfig("aes-128", aes128 = Some(k128)))
+ val locker = Some(coder.encryptor("aes-128"))
+
+ val param = Parameters("a", "abc").lock(locker).merge(Some(JsObject("b" -> JsString("xyz"))))
+ param.get.compactPrint should not include "abc"
+ ContainerProxy.unlockArguments(param, Map("a" -> "aes-128"), coder) shouldBe Some {
+ JsObject("a" -> JsString("abc"), "b" -> JsString("xyz"))
+ }
+ }
+
/*
* SUCCESSFUL CASES
*/
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala
index 3917122..9a1a96e 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala
@@ -19,28 +19,29 @@
import java.time.Instant
-import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport.{sprayJsonMarshaller, sprayJsonUnmarshaller}
-import akka.http.scaladsl.model.StatusCodes._
-import akka.http.scaladsl.model.headers.RawHeader
-import akka.http.scaladsl.server.Route
-import org.apache.commons.lang3.StringUtils
-import org.apache.openwhisk.core.connector.ActivationMessage
-import org.apache.openwhisk.core.controller.WhiskActionsApi
-import org.apache.openwhisk.core.database.UserContext
-import org.apache.openwhisk.core.entitlement.Collection
-import org.apache.openwhisk.core.entity.Attachments.Inline
-import org.apache.openwhisk.core.entity._
-import org.apache.openwhisk.core.entity.size._
-import org.apache.openwhisk.core.entity.test.ExecHelpers
-import org.apache.openwhisk.http.{ErrorResponse, Messages}
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-import org.scalatest.{FlatSpec, Matchers}
-import spray.json.DefaultJsonProtocol._
-import spray.json._
-
import scala.concurrent.duration.DurationInt
import scala.language.postfixOps
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+import akka.http.scaladsl.model.StatusCodes._
+import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport.sprayJsonMarshaller
+import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport.sprayJsonUnmarshaller
+import akka.http.scaladsl.server.Route
+import spray.json._
+import spray.json.DefaultJsonProtocol._
+import org.apache.openwhisk.core.controller.WhiskActionsApi
+import org.apache.openwhisk.core.entity._
+import org.apache.openwhisk.core.entity.size._
+import org.apache.openwhisk.core.entitlement.Collection
+import org.apache.openwhisk.http.ErrorResponse
+import org.apache.openwhisk.http.Messages
+import org.apache.openwhisk.core.database.UserContext
+import akka.http.scaladsl.model.headers.RawHeader
+import org.apache.commons.lang3.StringUtils
+import org.apache.openwhisk.core.connector.ActivationMessage
+import org.apache.openwhisk.core.entity.Attachments.Inline
+import org.apache.openwhisk.core.entity.test.ExecHelpers
+import org.scalatest.{FlatSpec, Matchers}
/**
* Tests Actions API.
@@ -223,22 +224,22 @@
}
}
-// it should "ignore updated field when updating action" in {
-// implicit val tid = transid()
-//
-// val action = WhiskAction(namespace, aname(), jsDefault(""))
-// val dummyUpdated = WhiskEntity.currentMillis().toEpochMilli
-//
-// val content = JsObject(
-// "exec" -> JsObject("code" -> "".toJson, "kind" -> action.exec.kind.toJson),
-// "updated" -> dummyUpdated.toJson)
-//
-// Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)) ~> check {
-// status should be(OK)
-// val response = responseAs[WhiskAction]
-// response.updated.toEpochMilli should be > dummyUpdated
-// }
-// }
+ it should "ignore updated field when updating action" in {
+ implicit val tid = transid()
+
+ val action = WhiskAction(namespace, aname(), jsDefault(""))
+ val dummyUpdated = WhiskEntity.currentMillis().toEpochMilli
+
+ val content = JsObject(
+ "exec" -> JsObject("code" -> "".toJson, "kind" -> action.exec.kind.toJson),
+ "updated" -> dummyUpdated.toJson)
+
+ Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)) ~> check {
+ status should be(OK)
+ val response = responseAs[WhiskAction]
+ response.updated.toEpochMilli should be > dummyUpdated
+ }
+ }
def getExecPermutations() = {
implicit val tid = transid()
@@ -1702,9 +1703,9 @@
@RunWith(classOf[JUnitRunner])
class WhiskActionsApiTests extends FlatSpec with Matchers with ExecHelpers {
+ import WhiskActionsApi.amendAnnotations
import Annotations.ProvideApiKeyAnnotationName
import WhiskAction.execFieldName
- import WhiskActionsApi.amendAnnotations
val baseParams = Parameters("a", JsString("A")) ++ Parameters("b", JsString("B"))
val keyTruthyAnnotation = Parameters(ProvideApiKeyAnnotationName, JsTrue)
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/ParameterEncryptionTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/ParameterEncryptionTests.scala
index c6aec18..8fe2a5d 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/ParameterEncryptionTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/ParameterEncryptionTests.scala
@@ -28,223 +28,235 @@
@RunWith(classOf[JUnitRunner])
class ParameterEncryptionTests extends FlatSpec with Matchers with BeforeAndAfter {
- after {
- ParameterEncryption.storageConfig = new ParameterStorageConfig("")
- }
+ 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 complex objects in param body" in {
- val input =
- """
- |{
- | "__ow_headers": {
- | "accept": "*/*",
- | "accept-encoding": "gzip, deflate",
- | "host": "controllers",
- | "user-agent": "Apache-HttpClient/4.5.5 (Java/1.8.0_212)",
- | "x-request-id": "fd2263668266da5a5433109076191d95"
- | },
- | "__ow_method": "get",
- | "__ow_path": "/a",
- | "a": "A"
- |}
- |""".stripMargin
- val ps = Parameters.readMergedList(input.parseJson)
- ps.get("a").get.convertTo[String] shouldBe "A"
- }
- it should "handle decryption json objects" in {
+ it should "handle decryption of json objects" in {
val originalValue =
"""
- |{
- |"paramName1":{"encryption":null,"init":false,"value":"from-action"},
- |"paramName2":{"encryption":null,"init":false,"value":"from-pack"}
- |}
+ |[
+ | { "key": "paramName1", "init": false, "value": "from-action" },
+ | { "key": "paramName2", "init": false, "value": "from-pack" }
+ |]
|""".stripMargin
- val ps = Parameters.serdes.read(originalValue.parseJson)
- ps.get("paramName1").get.convertTo[String] shouldBe "from-action"
- ps.get("paramName2").get.convertTo[String] shouldBe "from-pack"
+
+ 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 payload when no longer encrypted" in {
+ it should "handle decryption of json objects with null field" in {
val originalValue =
"""
- |{
- |"paramName1":{"encryption":null,"init":false,"value":"from-action"},
- |"paramName2":{"encryption":null,"init":false,"value":"from-action"}
- |}
+ |[
+ | { "key": "paramName1", "encryption":null, "init": false, "value": "from-action" },
+ | { "key": "paramName2", "encryption":null, "init": false, "value": "from-pack" }
+ |]
|""".stripMargin
- val ps = Parameters.serdes.read(originalValue.parseJson)
- val o = ps.toJsObject
- o.fields.map((tuple: (String, JsValue)) => {
- tuple._2.convertTo[String] shouldBe "from-action"
- })
+
+ 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 "read the merged unencrypted parameters during mixed storage" in {
+ it should "drop encryption propery when no longer encrypted" in {
val originalValue =
"""
- |{"name":"from-action","other":"from-action"}
+ |[
+ | { "key": "paramName1", "encryption":null, "init": false, "value": "from-action" },
+ | { "key": "paramName2", "encryption":null, "init": false, "value": "from-pack" }
+ |]
|""".stripMargin
- val ps = Parameters.readMergedList(originalValue.parseJson)
- val o = ps.toJsObject
- o.fields.map((tuple: (String, JsValue)) => {
- tuple._2.convertTo[String] shouldBe "from-action"
- })
+
+ 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 {
- ParameterEncryption.storageConfig = new ParameterStorageConfig("aes-128", "ra1V6AfOYAv0jCzEdufIFA==")
- val locked = ParameterEncryption.lock(parameters)
-
- val unlockedParam = new ParameterValue(JsString("test-plain"), false)
- val mixedParams =
- locked.merge(Some((new Parameters(Map.empty) + (new ParameterName("plain") -> unlockedParam)).toJsObject))
- val params = Parameters.readMergedList(mixedParams.get)
- params.get("one").get shouldBe locked.get("one").get
- params.get("two").get shouldBe locked.get("two").get
- params.get("two").get should not be locked.get("one").get
- params.get("plain").get shouldBe JsString("test-plain")
+ 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 {
- ParameterEncryption.storageConfig = new ParameterStorageConfig("aes-128", "ra1V6AfOYAv0jCzEdufIFA==")
- val locked = ParameterEncryption.lock(parameters)
- locked.getMap.map(({
+ val locked = parameters.lock(aes128encoder)
+
+ locked.params.foreach {
case (_, paramValue) =>
- paramValue.encryption.convertTo[String] shouldBe "aes-128"
+ paramValue.encryption shouldBe Some("aes-128")
paramValue.value.convertTo[String] should not be "secret"
- }))
+ }
}
it should "serialize to json correctly" in {
- val output =
- """\Q{"one":{"encryption":"aes-128","init":false,"value":"\E.*\Q"},"two":{"encryption":"aes-128","init":true,"value":"\E.*\Q"}}""".stripMargin.r
- ParameterEncryption.storageConfig = new ParameterStorageConfig("aes-128", "ra1V6AfOYAv0jCzEdufIFA==")
- val locked = ParameterEncryption.lock(parameters)
- val dbString = locked.toJsObject.toString
- dbString should fullyMatch regex output
+ 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 "correctly decrypted encrypted values" in {
- ParameterEncryption.storageConfig = new ParameterStorageConfig("aes-128", "ra1V6AfOYAv0jCzEdufIFA==")
- val locked = ParameterEncryption.lock(parameters)
- locked.getMap.map(({
+ 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.convertTo[String] shouldBe "aes-128"
+ paramValue.encryption shouldBe Some("aes-128")
paramValue.value.convertTo[String] should not be "secret"
- }))
+ }
- val unlocked = ParameterEncryption.unlock(locked)
- unlocked.getMap.map(({
+ val unlocked = locked.unlock(aes128decoder)
+ unlocked.params.foreach {
case (_, paramValue) =>
- paramValue.encryption shouldBe JsNull
+ paramValue.encryption shouldBe empty
paramValue.value.convertTo[String] shouldBe "secret"
- }))
+ }
}
- it should "correctly decrypted encrypted JsObject values" in {
- ParameterEncryption.storageConfig = new ParameterStorageConfig("aes-128", "ra1V6AfOYAv0jCzEdufIFA==")
- val obj = Map("key" -> "xyz".toJson, "value" -> "v1".toJson).toJson
+ 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 = ParameterEncryption.lock(complexParam)
- locked.getMap.map(({
+ val locked = complexParam.lock(aes128encoder)
+ locked.params.foreach {
case (_, paramValue) =>
- paramValue.encryption.convertTo[String] shouldBe "aes-128"
+ paramValue.encryption shouldBe Some("aes-128")
paramValue.value.convertTo[String] should not be "secret"
- }))
+ }
- val unlocked = ParameterEncryption.unlock(locked)
- unlocked.getMap.map(({
+ val unlocked = locked.unlock(aes128decoder)
+ unlocked.params.foreach {
case (_, paramValue) =>
- paramValue.encryption shouldBe JsNull
+ paramValue.encryption shouldBe empty
paramValue.value shouldBe obj
- }))
+ }
}
- it should "correctly decrypted encrypted multiline values" in {
- ParameterEncryption.storageConfig = new ParameterStorageConfig("aes-128", "ra1V6AfOYAv0jCzEdufIFA==")
+ 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 = ParameterEncryption.lock(multiline)
- locked.getMap.map(({
+ val locked = multiline.lock(aes128encoder)
+ locked.params.foreach {
case (_, paramValue) =>
- paramValue.encryption.convertTo[String] shouldBe "aes-128"
+ paramValue.encryption shouldBe Some("aes-128")
paramValue.value.convertTo[String] should not be "secret"
- }))
+ }
- val unlocked = ParameterEncryption.unlock(locked)
- unlocked.getMap.map(({
+ val unlocked = locked.unlock(aes128decoder)
+ unlocked.params.foreach {
case (_, paramValue) =>
- paramValue.encryption shouldBe JsNull
+ 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 {
- ParameterEncryption.storageConfig =
- new ParameterStorageConfig("aes-256", "", "j5rLzhtxwzPyUVUy8/p8XJmBoKeDoSzNJP1SITJEY9E=")
try {
- val locked = ParameterEncryption.lock(parameters)
- locked.getMap.map(({
+ val locked = parameters.lock(aes256encoder)
+ locked.params.foreach {
case (_, paramValue) =>
- paramValue.encryption.convertTo[String] shouldBe "aes-256"
+ paramValue.encryption shouldBe Some("aes-256")
paramValue.value.convertTo[String] should not be "secret"
- }))
+ }
- val unlocked = ParameterEncryption.unlock(locked)
- unlocked.getMap.map(({
+ val unlocked = locked.unlock(noop)
+ unlocked.params.foreach {
case (_, paramValue) =>
- paramValue.encryption shouldBe JsNull
+ 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 {
- ParameterEncryption.storageConfig = new ParameterStorageConfig("aes-128", "ra1V6AfOYAv0jCzEdufIFA==", "")
try {
- val locked = ParameterEncryption.lock(parameters)
- locked.getMap.map(({
+ val locked = parameters.lock(aes128encoder)
+ locked.params.foreach {
case (_, paramValue) =>
- paramValue.encryption.convertTo[String] shouldBe "aes-128"
+ paramValue.encryption shouldBe Some("aes-128")
paramValue.value.convertTo[String] should not be "secret"
- }))
+ }
- val lockedJson = locked.toJsObject
+ val lockedJson = Parameters.serdes.write(locked).compactPrint
+ val toDecrypt = Parameters.serdes.read(lockedJson.parseJson)
- ParameterEncryption.storageConfig = new ParameterStorageConfig("", "ra1V6AfOYAv0jCzEdufIFA==", "")
-
- val toDecrypt = Parameters.serdes.read(lockedJson)
-
- val unlocked = ParameterEncryption.unlock(toDecrypt)
- unlocked.getMap.map(({
+ // defaults to no-op
+ val unlocked = toDecrypt.unlock(noop)
+ unlocked.params.foreach {
case (_, paramValue) =>
- paramValue.encryption shouldBe JsNull
+ paramValue.encryption shouldBe empty
paramValue.value.convertTo[String] shouldBe "secret"
- }))
- unlocked.toJsObject should not be JsNull
+ }
+
+ unlocked.toJsObject shouldBe JsObject("one" -> "secret".toJson, "two" -> "secret".toJson)
} catch {
case e: InvalidAlgorithmParameterException =>
cancel(e.toString)
}
}
- behavior of "NoopEncryption"
+ behavior of "No-op Encryption"
+
it should "not mark parameters as encrypted" in {
- val locked = ParameterEncryption.lock(parameters)
- locked.getMap.map(({
+ val locked = parameters.lock()
+ locked.params.foreach {
case (_, paramValue) =>
paramValue.value.convertTo[String] shouldBe "secret"
- }))
+ }
}
}
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala
index 7b6cad4..ccab9df 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala
@@ -695,7 +695,7 @@
val json = Seq[JsValue](
JsArray(JsObject("key" -> "k".toJson, "value" -> "v".toJson)),
JsArray(JsObject("key" -> "k".toJson, "value" -> "v".toJson, "init" -> JsFalse)),
- JsArray(JsObject("key" -> "k".toJson, "value" -> "v".toJson, "init" -> JsTrue)))
+ JsArray(JsObject(Map("key" -> "k".toJson, "value" -> "v".toJson, "init" -> JsTrue))))
val params = json.map { p =>
Parameters.serdes.read(p)
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/loadBalancer/test/InvokerSupervisionTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/loadBalancer/test/InvokerSupervisionTests.scala
index aa07914..1cf7383 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/loadBalancer/test/InvokerSupervisionTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/loadBalancer/test/InvokerSupervisionTests.scala
@@ -195,7 +195,8 @@
rootControllerIndex = ControllerInstanceId("0"),
blocking = false,
content = None,
- initArgs = Set.empty)
+ initArgs = Set.empty,
+ lockedArgs = Map.empty)
val msg = ActivationRequest(activationMessage, invokerInstance)
supervisor ! msg
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/loadBalancer/test/ShardingContainerPoolBalancerTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/loadBalancer/test/ShardingContainerPoolBalancerTests.scala
index dab21aa..11fee6d 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/loadBalancer/test/ShardingContainerPoolBalancerTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/loadBalancer/test/ShardingContainerPoolBalancerTests.scala
@@ -508,7 +508,8 @@
ControllerInstanceId("0"),
blocking = false,
content = None,
- initArgs = Set.empty)
+ initArgs = Set.empty,
+ lockedArgs = Map.empty)
//send activation to loadbalancer
aid -> balancer.publish(actionMetaData.toExecutableWhiskAction.get, msg)