Skip to content

Commit

Permalink
Merge pull request #70 from amanjpro/feature/add-secure-env-variables
Browse files Browse the repository at this point in the history
Add EnvVar to represent Environment variables
  • Loading branch information
amanjpro authored Aug 27, 2020
2 parents e13e119 + afca586 commit 7712b56
Show file tree
Hide file tree
Showing 12 changed files with 227 additions and 40 deletions.
4 changes: 3 additions & 1 deletion src/main/scala/AppConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ object AppConfig {
val startAt = jobConfig.getLongWithDefault(
"start-at", groupStartAt)
val jobInfo = jobConfig.getOptionStringWithDefault("job-info", groupInfo)
val jobEnv = jobConfig.getEnv("env", groupEnv)
val jobEnv = jobConfig.getEnv("env", groupEnv).map {
case (name, value) => EnvVar(name, value)
}

Job(
index,
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/checker/StatusChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class StatusChecker(groups: Seq[Group],
val currentClockCounter = clockCounter()
val expireAt = currentClockCounter + 1000 *
refreshValidityInSeconds
self ! BatchRun(job.cmd, periods, job.env,
self ! BatchRun(job.cmd, periods, job.env.map(_.tupled),
group.groupId, job.jobId, job.prometheusId,
currentClockCounter, expireAt)
}
Expand Down
88 changes: 88 additions & 0 deletions src/main/scala/models/EnvVar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package me.amanj.greenish.models

import io.circe.syntax.EncoderOps
import io.circe.{Encoder, Decoder, HCursor, Json}

sealed trait EnvVar {
type T <: AnyRef
def name: String
def value: T
def tupled: (String, String)
}
object EnvVar {
private[this] val pattern = """secure\((.*)\)""".r
def apply(key: String, value: String): EnvVar = {
value match {
case pattern(v) => SecureEnvVar(key, v.toSeq)
case _ => PlainEnvVar(key, value)
}
}

implicit val envVarDecoer: Decoder[EnvVar] = new Decoder[EnvVar] {
final def apply(obj: HCursor): Decoder.Result[EnvVar] = {
obj.downField("type").as[String].flatMap {
case "secure" => obj.as[SecureEnvVar]
case "plain" => obj.as[PlainEnvVar]
}
}
}

implicit val envVarEncoder: Encoder[EnvVar] = Encoder.instance {
case sec: SecureEnvVar => sec.asJson
case plain: PlainEnvVar => plain.asJson
}
}

private[models] case class SecureEnvVar(name: String, value: Seq[Char]) extends EnvVar {
type T = Seq[Char]
def tupled: (String, String) = (name, value.mkString(""))
}

private[models] object SecureEnvVar {
val HIDDEN_PASSWORD = "****"
implicit val secureEnvVarEncoder: Encoder[SecureEnvVar] =
new Encoder[SecureEnvVar] {
final def apply(v: SecureEnvVar): Json = Json.obj(
("type", Json.fromString("secure")),
("name", Json.fromString(v.name)),
("value", Json.fromString(HIDDEN_PASSWORD)),
)
}

implicit val secureEnvVarDecoder: Decoder[SecureEnvVar] = new Decoder[SecureEnvVar] {
final def apply(c: HCursor): Decoder.Result[SecureEnvVar] =
c.downField("type").as[String].flatMap {
case "secure" =>
for {
name <- c.downField("name").as[String]
value <- c.downField("value").as[String].map(_.toSeq)
} yield SecureEnvVar(name, value)
}
}
}

private[models] case class PlainEnvVar(name: String, value: String) extends EnvVar {
type T = String
def tupled: (String, String) = (name, value)
}
private[models] object PlainEnvVar {
implicit val plainEnvVarEncoder: Encoder[PlainEnvVar] =
new Encoder[PlainEnvVar] {
final def apply(v: PlainEnvVar): Json = Json.obj(
("type", Json.fromString("plain")),
("name", Json.fromString(v.name)),
("value", Json.fromString(v.value)),
)
}

implicit val secureEnvVarDecoder: Decoder[PlainEnvVar] = new Decoder[PlainEnvVar] {
final def apply(c: HCursor): Decoder.Result[PlainEnvVar] =
c.downField("type").as[String].flatMap {
case "plain" =>
for {
name <- c.downField("name").as[String]
value <- c.downField("value").as[String]
} yield PlainEnvVar(name, value)
}
}
}
8 changes: 4 additions & 4 deletions src/main/scala/models/Job.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ case class Job(
startAt: Long,
alertLevels: AlertLevels,
info: Option[String],
env: Seq[(String, String)]
env: Seq[EnvVar]
) {
val timeFormat = DateTimeFormatter.ofPattern(timePattern)
}
Expand All @@ -37,8 +37,8 @@ object Job {
for {
zoneId <- c.downField("zone_id").as[String]
} yield ZoneId.of(zoneId)
}
}

implicit val checkEntryDecoder: Decoder[Job] = deriveConfiguredDecoder
implicit val checkEntryEncoder: Encoder[Job] = deriveConfiguredEncoder
implicit val jobDecoder: Decoder[Job] = deriveConfiguredDecoder
implicit val jobEncoder: Encoder[Job] = deriveConfiguredEncoder
}
4 changes: 3 additions & 1 deletion src/test/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ check-groups: {
# `env` can be set on both grup and job level
env: {
VAR1: "foo"
VAR2: "bar"
# `secure(...)` pattern tells Greenish that the data should be kept
# secret and not returned in any of the endpoints.
VAR2: "secure(bar)"
}
# Job groups, a group is a set of jobs/data-sets
# that have some sort of logical relation
Expand Down
24 changes: 14 additions & 10 deletions src/test/scala/AppConfigSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,32 @@ class AppConfigSpec() extends Matchers
ZoneId.of("UTC"), 24, 2,
AlertLevels(0, 1, 2, 3),
Some("Job info"),
Seq("VAR1" -> "baz", "VAR2" -> "bazomba",
"VAR3" -> "bada", "VAR4" -> "badam"),
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "bazomba"),
EnvVar("VAR3", "bada"), EnvVar("VAR4", "badam")),
),
Job(1, "Job2", Some("Reporting"), "job_2", "/tmp/second_script job2",
"yyyy-MM-dd-HH", Daily, 2,
ZoneId.of("UTC"), 24, 1,
AlertLevels(0, 1, 2, 3),
Some("Group info"),
Seq("VAR1" -> "baz", "VAR2" -> "bar", "VAR3" -> "bazooka"),
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "secure(bar)"),
EnvVar("VAR3", "bazooka")),
),
Job(2, "Job5", Some("Reporting"), "group1_job5", "/tmp/second_script job5",
"yyyy-MM-dd-HH", Hourly, 2,
ZoneId.of("US/Alaska"), 24, 1,
AlertLevels(0, 1, 2, 3),
Some("Group info"),
Seq("VAR1" -> "baz", "VAR2" -> "bar", "VAR3" -> "bazooka"),
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "secure(bar)"),
EnvVar("VAR3", "bazooka")),
),
Job(3, "Job7", Some("Reporting"), "group1_job7", "/tmp/second_script job7",
"yyyy-MM-dd-HH", Cron("0 * * * *"), 2,
ZoneId.of("US/Alaska"), 24, 1,
AlertLevels(0, 1, 2, 3),
Some("Group info"),
Seq("VAR1" -> "baz", "VAR2" -> "bar", "VAR3" -> "bazooka"),
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "secure(bar)"),
EnvVar("VAR3", "bazooka")),
),
)),
Group(1, "Group2", Seq(
Expand All @@ -53,7 +56,7 @@ class AppConfigSpec() extends Matchers
Some("""|<a href="link">
|Link
|</a>""".stripMargin),
Seq("VAR1" -> "foo", "VAR2" -> "bar"),
Seq(EnvVar("VAR1", "foo"), EnvVar("VAR2", "secure(bar)")),
),
Job(1, "Job4", Some("SRE"), "job_4", "/tmp/fourth_script",
"yyyy-01-01", Annually, 1,
Expand All @@ -62,7 +65,7 @@ class AppConfigSpec() extends Matchers
Some("""|<a href="link">
|Link
|</a>""".stripMargin),
Seq("VAR1" -> "foo", "VAR2" -> "bar"),
Seq(EnvVar("VAR1", "foo"), EnvVar("VAR2", "secure(bar)")),
),
Job(2, "Job6", Some("SRE"), "group2_job6", "/tmp/second_script job6",
"yyyy-MM-dd-HH-mm", Daily, 1,
Expand All @@ -71,7 +74,8 @@ class AppConfigSpec() extends Matchers
Some("""|<a href="link">
|Link
|</a>""".stripMargin),
Seq("VAR1" -> "baz", "VAR2" -> "bar", "VAR3" -> "bazooka"),
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "secure(bar)"),
EnvVar("VAR3", "bazooka")),
),
)),
),
Expand Down Expand Up @@ -192,12 +196,12 @@ class AppConfigSpec() extends Matchers
val appEnv = appConfig.getEnv("env", Seq.empty)

