Skip to content

Commit

Permalink
Merge pull request #226 from Banno/merge-7-to-8-on-2021-07-02
Browse files Browse the repository at this point in the history
Merge 7.x to 8.x
  • Loading branch information
rossabaker authored Jul 2, 2021
2 parents ef14532 + f4143ea commit 2954084
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 100 deletions.
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ val http4sV = "0.22.0-RC1"
val specs2V = "4.10.6"
val munitCatsEffectV = "1.0.5"
val munitScalaCheckV = "0.7.26"
val scalacheckEffectV = "1.0.2"

val kindProjectorV = "0.13.0"
val betterMonadicForV = "0.3.1"
Expand Down Expand Up @@ -156,7 +157,8 @@ lazy val commonSettings = Seq(

"org.http4s" %% "http4s-dsl" % http4sV % Test,
"org.typelevel" %% "munit-cats-effect-2" % munitCatsEffectV % Test,
"org.scalameta" %% "munit-scalacheck" % munitScalaCheckV % Test
"org.scalameta" %% "munit-scalacheck" % munitScalaCheckV % Test,
"org.typelevel" %% "scalacheck-effect" % scalacheckEffectV % Test,

) ++ {
if(scalaVersion.value.startsWith("3")) List.empty
Expand Down
40 changes: 40 additions & 0 deletions core/src/test/scala/com/banno/vault/MissingPieces.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.banno.vault

import cats.MonadThrow
import org.scalacheck.Prop._
import org.scalacheck.effect.PropF

/** Things missing in scalacheck-effect */
trait MissingPieces {

implicit class PropFExtensions[F[_]](self: PropF[F]) {
def ==>(p: => PropF[F])(implicit F: MonadThrow[F]): PropF[F] =
self.flatMap { res =>
res.status match {
case Proof => p.map { pRes => mergeResults(pRes.status, res, pRes) }
case True => p.map { mergeResults(True, res, _) }
case _ => res.copy(status = Undecided)
}
}

def label(l: String)(implicit F: MonadThrow[F]) =
self.map(r => r.copy(labels = r.labels + l))
}

implicit class ResultExtensions[F[_]](self: PropF.Result[F]) {
def success = self.status match {
case True => true
case Proof => true
case _ => false
}
def proved = self.status == Proof
}

private def mergeResults[F[_]: MonadThrow](st: Status, x: PropF.Result[F], y: PropF.Result[F]) =
PropF.Result[F](
status = st,
args = x.args ++ y.args,
collected = x.collected ++ y.collected,
labels = x.labels ++ y.labels
)
}
195 changes: 96 additions & 99 deletions core/src/test/scala/com/banno/vault/VaultSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ import org.http4s.circe._
import org.http4s.client.Client

import scala.concurrent.duration._
import munit.ScalaCheckSuite
import munit.{CatsEffectSuite, ScalaCheckSuite}
import org.scalacheck._
import scala.util.Random
import org.scalacheck.Prop._
import org.scalacheck.effect.PropF
import org.typelevel.ci.CIString

class VaultSpec extends ScalaCheckSuite {
class VaultSpec extends CatsEffectSuite with ScalaCheckSuite with MissingPieces {

case class RoleId(role_id: String)
object RoleId {
Expand Down Expand Up @@ -290,192 +290,189 @@ class VaultSpec extends ScalaCheckSuite {

val mockClient : Client[IO] = Client.fromHttpApp(mockVaultService[IO].orNotFound)

property("login works as expected when sending a valid roleId") {
Prop.forAll(VaultArbitraries.validVaultUri) { uri =>
Vault.login(mockClient, uri)(validRoleId).unsafeRunSync() == validToken
test("login works as expected when sending a valid roleId") {
PropF.forAllF(VaultArbitraries.validVaultUri) { uri =>
Vault.login(mockClient, uri)(validRoleId).assertEquals(validToken)
}
}
property("login should fail when sending an invalid roleId") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>

test("login should fail when sending an invalid roleId") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
Vault.login(mockClient, uri)(UUID.randomUUID().toString)
.attempt
.unsafeRunSync()
.isLeft
.map(_.isLeft)
.assert
}
}
property("login should fail when the response is not a valid") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>

test("login should fail when the response is not a valid") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
Vault.login(mockClient, uri)(invalidJSONRoleId)
.attempt
.unsafeRunSync()
.isLeft
.map(_.isLeft)
.assert
}
}
property("login should fail when the response doesn't contains a token") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>

test("login should fail when the response doesn't contains a token") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
import org.http4s.DecodeFailure
Vault.login(mockClient, uri)(roleIdWithoutToken)
.attempt
.unsafeRunSync()
.leftMap(_.isInstanceOf[DecodeFailure]) == Left(true)
.map(_.leftMap(_.isInstanceOf[DecodeFailure]))
.assertEquals(Left(true))
}
}
property("login should fail when the response doesn't contains a lease duration") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>

test("login should fail when the response doesn't contains a lease duration") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
import org.http4s.DecodeFailure
Vault.login(mockClient, uri)(roleIdWithoutLease)
.attempt
.unsafeRunSync()
.leftMap(_.isInstanceOf[DecodeFailure]) == Left(true)
.map(_.leftMap(_.isInstanceOf[DecodeFailure]))
.assertEquals(Left(true))
}
}

