diff --git a/ansible/group_vars/all b/ansible/group_vars/all index 0c54c92243f..342ad1d7938 100644 --- a/ansible/group_vars/all +++ b/ansible/group_vars/all @@ -49,8 +49,8 @@ whisk: 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 50a748a5db5..ea96e92709a 100644 --- a/common/scala/src/main/resources/application.conf +++ b/common/scala/src/main/resources/application.conf @@ -567,13 +567,17 @@ whisk { # 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 5ca2a71be8c..c86426d4894 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 @@ case class ActivationMessage(override val transid: TransactionId, 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 @@ object ActivationMessage extends DefaultJsonProtocol { 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 89494fd7351..4a66867903e 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 @@ import scala.util.{Failure, Success, Try} * @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 @@ protected[core] class Parameters protected[entity] (private val params: Map[Para .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 @@ protected[core] class Parameters protected[entity] (private val params: Map[Para 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 @@ protected[core] class Parameters protected[entity] (private val params: Map[Para 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 @@ protected[entity] class ParameterName protected[entity] (val name: String) exten * * @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 @@ protected[entity] case class ParameterValue protected[entity] (private val v: Js /** @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 @@ protected[core] object Parameters extends ArgNormalizer[Parameters] { * 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 @@ protected[core] object Parameters extends ArgNormalizer[Parameters] { /** * 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 @@ protected[core] object Parameters extends ArgNormalizer[Parameters] { 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 @@ protected[core] object Parameters extends ArgNormalizer[Parameters] { * @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 @@ protected[core] object Parameters extends ArgNormalizer[Parameters] { * @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 482579cbad2..2169eaf3ff6 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 javax.crypto.spec.{GCMParameterSpec, SecretKeySpec} 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 - }))) - } - 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] 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) } -} -private trait encrypter { - def encrypt(p: ParameterValue): ParameterValue - def decrypt(p: ParameterValue): ParameterValue - val name: String } -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[core] object ParameterEncryption { - protected[entity] def getCurrentEncrypter(): encrypter = { - availableEncrypters.get(ParameterEncryption.storageConfig.current).get + 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) } - protected[entity] def getEncrypter(name: String) = { - availableEncrypters.get(name) + + 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) } +} - def getKeyBytes(key: String): Array[Byte] = { +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 +} + +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 @@ private trait AesEncryption extends encrypter { 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 @@ private trait AesEncryption extends encrypter { 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" -} -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" +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 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 6dd60a1f12a..4370a1781e3 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 @@ case class ExecutableWhiskActionMetaData(namespace: EntityPath, 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 @@ object WhiskAction extends DocumentFactory[WhiskAction] with WhiskEntityQueries[ 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 @@ object WhiskAction extends DocumentFactory[WhiskAction] with WhiskEntityQueries[ 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 ec9e0ecadb9..3acac54c080 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 @@ object WhiskPackage } 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 1ac11515bc8..621a10e2f19 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 @@ protected[actions] trait PrimitiveActions { 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 e1b7c64e548..5a9d36707db 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 @@ class InvokerActor(invokerInstance: InvokerInstanceId, controllerInstance: Contr 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 eebcc7c9f95..d84947b5f5b 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 @@ class ContainerProxy(factory: (TransactionId, } hpa ! HealthPingEnabled(true) } + private def disableHealthPing() = { healthPingActor.foreach(_ ! HealthPingEnabled(false)) } @@ -774,14 +775,10 @@ class ContainerProxy(factory: (TransactionId, 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 @@ object ContainerProxy { (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 5f61695a559..e2eaa0f36de 100644 --- a/tests/src/test/resources/application.conf.j2 +++ b/tests/src/test/resources/application.conf.j2 @@ -95,7 +95,7 @@ whisk { } 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 8c2fea5f17a..63930fef799 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 @@ class ContainerPoolTests 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 968a5ea36bb..dc8a33f0ee3 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 @@ class ContainerProxyTests 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 @@ class ContainerProxyTests } } + 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 391712227f8..9a1a96e7227 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 @@ package org.apache.openwhisk.core.controller.test import java.time.Instant -import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport.{sprayJsonMarshaller, sprayJsonUnmarshaller} +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.model.headers.RawHeader +import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport.sprayJsonMarshaller +import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport.sprayJsonUnmarshaller import akka.http.scaladsl.server.Route -import org.apache.commons.lang3.StringUtils -import org.apache.openwhisk.core.connector.ActivationMessage +import spray.json._ +import spray.json.DefaultJsonProtocol._ 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.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.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 /** * Tests Actions API. @@ -223,22 +224,22 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { } } -// 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 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi { @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 c6aec18c3a0..8fe2a5d51a8 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 @@ import spray.json._ @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 "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" + 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 } - it should "handle decryption json objects" in { + behavior of "Parameters" + + 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 7b6cad4c1d9..ccab9dff6cc 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 @@ class SchemaTests extends FlatSpec with BeforeAndAfter with ExecHelpers with Mat 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 aa07914588e..1cf73834b82 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 @@ class InvokerSupervisionTests 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 dab21aa0e9a..11fee6d002b 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 @@ class ShardingContainerPoolBalancerTests 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)