"get value if parent is empty, and key exists" in {
appEnv shouldBe Seq("VAR1" -> "foo", "VAR2" -> "bar")
appEnv shouldBe Seq("VAR1" -> "foo", "VAR2" -> "secure(bar)")
}

"properly dedup parent and child lists, if key exists" in {
val actualGroup = groupConfig.getEnv("env", appEnv)
val expectedGroup = Seq("VAR1" -> "baz", "VAR2" -> "bar",
val expectedGroup = Seq("VAR1" -> "baz", "VAR2" -> "secure(bar)",
"VAR3" -> "bazooka")

actualGroup shouldBe expectedGroup
Expand Down
24 changes: 16 additions & 8 deletions src/test/scala/AppSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,31 @@ class AppSpec() extends Matchers
"yyyy-MM-dd-HH", Hourly, 3,
ZoneId.of("UTC"), 24, 0,
AlertLevels(0, 1, 2, 3),
None, Seq("VAR1" -> "foo", "VAR2" -> "bar"),
None,
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "secure(bar)")),
),
Job(1, "Job2", None, "job_1", "/tmp/second_script job2",
"yyyy-MM-dd-HH", Daily, 2,
ZoneId.of("UTC"), 24, 0,
AlertLevels(0, 1, 2, 3),
None, Seq("VAR1" -> "foo", "VAR2" -> "bar"),
None,
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "secure(bar)")),
),
)),
Group(1, "Group2", Seq(
Job(0, "Job3", None, "job_2", "/tmp/third_script",
"yyyy-MM-dd", Monthly, 1,
ZoneId.of("UTC"), 3, 0,
AlertLevels(0, 1, 2, 3),
None, Seq("VAR1" -> "foo", "VAR2" -> "bar"),
None,
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "secure(bar)")),
),
Job(1, "Job4", None, "job_2", "/tmp/fourth_script",
"yyyy-01-01", Annually, 1,
ZoneId.of("UTC"), 3, 0,
AlertLevels(0, 1, 2, 3),
None, Seq("VAR1" -> "foo", "VAR2" -> "bar"),
None,
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "secure(bar)")),
),
)),
),
Expand All @@ -63,27 +67,31 @@ class AppSpec() extends Matchers
"yyyy-MM-dd-HH", Hourly, 3,
ZoneId.of("UTC"), 24, 0,
AlertLevels(0, 1, 2, 3),
None, Seq("VAR1" -> "foo", "VAR2" -> "bar"),
None,
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "secure(bar)")),
),
Job(1, "Job2", None, "job_2", "/tmp/second_script job2",
"yyyy-MM-dd-HH", Daily, 2,
ZoneId.of("UTC"), 24, 0,
AlertLevels(0, 1, 2, 3),
None, Seq("VAR1" -> "foo", "VAR2" -> "bar"),
None,
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "secure(bar)")),
),
)),
Group(1, "Group2", Seq(
Job(0, "Job3", None, "job_3", "/tmp/third_script",
"yyyy-MM-dd", Monthly, 1,
ZoneId.of("UTC"), 3, 0,
AlertLevels(0, 1, 2, 3),
None, Seq("VAR1" -> "foo", "VAR2" -> "bar"),
None,
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "secure(bar)")),
),
Job(1, "Job4", None, "job_4", "/tmp/fourth_script",
"yyyy-01-01", Annually, 1,
ZoneId.of("UTC"), 3, 0,
AlertLevels(0, 1, 2, 3),
None, Seq("VAR1" -> "foo", "VAR2" -> "bar"),
None,
Seq(EnvVar("VAR1", "baz"), EnvVar("VAR2", "secure(bar)")),
),
)),
),
Expand Down
8 changes: 4 additions & 4 deletions src/test/scala/checker/StatusCheckerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,28 @@ class StatusCheckerSpec()
"yyyy-MM-dd-HH", Hourly, 1, ZoneId.of("UTC"),
2, 0, AlertLevels(0, 1, 2, 3),
None,
Seq("GREENISH_VALUE_FOR_TEST" -> "/tmp"),
Seq(EnvVar("GREENISH_VALUE_FOR_TEST", "secure(/tmp)")),
)

