├── 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 | [](https://travis-ci.org/getnelson/platypus)
4 | [](https://maven-badges.herokuapp.com/maven-central/io.getnelson.platypus/core_2.11)
5 | [](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.
--------------------------------------------------------------------------------