property("kubernetesLogin works as expected when sending valid role and jwt") {
Prop.forAll(VaultArbitraries.validVaultUri) { uri =>
Vault.kubernetesLogin(mockClient, uri)(validKubernetesRole, validKubernetesJwt).unsafeRunSync() == validToken
test("kubernetesLogin works as expected when sending valid role and jwt") {
PropF.forAllF(VaultArbitraries.validVaultUri) { uri =>
Vault.kubernetesLogin(mockClient, uri)(validKubernetesRole, validKubernetesJwt).assertEquals(validToken)
}
}
}

property("kubernetesLogin should fail when sending an invalid roleId") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>
test("kubernetesLogin should fail when sending an invalid roleId") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
Vault.kubernetesLogin(mockClient, uri)(UUID.randomUUID().toString, validKubernetesJwt)
.attempt
.unsafeRunSync()
.isLeft
.map(_.isLeft)
.assert
}
}

property("kubernetesLogin should fail when the response is not a valid JSON") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>
test("kubernetesLogin should fail when the response is not a valid JSON") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
Vault.kubernetesLogin(mockClient, uri)(invalidJSONRoleId, validKubernetesJwt)
.attempt
.unsafeRunSync()
.isLeft
.map(_.isLeft)
.assert
}
}

property("kubernetesLogin should fail when the response doesn't contains a token") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>
test("kubernetesLogin should fail when the response doesn't contains a token") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
import org.http4s.DecodeFailure
Vault.kubernetesLogin(mockClient, uri)(roleIdWithoutToken, validKubernetesJwt)
.attempt
.unsafeRunSync()
.leftMap(_.isInstanceOf[DecodeFailure]) == Left(true)
.map(_.leftMap(_.isInstanceOf[DecodeFailure]))
.assertEquals(Left(true))
}
}

property("kubernetesLogin should fail when the response doesn't contains a lease duration") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>
test("kubernetesLogin should fail when the response doesn't contains a lease duration") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
import org.http4s.DecodeFailure
Vault.kubernetesLogin(mockClient, uri)(roleIdWithoutLease, validKubernetesJwt)
.attempt
.unsafeRunSync()
.leftMap(_.isInstanceOf[DecodeFailure]) == Left(true)
.map(_.leftMap(_.isInstanceOf[DecodeFailure]))
.assertEquals(Left(true))
}
}

property("readSecret works as expected when requesting the postgres password with a valid") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>
test("readSecret works as expected when requesting the postgres password with a valid") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
Vault.readSecret[IO, VaultValue](mockClient, uri)(clientToken, secretPostgresPassPath)
.unsafeRunSync() == VaultSecret(VaultValue(postgresPass), leaseDuration.some, leaseId.some, renewable.some)
.assertEquals(VaultSecret(VaultValue(postgresPass), leaseDuration.some, leaseId.some, renewable.some))
}
}

property("readSecret works as expected when requesting the private key with a valid token") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>
test("readSecret works as expected when requesting the private key with a valid token") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
Vault.readSecret[IO, VaultValue](mockClient, uri)(clientToken, secretPrivateKeyPath)
.unsafeRunSync() == VaultSecret(VaultValue(privateKey), leaseDuration.some, leaseId.some, renewable.some)
.assertEquals(VaultSecret(VaultValue(privateKey), leaseDuration.some, leaseId.some, renewable.some))
}
}

property("readSecret works as expected when requesting the postgres password with an invalid token") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>
test("readSecret works as expected when requesting the postgres password with an invalid token") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
Vault.readSecret[IO, VaultValue](mockClient, uri)(UUID.randomUUID().toString, secretPostgresPassPath)
.attempt
.unsafeRunSync()
.isLeft
.map(_.isLeft)
.assert
}
}

property("readSecret works as expected when requesting the private key with an invalid token") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>
test("readSecret works as expected when requesting the private key with an invalid token") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
Vault.readSecret[IO, VaultValue](mockClient, uri)(UUID.randomUUID().toString, secretPrivateKeyPath)
.attempt
.unsafeRunSync()
.isLeft
.map(_.isLeft)
.assert
}
}

property("readSecret suppresses echoing the data when JSON decoding fails") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>
test("readSecret suppresses echoing the data when JSON decoding fails") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
Vault.readSecret[IO, TokenValue](mockClient, uri)(clientToken, secretPrivateKeyPath)
.attempt
.unsafeRunSync()
.fold(
{ error =>
if (error.getMessage.contains(privateKey)) Prop.falsified :| "Secret data in the error message"
else Prop.passed :| "Secret data redacted"
},
_ => Prop.falsified :| "Data should not be parseable"
.redeem(
error =>
if (error.getMessage.contains(privateKey)) PropF.falsified[IO].label("Secret data in the error message")
else PropF.passed[IO].label("Secret data redacted"),
_ => PropF.falsified[IO].label("Data should not be parseable")
)
}
}