val job2 = Job(1, "job2", None, "p2", s"$lsScript /tmp/job2",
"yyyy-MM-dd-HH", Hourly, 2, ZoneId.of("UTC"),
1, 0, AlertLevels(1, 2, 3, 4),
None,
Seq("GREENISH_VALUE_FOR_TEST" -> "/tmp"),
Seq(EnvVar("GREENISH_VALUE_FOR_TEST", "/tmp")),
)

val job3 = Job(0, "job3", None, "p3", s"$lsScript /tmp/job3",
"yyyy-MM-dd-HH", Hourly, 1, ZoneId.of("UTC"),
3, 0, AlertLevels(0, 1, 2, 3),
None,
Seq("GREENISH_VALUE_FOR_TEST" -> "/tmp"),
Seq(EnvVar("GREENISH_VALUE_FOR_TEST", "/tmp")),
)

val job4 = Job(1, "job4", None, "p4", s"$lsEnvScript job4",
"yyyy-MM-dd-HH", Hourly, 1, ZoneId.of("UTC"),
4, 0, AlertLevels(0, 1, 2, 3),
None,
Seq("GREENISH_VALUE_FOR_TEST" -> "/tmp"),
Seq(EnvVar("GREENISH_VALUE_FOR_TEST", "/tmp")),
)

