Skip to content

Commit

Permalink
Merge pull request #2 from Liftric/feature/lazy
Browse files Browse the repository at this point in the history
lazy configuration and enhanced error messages
  • Loading branch information
benjohnde authored Jul 11, 2020
2 parents 1121e8f + ba63925 commit 3b4ab42
Show file tree
Hide file tree
Showing 12 changed files with 260 additions and 116 deletions.
27 changes: 19 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,24 @@ Usage example in you build script:

```kotlin
import com.liftric.vault.vault
import com.liftric.vault.GetVaultSecretTask

plugins {
id("com.liftric.vault-client-plugin") version ("<latest>")
}
vault {
vaultAddress = "http://localhost:8200"
vaultToken = "myroottoken" // don't do that in production code!
vaultTokenFilePath = "${System.getProperty("user.home")}/.vault-token" // from file is prefered over vaultToken
maxRetries = 2
retryIntervalMilliseconds = 200
vaultAddress.set("http://localhost:8200")
vaultToken.set("myroottoken") // don't do that in production code!
vaultTokenFilePath.set("${System.getProperty("user.home")}/.vault-token") // from file is prefered over vaultToken
maxRetries.set(2)
retryIntervalMilliseconds.set(200)
}
tasks {
val needsSecrets by creating {
val secrets: Map<String, String> = project.vault("secret/example")
val needsSecrets by creating(GetVaultSecretTask::class) {
secretPath.set("secret/example")
doLast {
val secrets: Map<String, String> = secret.get()
}
}
}
```
Expand Down Expand Up @@ -68,10 +72,17 @@ import com.liftric.vault.vault
import org.gradle.api.Project

