├── project ├── build.properties ├── versions.scala ├── plugins.sbt ├── dependencies.scala └── CentralRequirementsPlugin.scala ├── version.sbt ├── core ├── src │ └── main │ │ └── scala │ │ └── platypus │ │ ├── RootToken.scala │ │ ├── Rule.scala │ │ ├── Mount.scala │ │ ├── package.scala │ │ ├── Initialization.scala │ │ ├── SealStatus.scala │ │ ├── InitialCreds.scala │ │ ├── Token.scala │ │ └── VaultOp.scala └── build.sbt ├── NOTICE ├── .buildkite ├── pipeline.teardown.sh ├── pipeline.exec.sh └── pipeline.yml ├── http4s ├── src │ ├── test │ │ ├── scala-2.11 │ │ │ └── platypus │ │ │ │ └── http4s │ │ │ │ └── package.scala │ │ ├── resources │ │ │ ├── logback-test.xml │ │ │ └── vault.hcl │ │ └── scala │ │ │ └── platypus │ │ │ └── http4s │ │ │ ├── DockerVaultService.scala │ │ │ └── Http4sVaultSpec.scala │ └── main │ │ └── scala │ │ └── platypus │ │ └── http4s │ │ ├── Json.scala │ │ └── Http4sVault.scala └── build.sbt ├── .gitignore ├── project.sbt ├── docs └── build.sbt ├── README.md └── LICENSE /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.1.6 2 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "0.1.0-SNAPSHOT" -------------------------------------------------------------------------------- /core/src/main/scala/platypus/RootToken.scala: -------------------------------------------------------------------------------- 1 | package platypus 2 | 3 | final case class RootToken(value: String) extends AnyVal 4 | -------------------------------------------------------------------------------- /core/src/main/scala/platypus/Rule.scala: -------------------------------------------------------------------------------- 1 | package platypus 2 | 3 | final case class Rule(path: String, 4 | capabilities: List[String], 5 | policy: Option[String]) 6 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | ============================================================== 2 | platypus 3 | Copyright (C) 2017 Verizon. 4 | ============================================================== 5 | 6 | This product includes software developed by 7 | Verizon (www.verizon.com; github.com/verizon). -------------------------------------------------------------------------------- /core/src/main/scala/platypus/Mount.scala: -------------------------------------------------------------------------------- 1 | package platypus 2 | 3 | final case class Mount(path: String, 4 | `type`: String, 5 | description: String, 6 | defaultLeaseTTL: Int, 7 | maxLeaseTTL: Int) 8 | -------------------------------------------------------------------------------- /.buildkite/pipeline.teardown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "==>> initilizing doctl..." 4 | doctl auth init -t "${DIGITAL_OCEAN_API_TOKEN}" 5 | 6 | echo "==>> deleting the droplet..." 7 | doctl compute droplet list | grep -v 'ID' | sort -r -k1 | grep "buildkite-worker" | awk '{print $1}' | xargs -L1 doctl compute droplet delete -f -------------------------------------------------------------------------------- /http4s/src/test/scala-2.11/platypus/http4s/package.scala: -------------------------------------------------------------------------------- 1 | package platypus 2 | 3 | package object http4s { 4 | 5 | private[http4s] implicit class EitherOps[A, B](val e: Either[A, B]) extends AnyVal { 6 | def toOption: Option[B] = e match { 7 | case Right(b) => Some(b) 8 | case _ => None 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/scala/platypus/package.scala: -------------------------------------------------------------------------------- 1 | 2 | import cats.{~>, Monad} 3 | import cats.free.Free 4 | 5 | package object platypus { 6 | 7 | type MasterKey = String 8 | 9 | type VaultOpF[A] = Free[VaultOp, A] 10 | 11 | def run[F[_]: Monad, A](interpreter: VaultOp ~> F, op: VaultOpF[A]): F[A] = 12 | op.foldMap(interpreter) 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/scala/platypus/Initialization.scala: -------------------------------------------------------------------------------- 1 | package platypus 2 | 3 | /** 4 | * Parameters needed to initialize a new vault 5 | * @param secretShares the numbers of unseal keys 6 | * @param secretThreshold the quorum of unseal keys needed to unseal 7 | */ 8 | final case class Initialization(secretShares: Int, 9 | secretThreshold: Int) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | release-notes 2 | logs 3 | *.db 4 | project/project 5 | project/target 6 | target 7 | lib_managed 8 | src_managed 9 | project/boot 10 | tmp 11 | .history 12 | dist 13 | .DS_Store 14 | .cache 15 | .settings 16 | .classpath 17 | .class 18 | .project 19 | .ivy2 20 | .lib 21 | .idea 22 | *.swp 23 | sbt 24 | *~ 25 | tags 26 | _site 27 | *.local.yml 28 | Gemfile.lock 29 | deploy_rsa 30 | deploy_rsa.pub -------------------------------------------------------------------------------- /core/src/main/scala/platypus/SealStatus.scala: -------------------------------------------------------------------------------- 1 | package platypus 2 | 3 | /** 4 | * @param sealed Whether or not the vault is sealed. 5 | * @param total The number of existing MasterKeys 6 | * @param quorum The number of keys needed to unseal the vault 7 | * @param progress The number of keys out of total that have been provided so far to unseal 8 | */ 9 | final case class SealStatus(`sealed`: Boolean, 10 | total: Int, 11 | quorum: Int, 12 | progress: Int) 13 | -------------------------------------------------------------------------------- /core/src/main/scala/platypus/InitialCreds.scala: -------------------------------------------------------------------------------- 1 | package platypus 2 | 3 | /** 4 | * This contains the result of the `initialize` call, it *must* have 5 | * the number of MasterKeys requested. They should distributed to N 6 | * trusted individuals, a quorum of which will have to present keys to 7 | * unseal a vault. 8 | * 9 | * the rootToken is a omnipotent bearer token for this vault, so treat 10 | * it with the utmost of care. It will be required to create other 11 | * less privileged tokens. 12 | */ 13 | final case class InitialCreds(keys: List[MasterKey], 14 | rootToken: RootToken) 15 | -------------------------------------------------------------------------------- /.buildkite/pipeline.exec.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o errtrace 5 | set -o nounset 6 | set -o pipefail 7 | 8 | git config --global user.email "team@getnelson.io" 9 | git config --global user.name "Nelson Team" 10 | 11 | # subvert the sbt-rig plugin 12 | export TRAVIS="true" # way hacky 13 | export TRAVIS_COMMIT="$BUILDKITE_COMMIT" 14 | export TRAVIS_REPO_SLUG="getnelson/platypus" 15 | export TRAVIS_BUILD_NUMBER="$BUILDKITE_BUILD_NUMBER" 16 | 17 | if [ "${BUILDKITE_PULL_REQUEST:-}" = 'false' ]; then 18 | git checkout -qf "$BUILDKITE_BRANCH"; 19 | fi 20 | 21 | echo "--> running build for ${TRAVIS_SCALA_VERSION}..." 22 | eval "sbt ++${TRAVIS_SCALA_VERSION} 'release with-defaults'" -------------------------------------------------------------------------------- /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | steps: 3 | - label: ":hammer: build" 4 | command: .buildkite/pipeline.exec.sh 5 | timeout_in_minutes: 45 6 | branches: master 7 | env: 8 | BUILDKITE_CLEAN_CHECKOUT: true 9 | TRAVIS_SCALA_VERSION: "2.11.12" 10 | TRAVIS_JOB_NUMBER: "1.1" 11 | agents: 12 | os: linux 13 | 14 | - label: ":hammer: build" 15 | command: .buildkite/pipeline.exec.sh 16 | timeout_in_minutes: 45 17 | branches: master 18 | env: 19 | BUILDKITE_CLEAN_CHECKOUT: true 20 | TRAVIS_SCALA_VERSION: "2.12.4" 21 | TRAVIS_JOB_NUMBER: "1.2" 22 | agents: 23 | os: linux 24 | 25 | - wait: ~ 26 | continue_on_failure: true 27 | 28 | - label: ":radioactive_sign: teardown" 29 | command: .buildkite/pipeline.teardown.sh 30 | branches: master 31 | timeout_in_minutes: 3 32 | agents: 33 | os: linux 34 | -------------------------------------------------------------------------------- /core/src/main/scala/platypus/Token.scala: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | package platypus 18 | 19 | final case class Token(value: String) extends AnyVal 20 | -------------------------------------------------------------------------------- /http4s/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %d{HH:mm:ss.SSS} %highlight([%level]) %logger{30} - %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /http4s/src/test/resources/vault.hcl: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | storage "inmem" {} 18 | 19 | listener "tcp" { 20 | address = "0.0.0.0:8200" 21 | tls_disable = 1 22 | } 23 | 24 | disable_mlock = true 25 | -------------------------------------------------------------------------------- /project/versions.scala: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | 18 | object V { 19 | val http4s = "0.20.0-M4" 20 | val journal = "3.0.18" 21 | val knobs = "6.0.33" 22 | val dockerit = "0.9.8" 23 | val argonaut = "6.2.1" 24 | 25 | val catsEffect = "1.1.0" 26 | val cats = "1.5.0" 27 | } -------------------------------------------------------------------------------- /project.sbt: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | 18 | organization in Global := "io.getnelson.platypus" 19 | 20 | crossScalaVersions in Global := Seq("2.12.4", "2.11.12") 21 | 22 | scalaVersion in Global := crossScalaVersions.value.head 23 | 24 | lazy val platypus = project.in(file(".")).aggregate(core, http4s) 25 | 26 | lazy val core = project 27 | 28 | // lazy val docs = project 29 | 30 | lazy val http4s = project dependsOn core 31 | 32 | enablePlugins(DisablePublishingPlugin) 33 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | resolvers += "bintray sbt" at "https://dl.bintray.com/sbt/sbt-plugin-releases/" 18 | 19 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.2") 20 | 21 | addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0") 22 | 23 | addSbtPlugin("io.verizon.build" % "sbt-rig" % "5.0.39") 24 | 25 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3") 26 | 27 | libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.1.3" -------------------------------------------------------------------------------- /project/dependencies.scala: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | 18 | import sbt._, Keys._ 19 | 20 | object dependencies { 21 | object kindprojector { 22 | val version = "0.9.4" 23 | 24 | val plugin = "org.spire-math" % "kind-projector" % version cross CrossVersion.binary 25 | } 26 | 27 | object simulacrum { 28 | val version = "0.11.0" 29 | 30 | val core = "com.github.mpilquist" %% "simulacrum" % version 31 | } 32 | 33 | object macroparadise { 34 | val version = "2.1.0" 35 | 36 | val plugin = "org.scalamacros" % "paradise" % version cross CrossVersion.full 37 | } 38 | } -------------------------------------------------------------------------------- /docs/build.sbt: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | import verizon.build._ 18 | 19 | enablePlugins(DocsPlugin) 20 | 21 | libraryDependencies += dependencies.simulacrum.core 22 | 23 | addCompilerPlugin(dependencies.macroparadise.plugin) 24 | 25 | addCompilerPlugin(dependencies.kindprojector.plugin) 26 | 27 | scalacOptions += "-Ypartial-unification" 28 | 29 | githubOrg := "getnelson" 30 | 31 | githubRepoName := "platypus" 32 | 33 | baseURL in Hugo := { 34 | if (isTravisBuild.value) new URI(s"https://getnelson.io/") 35 | else new URI(s"http://127.0.0.1:${previewFixedPort.value.getOrElse(1313)}/") 36 | } 37 | 38 | import com.typesafe.sbt.SbtGit.GitKeys.{gitBranch, gitRemoteRepo} 39 | 40 | gitRemoteRepo := "git@github.com:getnelson/platypus.git" 41 | 42 | includeFilter in Hugo := ("*.html" | "*.ico" | "*.jpg" | "*.svg" | "*.png" | "*.js" | "*.css" | "*.gif" | "CNAME") 43 | 44 | minimumHugoVersion in Hugo := "0.48" 45 | 46 | excludeFilter in Hugo := HiddenFileFilter 47 | -------------------------------------------------------------------------------- /core/build.sbt: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | import verizon.build._ 18 | 19 | enablePlugins(MetadataPlugin, ScalaTestPlugin) 20 | 21 | libraryDependencies ++= Seq( 22 | dependencies.simulacrum.core, 23 | "org.typelevel" %% "cats-core" % V.cats, 24 | "org.typelevel" %% "cats-free" % V.cats 25 | ) 26 | 27 | addCompilerPlugin(dependencies.kindprojector.plugin) 28 | 29 | scalaModuleInfo := scalaModuleInfo.value map { _.withOverrideScalaVersion(true) } 30 | 31 | addCompilerPlugin(dependencies.macroparadise.plugin) 32 | 33 | initialCommands in console := """ 34 | import platypus._ 35 | """ 36 | 37 | buildInfoPackage := "platypus" 38 | 39 | scalacOptions ++= List("-Ypartial-unification", "-Ywarn-value-discard") 40 | 41 | scalacOptions in (Compile, doc) ++= Seq( 42 | "-no-link-warnings" // Suppresses problems with Scaladoc @throws links 43 | ) 44 | 45 | scalaTestVersion := "3.0.5" 46 | 47 | scalaCheckVersion := "1.13.5" -------------------------------------------------------------------------------- /http4s/build.sbt: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | import verizon.build._ 18 | 19 | enablePlugins(MetadataPlugin, ScalaTestPlugin) 20 | 21 | libraryDependencies ++= Seq( 22 | dependencies.simulacrum.core, 23 | "io.argonaut" %% "argonaut" % V.argonaut, 24 | "io.argonaut" %% "argonaut-cats" % V.argonaut, 25 | "org.http4s" %% "http4s-argonaut" % V.http4s, 26 | "org.http4s" %% "http4s-blaze-client" % V.http4s, 27 | "io.verizon.knobs" %% "core" % V.knobs, 28 | "io.verizon.journal" %% "core" % V.journal, 29 | "com.whisk" %% "docker-testkit-scalatest" % V.dockerit % "test", 30 | "com.whisk" %% "docker-testkit-impl-spotify" % V.dockerit % "test" 31 | ) 32 | 33 | addCompilerPlugin(dependencies.kindprojector.plugin) 34 | 35 | scalaModuleInfo := scalaModuleInfo.value map { _.withOverrideScalaVersion(true) } 36 | 37 | addCompilerPlugin(dependencies.macroparadise.plugin) 38 | 39 | initialCommands in console := """ 40 | import platypus._ 41 | import platypus.http4s._ 42 | """ 43 | 44 | scalacOptions ++= List("-Ypartial-unification", "-Ywarn-value-discard") 45 | 46 | scalacOptions in (Compile, doc) ++= Seq( 47 | "-no-link-warnings" // Suppresses problems with Scaladoc @throws links 48 | ) 49 | 50 | scalaTestVersion := "3.0.5" 51 | 52 | scalaCheckVersion := "1.13.5" -------------------------------------------------------------------------------- /project/CentralRequirementsPlugin.scala: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | package verizon.build 18 | 19 | import sbt._, Keys._ 20 | import xerial.sbt.Sonatype.autoImport.sonatypeProfileName 21 | 22 | object CentralRequirementsPlugin extends AutoPlugin { 23 | 24 | override def trigger = allRequirements 25 | 26 | override def requires = RigPlugin 27 | 28 | override lazy val projectSettings = Seq( 29 | publishTo := Some("releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2"), 30 | sonatypeProfileName := "io.getnelson", 31 | pomExtra in Global := { 32 | 33 | 34 | timperrett 35 | Timothy Perrett 36 | http://github.com/timperrett 37 | 38 | 39 | stew 40 | Stew O'Connor 41 | http://github.com/stew 42 | 43 | 44 | rossabaker 45 | Ross Baker 46 | http://github.com/rossabaker 47 | 48 | 49 | ceedubs 50 | Cody Allen 51 | http://github.com/ceedubs 52 | 53 | 54 | }, 55 | licenses := Seq("Apache-2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0.html")), 56 | homepage := Some(url("http://getnelson.io/")), 57 | scmInfo := Some(ScmInfo(url("https://github.com/getnelson/platypus"), 58 | "git@github.com:getnelson/platypus.git")) 59 | ) 60 | } -------------------------------------------------------------------------------- /http4s/src/test/scala/platypus/http4s/DockerVaultService.scala: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | package platypus 18 | package http4s 19 | 20 | import com.spotify.docker.client.{DefaultDockerClient, DockerClient} 21 | import com.whisk.docker.impl.spotify.SpotifyDockerFactory 22 | import com.whisk.docker._ 23 | import journal.Logger 24 | import scala.concurrent.duration._ 25 | import scala.io.Source 26 | 27 | trait DockerVaultService extends DockerKit { 28 | private[this] val logger = Logger[DockerVaultService] 29 | 30 | private val client: DockerClient = DefaultDockerClient.fromEnv().build() 31 | override implicit val dockerFactory: DockerFactory = new SpotifyDockerFactory(client) 32 | 33 | logger.debug(client.info().toString) 34 | 35 | val consulContainer = 36 | DockerContainer("consul:1.2.2", name = Some("consul")) 37 | .withPorts(8500 -> Some(8500)) 38 | .withLogLineReceiver(LogLineReceiver(true, s => logger.debug(s"consul: $s"))) 39 | .withReadyChecker(DockerReadyChecker 40 | .HttpResponseCode(8500, "/v1/status/leader") 41 | .looped(12, 10.seconds)) 42 | 43 | private val vaultLocalConfig = 44 | Source.fromInputStream(getClass.getResourceAsStream("/vault.hcl")).mkString 45 | 46 | val vaultContainer = 47 | DockerContainer("vault:0.10.4", name = Some("vault")) 48 | .withPorts(8200 -> Some(8200)) 49 | .withEnv(s"VAULT_LOCAL_CONFIG=$vaultLocalConfig") 50 | .withLinks(ContainerLink(consulContainer, "consul")) 51 | .withCommand("server") 52 | .withLogLineReceiver(LogLineReceiver(true, s => logger.debug(s"vault: $s"))) 53 | .withReadyChecker(DockerReadyChecker 54 | .HttpResponseCode(8200, "/v1/sys/seal-status", code = 400) 55 | .looped(12, 10.seconds)) 56 | 57 | abstract override def dockerContainers: List[DockerContainer] = 58 | consulContainer :: vaultContainer :: super.dockerContainers 59 | } 60 | -------------------------------------------------------------------------------- /core/src/main/scala/platypus/VaultOp.scala: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2014 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | package platypus 18 | 19 | import cats.free.Free._ 20 | import scala.concurrent.duration.FiniteDuration 21 | import scala.collection.immutable.SortedMap 22 | 23 | /** 24 | * An algebra which represents an operation interacting with a 25 | * [HashiCorp Vault](https://www.vaultproject.io/docs/) server 26 | */ 27 | sealed abstract class VaultOp[A] extends Product with Serializable 28 | 29 | object VaultOp { 30 | 31 | case object IsInitialized extends VaultOp[Boolean] 32 | 33 | final case class Initialize( 34 | init: Initialization 35 | ) extends VaultOp[InitialCreds] 36 | 37 | final case class Unseal( 38 | key: MasterKey 39 | ) extends VaultOp[SealStatus] 40 | 41 | case object GetSealStatus extends VaultOp[SealStatus] 42 | 43 | case object Seal extends VaultOp[Unit] 44 | 45 | case object GetMounts extends VaultOp[SortedMap[String, Mount]] 46 | 47 | final case class CreatePolicy( 48 | name: String, 49 | rules: List[Rule] 50 | ) extends VaultOp[Unit] 51 | 52 | final case class DeletePolicy( 53 | name: String 54 | ) extends VaultOp[Unit] 55 | 56 | final case class CreateToken( 57 | policies: Option[List[String]], 58 | renewable: Boolean, 59 | ttl: Option[FiniteDuration], 60 | numUses: Long = 0L 61 | ) extends VaultOp[Token] 62 | 63 | def isInitialized: VaultOpF[Boolean] = 64 | liftF(IsInitialized) 65 | 66 | def initialize(masters: Int, quorum: Int): VaultOpF[InitialCreds] = 67 | liftF(Initialize(Initialization(masters, quorum))) 68 | 69 | def unseal(key: MasterKey): VaultOpF[SealStatus] = 70 | liftF(Unseal(key)) 71 | 72 | def seal: VaultOpF[Unit] = 73 | liftF(Seal) 74 | 75 | def sealStatus: VaultOpF[SealStatus] = 76 | liftF(GetSealStatus) 77 | 78 | def createPolicy(name: String, rules: List[Rule]): VaultOpF[Unit] = 79 | liftF(CreatePolicy(name, rules)) 80 | 81 | def deletePolicy(name: String): VaultOpF[Unit] = 82 | liftF(DeletePolicy(name)) 83 | 84 | def getMounts: VaultOpF[SortedMap[String, Mount]] = 85 | liftF(GetMounts) 86 | 87 | def createToken( 88 | policies: Option[List[String]] = None, 89 | renewable: Boolean = true, 90 | ttl: Option[FiniteDuration] = None, 91 | numUses: Long = 0L 92 | ): VaultOpF[Token] = liftF(CreateToken(policies, renewable, ttl, numUses)) 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Platypus 2 | 3 | [![Build Status](https://travis-ci.org/getnelson/platypus.svg?branch=master)](https://travis-ci.org/getnelson/platypus) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.getnelson.platypus/core_2.11/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.getnelson.platypus/core_2.11) 5 | [![codecov](https://codecov.io/gh/getnelson/platypus/branch/master/graph/badge.svg)](https://codecov.io/gh/getnelson/platypus) 6 | 7 | A native Scala client for interacting with [Vault](https://www.vaultproject.io). There is currently only one supported client, which uses [http4s](https://http4s.org) to make HTTP calls to Vault. Alternative implementations could be added with relative ease by providing an additional free interpreter for the `VaultOp` algebra. 8 | 9 | ## Getting started 10 | 11 | Add the following to your `build.sbt`: 12 | 13 | libraryDependencies += "io.getnelson.platypus" %% "http4s" % "x.y.z" 14 | 15 | Where `x.y.z` is the desired Platypus version. Check for the latest release [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22io.getnelson.platypus%22). 16 | 17 | ### Algebra 18 | 19 | Platypus currently only supports a limited subset of the total actions available within the [Vault HTTP API](https://www.vaultproject.io/api/overview.html). Supported operations are iterated within the [VaultOp source](core/src/main/scala/platypus/VaultOp.scala). For example, to create a new policy one can construct the operation as such: 20 | 21 | ```scala 22 | import platypus._ 23 | 24 | val fooReadOnly = Rule( 25 | path = "secret/foo", 26 | capabilities = "read" :: Nil, 27 | policy = None 28 | ) 29 | 30 | val createMyPolicy: VaultOpF[Unit] = VaultOp 31 | .createPolicy( 32 | name = "my-policy", 33 | rules = fooReadOnly :: Nil 34 | ) 35 | ``` 36 | 37 | This however is just a description of what operation the program might perform in the future, just creating these operations does not 38 | actually execute the operations. In order to create the policy, we need to use the [http4s](http://http4s.org) interpreter. 39 | 40 | ### http4s Interpreter 41 | 42 | First we create an interpreter, which requires a [Vault token](https://www.vaultproject.io/docs/concepts/tokens.html), an http4s client, and 43 | a base url for Vault: 44 | 45 | ```scala 46 | import cats.effect.IO 47 | import org.http4s.Uri.uri 48 | import org.http4s.client.blaze.Http1Client 49 | import platypus._ 50 | import platypus.http4s._ 51 | 52 | val token = Token("1c1cb196-a03c-4336-bfac-d551849e11de") 53 | val client = Http1Client[IO]().unsafeRunSync 54 | val baseUrl = uri("http://127.0.0.1:8200") 55 | 56 | val interpreter = new Http4sVaultClient(baseUrl, client) 57 | ``` 58 | 59 | Now we can apply commands to our http4s client to get back IOs 60 | which actually interact with Vault: 61 | 62 | ```scala 63 | import cats.effect.IO 64 | 65 | val c: IO[Unit] = platypus.run(interpreter, createMyPolicy) 66 | 67 | // actually execute the calls 68 | c.unsafeRunSync 69 | ``` 70 | 71 | Typically, the *Platypus* algebra would be a part of a `Coproduct` with other algebras in a larger program, so running the `IO` immediately after `platypus.run` is not typical. 72 | 73 | ## Supported Vault Versions 74 | - 0.10.x 75 | 76 | ## Contributing 77 | 78 | Contributions are welcome; particularly to expand the algebra with additional operations that are supported by Vault but not yet supported by *Platypus*. 79 | -------------------------------------------------------------------------------- /http4s/src/main/scala/platypus/http4s/Json.scala: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | package platypus 18 | package http4s 19 | 20 | import argonaut._ 21 | import Argonaut._ 22 | import argonaut.DecodeResultCats._ 23 | import cats.instances.stream._ 24 | import cats.syntax.foldable._ 25 | 26 | import scala.collection.immutable.SortedMap 27 | 28 | trait Json { 29 | import VaultOp._ 30 | 31 | implicit val encodeCreatePolicyJson: EncodeJson[CreatePolicy] = EncodeJson { cp => 32 | def capabilities(rule: Rule) = 33 | rule.capabilities match { 34 | case Nil => None 35 | case cs => Some(cs) 36 | } 37 | 38 | def path(rule: Rule): (String, argonaut.Json) = 39 | rule.path := { 40 | ("policy" :?= rule.policy) ->?: 41 | ("capabilities" :?= capabilities(rule)) ->?: 42 | jEmptyObject 43 | } 44 | 45 | jSingleObject("policy", 46 | jString( // So, yeah, rules is embedded HCL/JSON as a string. 47 | jSingleObject("path", jObjectFields(cp.rules.map(path): _*)).nospaces)) 48 | } 49 | 50 | implicit val jsonInitialized: DecodeJson[Initialized] = DecodeJson { c => 51 | for { 52 | i <- (c --\ "initialized").as[Boolean] 53 | } yield Initialized(i) 54 | } 55 | 56 | def jsonMount(path: String): DecodeJson[Mount] = DecodeJson { c => 57 | for { 58 | defaultLease <- (c --\ "config" --\ "default_lease_ttl").as[Int] 59 | maxLease <- (c --\ "config" --\ "max_lease_ttl").as[Int] 60 | typ <- (c --\ "type").as[String] 61 | desc <- (c --\ "description").as[String] 62 | } yield Mount(path, typ, desc, defaultLease, maxLease) 63 | } 64 | 65 | implicit val jsonMountMap: DecodeJson[SortedMap[String, Mount]] = DecodeJson { c => 66 | def go(obj: JsonObject): DecodeResult[SortedMap[String, Mount]] = 67 | obj.toMap.toStream.foldLeftM[DecodeResult, SortedMap[String, Mount]](SortedMap.empty) { 68 | // mounts end with '/'. Starting circa vault-0.6.2, this response includes keys that aren't mounts. */ => 69 | case (res, (jf, js)) if jf.endsWith("/") => 70 | jsonMount(jf).decodeJson(js).flatMap(m => DecodeResult.ok(res + (jf -> m))) 71 | case (res, _) => 72 | DecodeResult.ok(res) 73 | } 74 | 75 | c.focus.obj.fold[DecodeResult[SortedMap[String, Mount]]](DecodeResult.fail("expected mounts to be a JsonObject", c.history))(go) 76 | } 77 | 78 | implicit val jsonRootToken: DecodeJson[RootToken] = implicitly[DecodeJson[String]].map(RootToken.apply) 79 | 80 | implicit val jsonInitialCreds: DecodeJson[InitialCreds] = DecodeJson[InitialCreds] { c => 81 | for { 82 | k <- (c --\ "keys").as[List[MasterKey]] 83 | t <- (c --\ "root_token").as[RootToken] 84 | } yield InitialCreds(k,t) 85 | } 86 | 87 | implicit val jsonInitialization: CodecJson[Initialization] = casecodec2(Initialization.apply,Initialization.unapply)("secret_shares", "secret_threshold") 88 | 89 | implicit val jsonSealStatus: DecodeJson[SealStatus] = casecodec4(SealStatus.apply, SealStatus.unapply)("sealed", "n", "t", "progress") 90 | 91 | val jsonUnseal: EncodeJson[String] = EncodeJson { s => 92 | ("key" := s) ->: jEmptyObject 93 | } 94 | 95 | implicit val jsonCreateToken: EncodeJson[CreateToken] = EncodeJson { ct => 96 | ("policies" :?= ct.policies) ->?: 97 | ("renewable" := ct.renewable) ->: 98 | ("ttl" :?= ct.ttl.map(d => s"${d.toMillis}ms")) ->?: 99 | ("num_uses" := ct.numUses) ->: 100 | jEmptyObject 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /http4s/src/main/scala/platypus/http4s/Http4sVault.scala: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2014 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | package platypus 18 | package http4s 19 | 20 | import argonaut._ 21 | import Argonaut._ 22 | import cats.{FlatMap, ~>} 23 | import cats.effect.IO 24 | import cats.syntax.functor._ 25 | import journal._ 26 | import org.http4s.{argonaut => _, _} 27 | import org.http4s.argonaut._ 28 | import org.http4s.client._ 29 | 30 | import scala.collection.immutable.SortedMap 31 | 32 | final case class Initialized(init: Boolean) 33 | 34 | final class Http4sVaultClient(authToken: Token, 35 | baseUri: Uri, 36 | client: Client[IO]) extends (VaultOp ~> IO) with Json { 37 | 38 | import VaultOp._ 39 | import Method._ 40 | import Status._ 41 | 42 | val v1: Uri = baseUri / "v1" 43 | 44 | def apply[A](v: VaultOp[A]): IO[A] = v match { 45 | case IsInitialized => isInitialized 46 | case Initialize(init) => initialize(init) 47 | case GetSealStatus => sealStatus 48 | case Seal => seal 49 | case Unseal(key) => unseal(key) 50 | case cp @ CreatePolicy(_,_) => createPolicy(cp) 51 | case DeletePolicy(name) => deletePolicy(name) 52 | case GetMounts => getMounts 53 | case ct: CreateToken => createToken(ct) 54 | } 55 | 56 | val log = Logger[this.type] 57 | 58 | val addCreds: Request[IO] => Request[IO] = _.putHeaders(Header("X-Vault-Token", authToken.value)) 59 | 60 | def req[T: DecodeJson](req: Request[IO]): IO[T] = 61 | client.fetch(addCreds(req)){ 62 | case Ok(resp) => resp.as(FlatMap[IO], jsonOf[IO, T]) 63 | case resp => 64 | resp.as[String].flatMap(body => { 65 | val msg = s"unexpected status: ${resp.status} from request: ${req.pathInfo}, msg: ${body}" 66 | IO.raiseError(new RuntimeException(msg)) 67 | }) 68 | } 69 | 70 | def reqVoid(req: Request[IO]): IO[Unit] = 71 | client.fetch(addCreds(req)) { 72 | case NoContent(_) => IO.pure(()) 73 | case resp => 74 | resp.as[String].flatMap(body => { 75 | val msg = s"unexpected status: ${resp.status} from request: ${req.pathInfo}, msg: ${body}" 76 | IO.raiseError(new RuntimeException(msg)) 77 | }) 78 | } 79 | 80 | def isInitialized: IO[Boolean] = 81 | req[Initialized](Request(GET, v1 / "sys" / "init")).map(_.init) 82 | 83 | def initialize(init: Initialization): IO[InitialCreds] = 84 | req[InitialCreds](Request(PUT, v1 / "sys" / "init").withEntity(init.asJson)) 85 | 86 | def unseal(key: MasterKey): IO[SealStatus] = 87 | req[SealStatus](Request(PUT, v1 / "sys" / "unseal").withEntity(jsonUnseal(key))) 88 | 89 | def sealStatus: IO[SealStatus] = 90 | req[SealStatus](Request(GET, v1 / "sys" / "seal-status")) 91 | 92 | def seal: IO[Unit] = 93 | req[String](Request(GET, v1 / "sys" / "init")).void 94 | 95 | def createPolicy(cp: CreatePolicy): IO[Unit] = 96 | reqVoid(Request(POST, v1 / "sys" / "policy" / cp.name).withEntity(cp.asJson)) 97 | 98 | def deletePolicy(name: String): IO[Unit] = 99 | reqVoid(Request(DELETE, v1 / "sys" / "policy" / name)) 100 | 101 | def getMounts: IO[SortedMap[String, Mount]] = 102 | req[SortedMap[String, Mount]](Request(GET, uri = v1 / "sys" / "mounts")) 103 | 104 | def createToken(ct: CreateToken): IO[Token] = 105 | req[argonaut.Json](Request(POST, v1 / "auth" / "token" / "create").withEntity(ct.asJson)).flatMap { json => 106 | val clientToken = for { 107 | cursor <- Some(json.cursor): Option[Cursor] 108 | auth <- cursor.downField("auth") 109 | token <- auth.downField("client_token") 110 | str <- token.focus.string 111 | } yield str 112 | 113 | clientToken match { 114 | case Some(token) => IO.pure(Token(token)) 115 | case None => IO.raiseError(new RuntimeException("No auth/client_token in create token response")) 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /http4s/src/test/scala/platypus/http4s/Http4sVaultSpec.scala: -------------------------------------------------------------------------------- 1 | //: ---------------------------------------------------------------------------- 2 | //: Copyright (C) 2017 Verizon. All Rights Reserved. 3 | //: 4 | //: Licensed under the Apache License, Version 2.0 (the "License"); 5 | //: you may not use this file except in compliance with the License. 6 | //: You may obtain a copy of the License at 7 | //: 8 | //: http://www.apache.org/licenses/LICENSE-2.0 9 | //: 10 | //: Unless required by applicable law or agreed to in writing, software 11 | //: distributed under the License is distributed on an "AS IS" BASIS, 12 | //: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | //: See the License for the specific language governing permissions and 14 | //: limitations under the License. 15 | //: 16 | //: ---------------------------------------------------------------------------- 17 | package platypus 18 | package http4s 19 | 20 | import argonaut.Argonaut._ 21 | import cats.effect.IO 22 | import com.whisk.docker.scalatest._ 23 | import org.http4s.Uri 24 | import org.http4s.client.blaze._ 25 | import org.scalatest.{FlatSpec, Matchers} 26 | 27 | import scala.concurrent.duration._ 28 | import scala.concurrent.ExecutionContext.Implicits.global 29 | import cats.effect._ 30 | import org.http4s._ 31 | import org.http4s.client.Client 32 | 33 | class Http4sVaultSpec extends FlatSpec 34 | with DockerTestKit 35 | with Matchers 36 | with DockerVaultService 37 | with http4s.Json { 38 | override val PullImagesTimeout = 20.minutes 39 | override val StartContainersTimeout = 1.minute 40 | override val StopContainersTimeout = 1.minute 41 | 42 | def vaultHost: Option[Uri] = 43 | for { 44 | host <- Option(dockerExecutor.host) 45 | yolo <- Uri.fromString(s"http://$host:8200").toOption 46 | } yield yolo 47 | 48 | var masterKey: MasterKey = _ 49 | var rootToken: RootToken = _ 50 | 51 | type Interpreter = Client[IO] => Http4sVaultClient 52 | 53 | var interp: Interpreter = _ 54 | 55 | val baseUrl: Uri = vaultHost getOrElse Uri.uri("http://0.0.0.0:8200") 56 | 57 | implicit val cs: ContextShift[IO] = IO.contextShift(global) 58 | implicit val timer: Timer[IO] = IO.timer(global) 59 | 60 | val resource: Resource[IO, Client[IO]] = BlazeClientBuilder[IO](global).resource 61 | 62 | val token = Token("asdf") 63 | interp = client => new Http4sVaultClient(token, baseUrl, client) 64 | 65 | def withClient[A](op : VaultOpF[A])(interpreter:Interpreter):IO[A] = resource.use( client => op.foldMap(interpreter(client))) 66 | 67 | if (sys.env.get("BUILDKITE").isEmpty) { 68 | behavior of "vault" 69 | 70 | it should "not be initialized" in { 71 | withClient(VaultOp.isInitialized)(interp).unsafeRunSync() should be(false) 72 | } 73 | 74 | it should "initialize" in { 75 | val result = withClient(VaultOp.initialize(1, 1))(interp).unsafeRunSync() 76 | result.keys.size should be(1) 77 | this.masterKey = result.keys(0) 78 | this.rootToken = result.rootToken 79 | this.interp = client => new Http4sVaultClient(Token(rootToken.value), baseUrl, client) 80 | } 81 | 82 | it should "be initialized now" in { 83 | withClient(VaultOp.isInitialized)(interp).unsafeRunSync() should be(true) 84 | } 85 | 86 | it should "be sealed at startup" in { 87 | val sealStatus = withClient(VaultOp.sealStatus)(interp).unsafeRunSync() 88 | sealStatus.`sealed` should be(true) 89 | sealStatus.total should be(1) 90 | sealStatus.progress should be(0) 91 | sealStatus.quorum should be(1) 92 | } 93 | 94 | it should "be unsealable" in { 95 | val sealStatus = withClient(VaultOp.unseal(this.masterKey))(interp).unsafeRunSync() 96 | sealStatus.`sealed` should be(false) 97 | sealStatus.total should be(1) 98 | sealStatus.progress should be(0) 99 | sealStatus.quorum should be(1) 100 | } 101 | 102 | it should "be unsealed after unseal" in { 103 | val sealStatus = withClient(VaultOp.sealStatus)(interp).unsafeRunSync() 104 | sealStatus.`sealed` should be(false) 105 | sealStatus.total should be(1) 106 | sealStatus.progress should be(0) 107 | sealStatus.quorum should be(1) 108 | } 109 | 110 | it should "have cubbyhole, secret, sys mounted" in { 111 | val mounts = withClient(VaultOp.getMounts)(interp).attempt.unsafeRunSync() 112 | mounts.toOption.get.size should be(4) 113 | mounts.toOption.get.contains("cubbyhole/") should be(true) 114 | mounts.toOption.get.contains("secret/") should be(true) 115 | mounts.toOption.get.contains("identity/") should be(true) 116 | mounts.toOption.get.contains("sys/") should be(true) 117 | } 118 | 119 | // This is how platypus writes policies. It provides a good test case for us. 120 | val StaticRules = List( 121 | Rule("sys/*", policy = Some("deny"), capabilities = Nil), 122 | Rule("auth/token/revoke-self", policy = Some("write"), capabilities = Nil) 123 | ) 124 | val cp: VaultOp.CreatePolicy = 125 | VaultOp.CreatePolicy( 126 | name = s"qa__howdy", 127 | rules = StaticRules ::: 128 | List("example/qa/mysql", "example/qa/cassandra").map { resource => 129 | Rule( 130 | path = s"${resource}/creds/howdy", 131 | capabilities = List("read"), 132 | policy = None 133 | ) 134 | } 135 | ) 136 | 137 | it should "write policies" in { 138 | withClient(VaultOp.createPolicy(cp.name, cp.rules))(interp).unsafeRunSync() should be(()) 139 | } 140 | 141 | it should "delete policies" in { 142 | withClient(VaultOp.deletePolicy(cp.name))(interp).unsafeRunSync() should be(()) 143 | } 144 | 145 | it should "encode policies correctly" in { 146 | cp.asJson.field("policy") should be(Some(jString("""{"path":{"sys/*":{"policy":"deny"},"auth/token/revoke-self":{"policy":"write"},"example/qa/mysql/creds/howdy":{"capabilities":["read"]},"example/qa/cassandra/creds/howdy":{"capabilities":["read"]}}}"""))) 147 | } 148 | 149 | it should "create tokens" in { 150 | val token2 = withClient(VaultOp.createToken( 151 | policies = Some(List("default")), 152 | ttl = Some(1.minute) 153 | ))(interp).unsafeRunSync() 154 | val interp2 : Interpreter = client => new Http4sVaultClient(token2, baseUrl, client) 155 | withClient(VaultOp.isInitialized)(interp2).unsafeRunSync() should be(true) 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner]. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. --------------------------------------------------------------------------------