val group1 = Group(0, "group1", Seq(job1, job2))
Expand Down
6 changes: 3 additions & 3 deletions src/test/scala/endpoints/RoutesSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ class RoutesSpec()

val job1 = Job(0, "job1", None, "p1", s"$lsScript /tmp",
"yyyy-MM-dd-HH", Hourly, 1, ZoneId.of("UTC"),
2, 0, AlertLevels(0, 1, 2, 3), None, Seq("a" -> "b"),
2, 0, AlertLevels(0, 1, 2, 3), None, Seq(EnvVar("a", "b")),
)

val job2 = Job(1, "job2", None, "p2", s"$lsScript /tmp",
"yyyy-MM-dd-HH", Hourly, 1, ZoneId.of("UTC"),
2, 0, AlertLevels(0, 1, 2, 3), None, Seq("a" -> "b"),
2, 0, AlertLevels(0, 1, 2, 3), None, Seq(EnvVar("a", "b")),
)

val job3 = Job(0, "job3", None, "p3", s"$lsScript /tmp",
"yyyy-MM-dd-HH", Hourly, 1, ZoneId.of("UTC"),
2, 0, AlertLevels(0, 1, 2, 3), None, Seq("a" -> "b"),
2, 0, AlertLevels(0, 1, 2, 3), None, Seq(EnvVar("a", "b")),
)

val group1 = Group(0, "group1", Seq(job1, job2))
Expand Down
48 changes: 48 additions & 0 deletions src/test/scala/models/EnvVarSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package me.amanj.greenish.models

import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike

class EnvVarSpec() extends Matchers with AnyWordSpecLike {
"EnvVar.apply" must {
"create PlainEnvVar when secure flag is not provided" in {
val (name, value) = ("username", "Homa")
val expected = PlainEnvVar(name, value)
val actual = EnvVar(name, value)
actual shouldBe expected
}

"create SecureEnvVar when secure flag is provided" in {
val (name, value) = ("username", "secure(Homa)")
val expected = SecureEnvVar(name, "Homa".toSeq)
val actual = EnvVar(name, value)
actual shouldBe expected
}

"create SecureEnvVar when secure flag is provided but value is empty" in {
val (name, value) = ("username", "secure()")
val expected = SecureEnvVar(name, "".toSeq)
val actual = EnvVar(name, value)
actual shouldBe expected
}
}

"EnvVar.tupled" must {
"work for secure variables" in {
val (name, value) = ("username", "secure(Homa)")
val origin = EnvVar(name, value)
val expected = (name, "Homa")
val actual = origin.tupled
actual shouldBe expected
}

"work for plain variables" in {
val (name, value) = ("username", "Homa")
val origin = EnvVar(name, value)
val expected = (name, value)
val actual = origin.tupled
actual shouldBe expected
}
}
}

Loading

0 comments on commit 7712b56

Please sign in to comment.