object Configs {
fun Project.secretStuff(): String {
fun Project.secretStuff(): Any {
val secrets = project.vault("secret/example")
[...] // use the secrets
}
fun Project.secretStuff(): Any {
val needsSecrets: GetVaultSecretTask = tasks.getByName<GetVaultSecretTask>("needsSecrets").apply {
execute()
}
val secret = needsSecrets.secret.get()
[...] // use the secrets
}
}
```
This can be used in your build scripts like:
Expand Down
6 changes: 2 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import net.nemerosa.versioning.tasks.VersionDisplayTask

plugins {
kotlin("jvm") version "1.3.61"
`java-gradle-plugin`
id("org.gradle.kotlin.kotlin-dsl") version "1.3.3"
`kotlin-dsl`
`maven-publish`
id("com.gradle.plugin-publish") version "0.10.1"
id("com.gradle.plugin-publish") version "0.11.0"
id("net.nemerosa.versioning") version "2.12.0"
}

Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
20 changes: 11 additions & 9 deletions integration-token/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import com.liftric.vault.vault
import com.liftric.vault.GetVaultSecretTask

plugins {
java
id("com.liftric.vault-client-plugin") // version known from buildSrc
}
vault {
vaultAddress = "http://localhost:8200"
vaultToken = "myroottoken" // don't do that in production code!
maxRetries = 2
retryIntervalMilliseconds = 200
vaultAddress.set("http://localhost:8200")
vaultToken.set("myroottoken") // don't do that in production code!
maxRetries.set(2)
retryIntervalMilliseconds.set(200)
}
tasks {
val needsSecrets by creating {
val needsSecrets by creating(GetVaultSecretTask::class) {
secretPath.set("secret/example")
doLast {
val secrets: Map<String, String> = project.vault("secret/example")
if (secrets["examplestring"] != "helloworld") throw kotlin.IllegalStateException("examplestring couldn't be read")
if (secrets["exampleint"]?.toInt() != 1337) throw kotlin.IllegalStateException("exampleint couldn't be read")
println("getting secrets succeeded!")
val secret = secret.get()
if (secret["examplestring"] != "helloworld") throw kotlin.IllegalStateException("examplestring couldn't be read")
if (secret["exampleint"]?.toInt() != 1337) throw kotlin.IllegalStateException("exampleint couldn't be read")
println("getting secret succeeded!")
}
}
val fromBuildSrc by creating {
Expand Down
12 changes: 8 additions & 4 deletions integration-token/buildSrc/src/main/kotlin/Configs.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import com.liftric.vault.vault
import com.liftric.vault.GetVaultSecretTask
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByName

object Configs {
fun Project.secretStuff(): String {
val secrets = project.vault("secret/example")
return "${secrets["examplestring"]}:${secrets["exampleint"]}"
val needsSecrets: GetVaultSecretTask = tasks.getByName<GetVaultSecretTask>("needsSecrets").apply {
execute()
}
val secret = needsSecrets.secret.get()
return "${secret["examplestring"]}:${secret["exampleint"]}"
}
}
}
23 changes: 12 additions & 11 deletions integration-tokenfile/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import com.liftric.vault.vault
import com.liftric.vault.GetVaultSecretTask

plugins {
java
id("com.liftric.vault-client-plugin") version "1.0.0-SNAPSHOT"
id("com.liftric.vault-client-plugin")
}
vault {
vaultAddress = "http://localhost:8200"
vaultTokenFilePath = "${projectDir.path}/.vault-token" // don't put the token file on the repo itself like here!
maxRetries = 2
retryIntervalMilliseconds = 200
vaultAddress.set("http://localhost:8200")
vaultTokenFilePath.set("${projectDir.path}/.vault-token") // don't put the token file on the repo itself like here!
maxRetries.set(2)
retryIntervalMilliseconds.set(200)
}
tasks {
val needsSecrets by creating {
val secrets = project.objects.mapProperty<String, String>()
val needsSecrets by creating(GetVaultSecretTask::class) {
secretPath.set("secret/example")
doLast {
secrets.set(project.vault("secret/example"))
if (secrets.get()["examplestring"] != "helloworld") throw kotlin.IllegalStateException("examplestring couldn't be read")
if (secrets.get()["exampleint"]?.toInt() != 1337) throw kotlin.IllegalStateException("exampleint couldn't be read")
println("getting secrets succeeded!")
val secret = secret.get()
if (secret["examplestring"] != "helloworld") throw kotlin.IllegalStateException("examplestring couldn't be read")
if (secret["exampleint"]?.toInt() != 1337) throw kotlin.IllegalStateException("exampleint couldn't be read")
println("getting secret succeeded!")
}
}
val build by existing {
Expand Down
12 changes: 12 additions & 0 deletions src/main/kotlin/com/liftric/vault/Defaults.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.liftric.vault

object Defaults {
// values
const val MAX_RETRIES = 5
const val RETRY_INTERVAL_MILLI = 1000

// env vars
const val VAULT_TOKEN_ENV = "VAULT_TOKEN"
const val VAULT_TOKEN_FILE_PATH_ENV = "VAULT_TOKEN_FILE_PATH"
const val VAULT_ADDR_ENV = "VAULT_ADDR"
}
91 changes: 91 additions & 0 deletions src/main/kotlin/com/liftric/vault/GetVaultSecretTask.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.liftric.vault

import com.liftric.vault.Defaults.MAX_RETRIES
import com.liftric.vault.Defaults.RETRY_INTERVAL_MILLI
import com.liftric.vault.Defaults.VAULT_ADDR_ENV
import com.liftric.vault.Defaults.VAULT_TOKEN_ENV
import com.liftric.vault.Defaults.VAULT_TOKEN_FILE_PATH_ENV
import org.gradle.api.DefaultTask
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.mapProperty
import org.gradle.kotlin.dsl.property
import java.io.File

/**
* See extension for property documentation
*/
open class GetVaultSecretTask : DefaultTask() {
init {
group = "com.liftric.vault"
description = "Fetches a secret from vault."
outputs.upToDateWhen { false }
}

@Input
val secretPath: Property<String> = project.objects.property()

@Input
@Optional
val vaultAddress: Property<String> = project.objects.property()

@Input
@Optional
val vaultToken: Property<String> = project.objects.property()

@Input
@Optional
val vaultTokenFilePath: Property<String> = project.objects.property()

@Input
@Optional
val maxRetries: Property<Int> = project.objects.property()

@Input
@Optional
val retryIntervalMilliseconds: Property<Int> = project.objects.property()

@Internal
// actually used as output...
val secret: MapProperty<String, String> = project.objects.mapProperty()

@TaskAction
fun execute() {
val token = determineToken(vaultToken = vaultToken.orNull, vaultTokenFilePath = vaultTokenFilePath.orNull)
val address = determinAddress(vaultAddress = vaultAddress.orNull)
val maxRetries = maxRetries.getOrElse(MAX_RETRIES)
val retryIntervalMilliseconds = retryIntervalMilliseconds.getOrElse(RETRY_INTERVAL_MILLI)
val path = secretPath.get()
println("[vault] getting `$path` from $address")
secret.set(
VaultClient(
token = token,
vaultAddress = address,
maxRetries = maxRetries,
retryIntervalMilliseconds = retryIntervalMilliseconds
).get(path)
)
}

companion object {
fun determineToken(vaultToken: String?, vaultTokenFilePath: String?): String {
val finalVaultToken = vaultToken ?: System.getenv()[VAULT_TOKEN_ENV]?.trim()
val finalVaultTokenFilePath = vaultTokenFilePath ?: System.getenv()[VAULT_TOKEN_FILE_PATH_ENV]?.trim()
return when {
finalVaultToken != null -> finalVaultToken.trim()
finalVaultTokenFilePath != null -> File(finalVaultTokenFilePath).apply {
if (exists().not()) error("vault token file doesn't exist!")
}.let {
return@let it.readText().trim()
}
else -> error("neither `vaultToken` nor `vaultTokenFilePath` nor `$VAULT_TOKEN_FILE_PATH_ENV` env var nor `$VAULT_TOKEN_ENV` env var provided!")
}
}

fun determinAddress(vaultAddress: String?): String {
val finalVaultAddress = vaultAddress ?: System.getenv()[VAULT_ADDR_ENV]
return finalVaultAddress?.trim() ?: error("neither `vaultAddress` nor `$VAULT_ADDR_ENV` env var provided!")
}
}
}
72 changes: 55 additions & 17 deletions src/main/kotlin/com/liftric/vault/VaultClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,70 @@ package com.liftric.vault

import com.bettercloud.vault.Vault
import com.bettercloud.vault.VaultConfig
import com.bettercloud.vault.VaultException

/**
* Actual client connecting to the configured vault server
*/
class VaultClient(
private val extension: VaultClientExtension,
token: String
token: String,
private val vaultAddress: String,
private val maxRetries: Int,
private val retryIntervalMilliseconds: Int
) {
private val config by lazy {
VaultConfig()
.address(extension.vaultAddress)
.token(token)
.build()
try {
VaultConfig()
.address(vaultAddress)
.token(token)
.build()
} catch (e: VaultException) {
println("[vault] exception while creating vault client for $vaultAddress: ${e.message}")
throw e
}
}
private val vault by lazy {
try {
Vault(config)
} catch (e: VaultException) {
println(
"[vault] exception while preparing vault client config for $vaultAddress: ${e.message} - token valid?"
.replace('\n', '#')
)
throw e
}
}

fun get(secretPath: String): Map<String, String> {
verifyTokenValid()
return try {
vault.withRetries(maxRetries, retryIntervalMilliseconds)
.logical()
.read(secretPath)
.data.also {
if (it.isEmpty()) error("[vault] secret response contains no data - secret exists? token has correct rights to access it?")
}
} catch (e: VaultException) {
println(
"[vault] exception while calling vault at $vaultAddress: ${e.message} - secret exists? token has correct rights to access it?"
.replace('\n', '#')
)
if (vaultAddress.startsWith("https").not()) {
println("[vault] is your vault address correct? It doesn't start with https!")
}
throw e
}
}
private val vault by lazy { Vault(config) }

fun get(secretPath: String): Map<String, String> = try {
vault.withRetries(extension.maxRetries, extension.retryIntervalMilliseconds)
.logical()
.read(secretPath)
.data
} catch (e: Exception) {
println("[vault] exception while calling vault at ${extension.vaultAddress}: ${e.message}")
if (extension.vaultAddress?.startsWith("https") == false) {
println("[vault] is your vault address correct? It doesn't start with https!")
private fun verifyTokenValid() {
try {
vault.auth().lookupSelf()
} catch (e: VaultException) {
println(
"[vault] exception while verifying vault token validity for $vaultAddress: ${e.message}"
.replace('\n', '#')
)
throw e
}
throw e
}
}
Loading

0 comments on commit 3b4ab42

Please sign in to comment.