Skip to content

Commit

Permalink
Merge pull request #502 from megha070/column
Browse files Browse the repository at this point in the history
Update exchange resources to support secure application secrets #498
  • Loading branch information
sf2ne committed May 17, 2021
2 parents 2961b67 + 7f25063 commit dfa65f3
Show file tree
Hide file tree
Showing 15 changed files with 208 additions and 80 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,11 @@ Now you can disable root by setting `api.root.enabled` to `false` in `/etc/horiz
- detect if pattern contains 2 services that depend on the same exclusive MS
- detect if a pattern is updated with service that has userInput w/o default values, and give warning
- Consider changing all creates to POST, and update (via put/patch) return codes to 200



## Changes in 2.73.0

- Issue 491: Updated the service definition, deployment policy and pattern definitions to support vault based secrets to be used with Open Horizon.

## Changes in 2.72.0
- Issue 259: Added TLS support to the Exchange.
- Updated Akka: 2.6.10 -> 2.6.14.
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.72.0
2.73.0
58 changes: 51 additions & 7 deletions src/main/scala/com/horizon/exchangeapi/BusinessRoutes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ object BusinessUtils {
}

/** Input format for POST/PUT /orgs/{orgid}/business/policies/<bus-pol-id> */
final case class PostPutBusinessPolicyRequest(label: String, description: Option[String], service: BService, userInput: Option[List[OneUserInputService]], properties: Option[List[OneProperty]], constraints: Option[List[String]]) {
final case class PostPutBusinessPolicyRequest(label: String, description: Option[String], service: BService, userInput: Option[List[OneUserInputService]],secretBinding: Option[List[OneSecretBindingService]], properties: Option[List[OneProperty]], constraints: Option[List[String]]) {
require(label!=null && service!=null && service.name!=null && service.org!=null && service.arch!=null && service.serviceVersions!=null)
protected implicit val jsonFormats: Formats = DefaultFormats

Expand All @@ -70,15 +70,15 @@ final case class PostPutBusinessPolicyRequest(label: String, description: Option

// Note: write() handles correctly the case where the optional fields are None.
def getDbInsert(businessPolicy: String, orgid: String, owner: String): DBIO[_] = {
BusinessPolicyRow(businessPolicy, orgid, owner, label, description.getOrElse(label), write(defaultNodeHealth(service)), write(userInput), write(properties), write(constraints), ApiTime.nowUTC, ApiTime.nowUTC).insert
BusinessPolicyRow(businessPolicy, orgid, owner, label, description.getOrElse(label), write(defaultNodeHealth(service)), write(userInput),write(secretBinding), write(properties), write(constraints), ApiTime.nowUTC, ApiTime.nowUTC).insert
}

def getDbUpdate(businessPolicy: String, orgid: String, owner: String): DBIO[_] = {
BusinessPolicyRow(businessPolicy, orgid, owner, label, description.getOrElse(label), write(defaultNodeHealth(service)), write(userInput), write(properties), write(constraints), ApiTime.nowUTC, "").update
BusinessPolicyRow(businessPolicy, orgid, owner, label, description.getOrElse(label), write(defaultNodeHealth(service)), write(userInput),write(secretBinding),write(properties), write(constraints), ApiTime.nowUTC, "").update
}
}

final case class PatchBusinessPolicyRequest(label: Option[String], description: Option[String], service: Option[BService], userInput: Option[List[OneUserInputService]], properties: Option[List[OneProperty]], constraints: Option[List[String]]) {
final case class PatchBusinessPolicyRequest(label: Option[String], description: Option[String], service: Option[BService], userInput: Option[List[OneUserInputService]],secretBinding:Option[List[OneSecretBindingService]] , properties: Option[List[OneProperty]], constraints: Option[List[String]]) {
protected implicit val jsonFormats: Formats = DefaultFormats

def getAnyProblem: Option[String] = {
Expand Down Expand Up @@ -168,6 +168,7 @@ trait BusinessRoutes extends JacksonSupport with AuthenticationSupport {
}
},
"userInput": [],
"secretBinding": [],
"properties": [
{
"name": "string",
Expand Down Expand Up @@ -253,6 +254,7 @@ trait BusinessRoutes extends JacksonSupport with AuthenticationSupport {
}
},
"userInput": [],
"secretBinding": [],
"properties": [
{
"name": "string",
Expand Down Expand Up @@ -370,7 +372,7 @@ trait BusinessRoutes extends JacksonSupport with AuthenticationSupport {
"serviceOrgid": "IBM",
"serviceUrl": "ibm.cpu2msghub",
"serviceArch": "", // omit or leave blank to mean all architectures
"serviceVersionRange": "[0.0.0,INFINITY)", // or omit to mean all versions
"serviceVersionRange": "x.y.z", // or omit to mean all versions
"inputs": [
{
"name": "foo",
Expand All @@ -379,6 +381,20 @@ trait BusinessRoutes extends JacksonSupport with AuthenticationSupport {
]
}
],
"secretBinding": [
{
"serviceOrgid": "string",
"serviceUrl": "string",
"serviceArch": "amd64",
"serviceVersionRange": "x.y.z",
"secrets": [
{
"FirstSecret": "secret1"
"Foo": "Bar"
}
]
},
],
"properties": [
{
"name": "mypurpose",
Expand Down Expand Up @@ -534,7 +550,7 @@ trait BusinessRoutes extends JacksonSupport with AuthenticationSupport {
"serviceOrgid": "IBM",
"serviceUrl": "ibm.cpu2msghub",
"serviceArch": "", // omit or leave blank to mean all architectures
"serviceVersionRange": "[0.0.0,INFINITY)", // or omit to mean all versions
"serviceVersionRange": "x.y.z", // or omit to mean all versions
"inputs": [
{
"name": "foo",
Expand All @@ -543,6 +559,20 @@ trait BusinessRoutes extends JacksonSupport with AuthenticationSupport {
]
}
],
"secretBinding": [
{
"serviceOrgid": "string",
"serviceUrl": "string",
"serviceArch": "amd64",
"serviceVersionRange": "x.y.z",
"secrets": [
{
"FirstSecret": "secret1"
"Foo": "Bar"
}
]
},
],
"properties": [
{
"name": "mypurpose",
Expand Down Expand Up @@ -673,7 +703,7 @@ trait BusinessRoutes extends JacksonSupport with AuthenticationSupport {
"serviceOrgid": "IBM",
"serviceUrl": "ibm.cpu2msghub",
"serviceArch": "", // omit or leave blank to mean all architectures
"serviceVersionRange": "[0.0.0,INFINITY)", // or omit to mean all versions
"serviceVersionRange": "x.y.z", // or omit to mean all versions
"inputs": [
{
"name": "foo",
Expand All @@ -682,6 +712,20 @@ trait BusinessRoutes extends JacksonSupport with AuthenticationSupport {
]
}
],
"secretBinding": [
{
"serviceOrgid": "string",
"serviceUrl": "string",
"serviceArch": "amd64",
"serviceVersionRange": "x.y.z",
"secrets": [
{
"FirstSecret": "secret1"
"Foo": "Bar"
}
]
},
],
"properties": [
{
"name": "mypurpose",
Expand Down
57 changes: 51 additions & 6 deletions src/main/scala/com/horizon/exchangeapi/PatternsRoutes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ object PatternUtils {
}

/** Input format for POST/PUT /orgs/{orgid}/patterns/<pattern-id> */
final case class PostPutPatternRequest(label: String, description: Option[String], public: Option[Boolean], services: List[PServices], userInput: Option[List[OneUserInputService]], agreementProtocols: Option[List[Map[String,String]]]) {
final case class PostPutPatternRequest(label: String, description: Option[String], public: Option[Boolean], services: List[PServices], userInput: Option[List[OneUserInputService]], secretBinding: Option[List[OneSecretBindingService]] ,agreementProtocols: Option[List[Map[String,String]]]) {
require(label!=null && services!=null)
protected implicit val jsonFormats: Formats = DefaultFormats

Expand All @@ -91,11 +91,11 @@ final case class PostPutPatternRequest(label: String, description: Option[String
services
}
val agreementProtocols2: Option[List[Map[String, String]]] = agreementProtocols.orElse(Some(List(Map("name" -> "Basic"))))
PatternRow(pattern, orgid, owner, label, description.getOrElse(label), public.getOrElse(false), write(services2), write(userInput), write(agreementProtocols2), ApiTime.nowUTC)
PatternRow(pattern, orgid, owner, label, description.getOrElse(label), public.getOrElse(false), write(services2), write(userInput),write(secretBinding), write(agreementProtocols2),ApiTime.nowUTC)
}
}

final case class PatchPatternRequest(label: Option[String], description: Option[String], public: Option[Boolean], services: Option[List[PServices]], userInput: Option[List[OneUserInputService]], agreementProtocols: Option[List[Map[String,String]]]) {
final case class PatchPatternRequest(label: Option[String], description: Option[String], public: Option[Boolean], services: Option[List[PServices]], userInput: Option[List[OneUserInputService]], secretBinding:Option[List[OneSecretBindingService]] , agreementProtocols: Option[List[Map[String,String]]]) {
protected implicit val jsonFormats: Formats = DefaultFormats

def getAnyProblem: Option[String] = {
Expand All @@ -113,6 +113,7 @@ final case class PatchPatternRequest(label: Option[String], description: Option[
public match { case Some(pub) => return ((for { d <- PatternsTQ.rows if d.pattern === pattern } yield (d.pattern,d.public,d.lastUpdated)).update((pattern, pub, lastUpdated)), "public"); case _ => ; }
services match { case Some(svc) => return ((for { d <- PatternsTQ.rows if d.pattern === pattern } yield (d.pattern,d.services,d.lastUpdated)).update((pattern, write(svc), lastUpdated)), "services"); case _ => ; }
userInput match { case Some(input) => return ((for { d <- PatternsTQ.rows if d.pattern === pattern } yield (d.pattern,d.userInput,d.lastUpdated)).update((pattern, write(input), lastUpdated)), "userInput"); case _ => ; }
secretBinding match {case Some(bind) => return ((for { d <- PatternsTQ.rows if d.pattern === pattern } yield (d.pattern,d.secretBinding,d.lastUpdated)).update((pattern, write(bind), lastUpdated)), "secretBinding"); case _ => ; }
agreementProtocols match { case Some(ap) => return ((for { d <- PatternsTQ.rows if d.pattern === pattern } yield (d.pattern,d.agreementProtocols,d.lastUpdated)).update((pattern, write(ap), lastUpdated)), "agreementProtocols"); case _ => ; }
(null, null)
}
Expand Down Expand Up @@ -207,6 +208,7 @@ trait PatternsRoutes extends JacksonSupport with AuthenticationSupport {
}
],
"userInput": [],
"secretBinding": [],
"agreementProtocols": [
{
"name": "Basic"
Expand Down Expand Up @@ -316,6 +318,7 @@ trait PatternsRoutes extends JacksonSupport with AuthenticationSupport {
}
],
"userInput": [],
"secretBinding": [],
"agreementProtocols": [
{
"name": "Basic"
Expand Down Expand Up @@ -446,7 +449,7 @@ trait PatternsRoutes extends JacksonSupport with AuthenticationSupport {
"serviceOrgid": "IBM",
"serviceUrl": "ibm.cpu2msghub",
"serviceArch": "", // omit or leave blank to mean all architectures
"serviceVersionRange": "[0.0.0,INFINITY)", // or omit to mean all versions
"serviceVersionRange": "x.y.z", // or omit to mean all versions
"inputs": [
{
"name": "foo",
Expand All @@ -455,6 +458,20 @@ trait PatternsRoutes extends JacksonSupport with AuthenticationSupport {
]
}
],
"secretBinding": [
{
"serviceOrgid": "string",
"serviceUrl": "string",
"serviceArch": "amd64",
"serviceVersionRange": "x.y.z",
"secrets": [
{
"FirstSecret": "secret1"
"Foo": "Bar"
}
]
},
],
// The Horizon agreement protocol(s) to use. "Basic" means make agreements w/o a blockchain. "Citizen Scientist" means use ethereum to record the agreement.
"agreementProtocols": [ // can be omitted
{
Expand Down Expand Up @@ -637,7 +654,7 @@ trait PatternsRoutes extends JacksonSupport with AuthenticationSupport {
"serviceOrgid": "IBM",
"serviceUrl": "ibm.cpu2msghub",
"serviceArch": "", // omit or leave blank to mean all architectures
"serviceVersionRange": "[0.0.0,INFINITY)", // or omit to mean all versions
"serviceVersionRange": "x.y.z", // or omit to mean all versions
"inputs": [
{
"name": "foo",
Expand All @@ -646,6 +663,20 @@ trait PatternsRoutes extends JacksonSupport with AuthenticationSupport {
]
}
],
"secretBinding": [
{
"serviceOrgid": "string",
"serviceUrl": "string",
"serviceArch": "amd64",
"serviceVersionRange":"x.y.z",
"secrets": [
{
"FirstSecret": "secret1"
"Foo": "Bar"
}
]
},
],
// The Horizon agreement protocol(s) to use. "Basic" means make agreements w/o a blockchain. "Citizen Scientist" means use ethereum to record the agreement.
"agreementProtocols": [ // can be omitted
{
Expand Down Expand Up @@ -827,7 +858,7 @@ trait PatternsRoutes extends JacksonSupport with AuthenticationSupport {
"serviceOrgid": "IBM",
"serviceUrl": "ibm.cpu2msghub",
"serviceArch": "", // omit or leave blank to mean all architectures
"serviceVersionRange": "[0.0.0,INFINITY)", // or omit to mean all versions
"serviceVersionRange": "x.y.z", // or omit to mean all versions
"inputs": [
{
"name": "foo",
Expand All @@ -836,6 +867,20 @@ trait PatternsRoutes extends JacksonSupport with AuthenticationSupport {
]
}
],
"secretBinding": [
{
"serviceOrgid": "string",
"serviceUrl": "string",
"serviceArch": "amd64",
"serviceVersionRange": "x.y.z",
"secrets": [
{
"FirstSecret": "secret1"
"Foo": "Bar"
}
]
},
],
// The Horizon agreement protocol(s) to use. "Basic" means make agreements w/o a blockchain. "Citizen Scientist" means use ethereum to record the agreement.
"agreementProtocols": [ // can be omitted
{
Expand Down
14 changes: 9 additions & 5 deletions src/main/scala/com/horizon/exchangeapi/tables/Business.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,20 @@ final case class BService(name: String, org: String, arch: String, serviceVersio
final case class BServiceVersions(version: String, priority: Option[Map[String,Int]], upgradePolicy: Option[Map[String,String]])

// This is the businesspolicies table minus the key - used as the data structure to return to the REST clients
class BusinessPolicy(var owner: String, var label: String, var description: String, var service: BService, var userInput: List[OneUserInputService], var properties: List[OneProperty], var constraints: List[String], var lastUpdated: String, var created: String) {
def copy = new BusinessPolicy(owner, label, description, service, userInput, properties, constraints, lastUpdated, created)
class BusinessPolicy(var owner: String, var label: String, var description: String, var service: BService, var userInput: List[OneUserInputService],var secretBinding: List[OneSecretBindingService], var properties: List[OneProperty], var constraints: List[String], var lastUpdated: String, var created: String) {
def copy = new BusinessPolicy(owner, label, description, service, userInput,secretBinding, properties, constraints, lastUpdated, created)
}

// Note: if you add fields to this, you must also add them the update method below
final case class BusinessPolicyRow(businessPolicy: String, orgid: String, owner: String, label: String, description: String, service: String, userInput: String, properties: String, constraints: String, lastUpdated: String, created: String) {
final case class BusinessPolicyRow(businessPolicy: String, orgid: String, owner: String, label: String, description: String, service: String, userInput: String, secretBinding: String,properties: String, constraints: String, lastUpdated: String, created: String) {
protected implicit val jsonFormats: Formats = DefaultFormats

def toBusinessPolicy: BusinessPolicy = {
val input: List[OneUserInputService] = if (userInput != "") read[List[OneUserInputService]](userInput) else List[OneUserInputService]()
val bind: List[OneSecretBindingService] = if (secretBinding != "") read[List[OneSecretBindingService]](secretBinding) else List[OneSecretBindingService]()
val prop: List[OneProperty] = if (properties != "") read[List[OneProperty]](properties) else List[OneProperty]()
val con: List[String] = if (constraints != "") read[List[String]](constraints) else List[String]()
new BusinessPolicy(owner, label, description, read[BService](service), input, prop, con, lastUpdated, created)
new BusinessPolicy(owner, label, description, read[BService](service), input,bind, prop, con, lastUpdated, created)
}

// update returns a DB action to update this row
Expand All @@ -48,12 +49,13 @@ class BusinessPolicies(tag: Tag) extends Table[BusinessPolicyRow](tag, "business
def description = column[String]("description")
def service = column[String]("service")
def userInput = column[String]("userinput")
def secretBinding = column[String]("secretbinding")
def properties = column[String]("properties")
def constraints = column[String]("constraints")
def lastUpdated = column[String]("lastupdated")
def created = column[String]("created")
// this describes what you get back when you return rows from a query
def * = (businessPolicy, orgid, owner, label, description, service, userInput, properties, constraints, lastUpdated, created).<>(BusinessPolicyRow.tupled, BusinessPolicyRow.unapply)
def * = (businessPolicy, orgid, owner, label, description, service, userInput,secretBinding, properties, constraints, lastUpdated, created).<>(BusinessPolicyRow.tupled, BusinessPolicyRow.unapply)
def user = foreignKey("user_fk", owner, UsersTQ.rows)(_.username, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade)
def orgidKey = foreignKey("orgid_fk", orgid, OrgsTQ.rows)(_.orgid, onUpdate=ForeignKeyAction.Cascade, onDelete=ForeignKeyAction.Cascade)
}
Expand Down Expand Up @@ -100,6 +102,7 @@ object BusinessPoliciesTQ {
def getService(businessPolicy: String): Query[Rep[String], String, Seq] = rows.filter(_.businessPolicy === businessPolicy).map(_.service)
def getServiceFromString(service: String): BService = read[BService](service)
def getUserInput(businessPolicy: String): Query[Rep[String], String, Seq] = rows.filter(_.businessPolicy === businessPolicy).map(_.userInput)
def getSecretBindings(businessPolicy: String): Query[Rep[String],String, Seq] = rows.filter(_.businessPolicy === businessPolicy).map(_.secretBinding)
def getLastUpdated(businessPolicy: String): Query[Rep[String], String, Seq] = rows.filter(_.businessPolicy === businessPolicy).map(_.lastUpdated)

/** Returns a query for the specified businessPolicy attribute value. Returns null if an invalid attribute name is given. */
Expand All @@ -112,6 +115,7 @@ object BusinessPoliciesTQ {
case "description" => filter.map(_.description)
case "service" => filter.map(_.service)
case "userInput" => filter.map(_.userInput)
case "secretBinding" => filter.map(_.secretBinding)
case "properties" => filter.map(_.properties)
case "constraints" => filter.map(_.constraints)
case "lastUpdated" => filter.map(_.lastUpdated)
Expand Down
Loading

0 comments on commit dfa65f3

Please sign in to comment.