Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
kshakir committed May 6, 2021
2 parents 14bb3ba + 0f35687 commit f1cd3e1
Show file tree
Hide file tree
Showing 65 changed files with 1,014 additions and 436 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1157,7 +1157,7 @@ data. When switching connection information for an existing database containing
should be manually replicated from one database instance to another using the tools appropriate for your specific
database types. Cromwell will not move any existing data automatically. This feature should be considered experimental
and likely to change in the future. See the [Database Documentation](https://cromwell.readthedocs.io/en/develop/Configuring/#database) or the `database` section in
[cromwell.examples.conf](https://github.com/broadinstitute/cromwell/blob/develop/cromwell.examples.conf) for more
[cromwell.examples.conf](https://www.github.com/broadinstitute/cromwell/tree/develop/cromwell.example.backends/cromwell.examples.conf) for more
information.

* **StatsD**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import cromwell.backend._
import cromwell.backend.standard.callcaching._
import cromwell.core.Dispatcher.BackendDispatcher
import cromwell.core.path.Path
import cromwell.core.{CallOutputs, Dispatcher}
import cromwell.core.{BackendDockerConfiguration, CallOutputs, Dispatcher}
import wom.expression.IoFunctionSet
import wom.graph.CommandCallNode

Expand Down Expand Up @@ -216,4 +216,10 @@ trait StandardLifecycleActorFactory extends BackendLifecycleActorFactory {

initializationData.runtimeAttributesBuilder.definitions.toSet
}

override def dockerHashCredentials(workflowDescriptor: BackendWorkflowDescriptor,
initializationDataOption: Option[BackendInitializationData],
): List[Any] = {
BackendDockerConfiguration.build(configurationDescriptor.backendConfig).dockerCredentials.toList
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: hello_private_repo
testFormat: runtwiceexpectingcallcaching
backends: [LocalDockerSecure]

files {
workflow: hello_private_repo/hello_private_repo.wdl
inputs: hello_private_repo/hello_private_repo.inputs.json
}

metadata {
workflowName: hello_private_repo
status: Succeeded
"calls.hello_private_repo.hello.callCaching.result": "Cache Hit: <<CACHE_HIT_UUID>>:hello_private_repo.hello:-1"
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"hello_private_repo.addressee": "m'Lord"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version 1.0

task hello {
input {
String addressee
}
command {
echo "Hello ~{addressee}!"
}
output {
String salutation = read_string(stdout())
}
runtime {
backend: "LocalDockerSecure"
docker: "broadinstitute/cromwell-docker-test:private-repo"
}
}

workflow hello_private_repo {
input {
String addressee
}
call hello { input: addressee = addressee }
output {
String salutation = hello.salutation
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,15 @@ object MarthaField extends Enumeration {
val GoogleServiceAccount: MarthaField.Value = Value("googleServiceAccount")
val Hashes: MarthaField.Value = Value("hashes")
val FileName: MarthaField.Value = Value("fileName")
val AccessUrl: MarthaField.Value = Value("accessUrl")
}

final case class MarthaRequest(url: String, fields: NonEmptyList[MarthaField.Value])

final case class SADataObject(data: Json)

final case class AccessUrl(url: String, headers: Option[Map[String, String]])

/**
* A response from `martha_v3`.
*
Expand All @@ -99,6 +102,7 @@ final case class SADataObject(data: Json)
* @param googleServiceAccount The service account to access the gsUri contents created via bondProvider
* @param fileName A possible different file name for the object at gsUri, ex: "gsutil cp gs://bucket/12/345 my.vcf"
* @param hashes Hashes for the contents stored at gsUri
* @param accessUrl URL to query for signed URL
*/
final case class MarthaResponse(size: Option[Long] = None,
timeCreated: Option[String] = None,
Expand All @@ -108,6 +112,7 @@ final case class MarthaResponse(size: Option[Long] = None,
googleServiceAccount: Option[SADataObject] = None,
fileName: Option[String] = None,
hashes: Option[Map[String, String]] = None,
accessUrl: Option[AccessUrl] = None
)

// Adapted from https://github.com/broadinstitute/martha/blob/f31933a3a11e20d30698ec4b4dc1e0abbb31a8bc/common/helpers.js#L210-L218
Expand All @@ -125,6 +130,8 @@ object MarthaResponseSupport {
implicit lazy val marthaFailureResponseDecoder: Decoder[MarthaFailureResponse] = deriveDecoder
implicit lazy val marthaFailureResponsePayloadDecoder: Decoder[MarthaFailureResponsePayload] = deriveDecoder

implicit lazy val marthaAccessUrlDecoder: Decoder[AccessUrl] = deriveDecoder

private val GcsScheme = "gs://"

def getGcsBucketAndName(gcsUrl: String): (String, String) = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
*/
package cromwell.cloudsupport.aws.auth

import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.client.json.gson.GsonFactory
import cromwell.cloudsupport.aws.auth.AwsAuthMode.OptionLookup
import org.slf4j.LoggerFactory
import software.amazon.awssdk.auth.credentials._
Expand All @@ -43,7 +43,7 @@ import scala.util.{Failure, Success, Try}

object AwsAuthMode {
type OptionLookup = String => String
lazy val jsonFactory = JacksonFactory.getDefaultInstance
lazy val jsonFactory = GsonFactory.getDefaultInstance
}

sealed trait AwsAuthMode {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import java.nio.charset.StandardCharsets
import better.files.File
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport
import com.google.api.client.http.{HttpResponseException, HttpTransport}
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.client.json.gson.GsonFactory
import com.google.auth.Credentials
import com.google.auth.http.HttpTransportFactory
import com.google.auth.oauth2.{GoogleCredentials, OAuth2Credentials, ServiceAccountCredentials, UserCredentials}
Expand Down Expand Up @@ -38,7 +38,7 @@ object GoogleAuthMode {
throw new UnsupportedOperationException(s"cannot lookup $string")
}

lazy val jsonFactory: JacksonFactory = JacksonFactory.getDefaultInstance
lazy val jsonFactory: GsonFactory = GsonFactory.getDefaultInstance
lazy val httpTransport: HttpTransport = GoogleNetHttpTransport.newTrustedTransport
lazy val HttpTransportFactory: HttpTransportFactory = () => httpTransport

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cromwell.cloudsupport.gcp.gcs

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport
import com.google.api.client.googleapis.media.MediaHttpUploader
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.client.json.gson.GsonFactory
import com.google.api.gax.retrying.RetrySettings
import com.google.cloud.storage.StorageOptions
import com.google.api.services.storage.Storage
Expand All @@ -14,7 +14,7 @@ import cromwell.cloudsupport.gcp.http.GoogleHttpTransportOptions.TransportOption
import net.ceedubs.ficus.Ficus._

object GcsStorage {
val JsonFactory = JacksonFactory.getDefaultInstance
val JsonFactory = GsonFactory.getDefaultInstance
val HttpTransport = GoogleNetHttpTransport.newTrustedTransport

val DefaultCloudStorageConfiguration = {
Expand Down
26 changes: 25 additions & 1 deletion common/src/main/scala/common/util/StringUtil.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package common.util

import common.util.UriUtil._
import mouse.all._
import org.apache.commons.lang3.StringUtils
import org.apache.commons.text.StringEscapeUtils

import java.net.URI
import scala.util.Try

object StringUtil {

implicit class EnhancedToStringable(val any: Any) extends AnyVal {
Expand Down Expand Up @@ -58,12 +62,32 @@ object StringUtil {
* i.e no slash prefix and a slash suffix
* e.g: /root/some/dir -> root/some/dir/
*/
def relativeDirectory = string.ensureNoLeadingSlash.ensureSlashed
def relativeDirectory: String = string.ensureNoLeadingSlash.ensureSlashed

def elided(limit: Int): String = {
if (string.length > limit) {
s"(elided) ${string.take(limit)}..."
} else string
}

/**
* Removes userInfo and sensitive query parts from strings that are RFC 2396 URIs.
*
* If the URI query does not contain any detected sensitive information, then the entire query will be masked.
*
* Returns the original input for:
* - Any URI that cannot be parsed
* - RFC 3986 URIs as those are not supported by java.net.URI
* - Other non-IETF / non-W3C URI specifications such as DOS URI or DRS URI
*
* Depending on the encoding used in the input URI the masked output may have unexpected encoding. See:
* - the StringUtilSpec for current expectations
* - https://stackoverflow.com/questions/4571346/how-to-encode-url-to-avoid-special-characters-in-java#answer-4571518
*/
def maskSensitiveUri: String = {
Try(new URI(string))
.map(_.maskSensitive.toASCIIString)
.getOrElse(string)
}
}
}
95 changes: 95 additions & 0 deletions common/src/main/scala/common/util/UriUtil.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package common.util

import java.net.URI
import scala.util.Try

object UriUtil {
implicit class EnhancedUri(val uri: URI) extends AnyVal {

/**
* Removes userInfo and sensitive query parts from instances of java.net.URI.
*
* If the URI query does not contain any detected sensitive information, then the entire query will be masked.
*
* Depending on the encoding used in the input URI the masked output may have unexpected encoding. See:
* - the StringUtilSpec for current expectations
* - https://stackoverflow.com/questions/4571346/how-to-encode-url-to-avoid-special-characters-in-java#answer-4571518
*/
def maskSensitive: URI = {
Try {
new URI(
uri.getScheme,
null, // Remove all userInfo
uri.getHost,
uri.getPort,
uri.getPath,
Option(uri.getQuery).map(maskSensitiveQuery).orNull,
uri.getFragment,
)
}
.getOrElse(uri)
}
}

private def maskSensitiveQuery(query: String): String = {
val parsedQuery: Array[Seq[String]] =
query
.split("&")
.map { param =>
param.split("=", 2).toSeq match {
case seq @ Seq(_, _) => seq
case _ => Seq(param)
}
}

if (!parsedQuery.exists(param => isSensitiveKey(param.head))) {
// Mask the entire query just in case
"masked"
} else {
parsedQuery
.map {
case Seq(name, _) if isSensitiveKey(name) => s"$name=masked"
case seq => seq.mkString("=")
}
.mkString("&")
}
}

/*
Parts of these examples have been redacted even if they will not be masked.
via: https://bvdp-saturn-dev.appspot.com/#workspaces/general-dev-billing-account/DRS%20and%20Signed%20URL%20Development%20-%20Dev/notebooks/launch/drs_signed_url_flow_kids_dev.ipynb
```
https://example-redacted-but-not-masked.s3.amazonaws.com/_example_redacted_but_not_masked_.CNVs.p.value.txt
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=_to_be_masked_
&X-Amz-Date=20210504T200819Z
&X-Amz-Expires=3600
&X-Amz-SignedHeaders=host
&user_id=122
&username=_example_redacted_but_not_masked_
&X-Amz-Signature=_to_be_masked_
```
via: https://bvdp-saturn-dev.appspot.com/#workspaces/general-dev-billing-account/DRS%20and%20Signed%20URL%20Development%20-%20Dev/notebooks/launch/drs_signed_url_flow_bdcat_dev.ipynb
```
https://storage.googleapis.com/_example_redacted_but_not_masked_/testfile.txt
?GoogleAccessId=_example_redacted_but_not_masked_
&Expires=1614119022
&Signature=_to_be_masked_
&userProject=_example_redacted_but_not_masked_
```
*/
private val SensitiveKeyParts =
List(
"credential",
"signature",
)

private def isSensitiveKey(name: String): Boolean = {
val lower = name.toLowerCase
SensitiveKeyParts.exists(lower.contains(_))
}
}
Loading

0 comments on commit f1cd3e1

Please sign in to comment.