property("listSecrets works as expected when requesting keys under path") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>
test("listSecrets works as expected when requesting keys under path") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
Vault.listSecrets[IO](mockClient, uri)(clientToken, "/secret/postgres/")
.unsafeRunSync() == VaultKeys(List("postgres1", "postgres-pupper"))
.assertEquals(VaultKeys(List("postgres1", "postgres-pupper")))
}
}

property("renewToken works as expected when sending a valid token") {
Prop.forAll(VaultArbitraries.validVaultUri){uri =>
test("renewToken works as expected when sending a valid token") {
PropF.forAllF(VaultArbitraries.validVaultUri){uri =>
Vault.renewSelfToken[IO](mockClient, uri)(VaultToken(clientToken, 3600, true), 1.hour)
.unsafeRunSync() === VaultToken(clientToken, 3600, renewable)
.assertEquals(VaultToken(clientToken, 3600, renewable))
}
}

property("revokeToken works as expected when revoking a valid token") {
Prop.forAll(VaultArbitraries.validVaultUri){ uri =>
Vault.revokeSelfToken[IO](mockClient, uri)(VaultToken(clientToken, 3600, true)).unsafeRunSync() ===( () )
test("revokeToken works as expected when revoking a valid token") {
PropF.forAllF(VaultArbitraries.validVaultUri){ uri =>
Vault.revokeSelfToken[IO](mockClient, uri)(VaultToken(clientToken, 3600, true)).assertEquals(())
}
}

property("renewLease works as expected when sending valid input arguments") {
Prop.forAll(VaultArbitraries.validVaultUri) { uri =>
Vault.renewLease(mockClient, uri)(leaseId, increment, clientToken).unsafeRunSync() == VaultSecretRenewal(leaseDuration, leaseId, renewable)
test("renewLease works as expected when sending valid input arguments") {
PropF.forAllF(VaultArbitraries.validVaultUri) { uri =>
Vault.renewLease(mockClient, uri)(leaseId, increment, clientToken).assertEquals(VaultSecretRenewal(leaseDuration, leaseId, renewable))
}
}

property("revokeLease works as expected when sending valid input arguments") {
Prop.forAll(VaultArbitraries.validVaultUri) { uri =>
Vault.revokeLease(mockClient, uri)(clientToken, leaseId).unsafeRunSync() ===( () )
test("revokeLease works as expected when sending valid input arguments") {
PropF.forAllF(VaultArbitraries.validVaultUri) { uri =>
Vault.revokeLease(mockClient, uri)(clientToken, leaseId).assertEquals(())
}
}

property("generateCertificate works as expected when sending a valid token") {
Prop.forAll(VaultArbitraries.validVaultUri, VaultArbitraries.certRequestGen) { (uri, certRequest) =>
test("generateCertificate works as expected when sending a valid token") {
PropF.forAllF(VaultArbitraries.validVaultUri, VaultArbitraries.certRequestGen) { (uri, certRequest) =>
Vault.generateCertificate(mockClient, uri)(clientToken, generateCertsPath, certRequest)
.unsafeRunSync() === VaultSecret(CertificateData(certificate, issuing_ca, List(ca_chain), private_key, private_key_type, serial_number), leaseDuration.some, leaseId.some, renewable.some)
.assertEquals(VaultSecret(CertificateData(certificate, issuing_ca, List(ca_chain), private_key, private_key_type, serial_number), leaseDuration.some, leaseId.some, renewable.some))
}
}

property("loginAndKeepSecretLeased fails when wait duration is longer than lease duration") {
Prop.forAll(
VaultArbitraries.validVaultUri,
Arbitrary.arbitrary[FiniteDuration],
Arbitrary.arbitrary[FiniteDuration]
) { case (uri, leaseDuration, waitInterval) => leaseDuration < waitInterval ==> {
import scala.concurrent.ExecutionContext.global
implicit val t = IO.timer(global)
implicit val ct = IO.contextShift(global)
test("loginAndKeepSecretLeased fails when wait duration is longer than lease duration") {
PropF.forAllF(
VaultArbitraries.validVaultUri,
Arbitrary.arbitrary[FiniteDuration],
Arbitrary.arbitrary[FiniteDuration]
) { case (uri, leaseDuration, waitInterval) => PropF.boolean[IO](leaseDuration < waitInterval) ==> {

Vault.loginAndKeepSecretLeased[IO, Unit](mockClient, uri)(validRoleId, "", leaseDuration, waitInterval)
.attempt
.compile
.last
.unsafeRunSync() == Some(Left(Vault.InvalidRequirement("waitInterval longer than requested Lease Duration")))
.attempt
.compile
.last
.assertEquals(Some(Left(Vault.InvalidRequirement("waitInterval longer than requested Lease Duration"))))
}}
}

}
}

0 comments on commit 2954084

Please sign in to comment.