Skip to content

Instantly share code, notes, and snippets.

@randomstatistic
Created June 21, 2019 18:54
Show Gist options
  • Save randomstatistic/43cfec78fcb945b6aa9b6faef65d22b9 to your computer and use it in GitHub Desktop.
Save randomstatistic/43cfec78fcb945b6aa9b6faef65d22b9 to your computer and use it in GitHub Desktop.
Mask sensitive TypeSafe-Config values for rendering
import java.util.Map.Entry
import com.typesafe.config.{ Config, ConfigRenderOptions, ConfigValue, ConfigValueFactory }
import org.mindrot.jbcrypt.BCrypt
import scala.io.{ Codec, Source }
import scala.collection.JavaConverters._
import scala.util.Try
import scala.util.matching.Regex
object ConfigMask {
implicit val codec: Codec = Codec.UTF8
val defaultMaskedKeys = Set(
"(?i).*password.*",
"(?i).*secret.*",
"(?i).*authToken.*",
"(?i).*_pw$"
)
val maskedKeys: Set[String] = {
val resources = ClassLoader.getSystemResources("config_secrets.conf")
resources.asScala.flatMap(u => Source.fromInputStream(u.openStream).getLines())
.map(_.trim).filter(_.nonEmpty).toSet
}
val allTests: Set[Regex] = (defaultMaskedKeys ++ maskedKeys).map(_.r)
def hash(s: String): String = BCrypt.hashpw(s, BCrypt.gensalt)
def checkHash(s: String, hash: String): Boolean = Try(BCrypt.checkpw(s, hash)).getOrElse(false)
def shouldMask(entry: Entry[String, ConfigValue]): Boolean = {
allTests.exists(_.findFirstIn(entry.getKey).nonEmpty) ||
entry.getValue.origin.description.contains("env var")
}
def maskValue(conf: Config, entry: Entry[String, ConfigValue]): Config = {
val originalOrigin = entry.getValue.origin
val secret = conf.getString(entry.getKey)
val newValue = ConfigValueFactory
.fromAnyRef(hash(secret))
.withOrigin(originalOrigin.withComments(List("(Hashed for rendering)").asJava))
conf.withValue(entry.getKey, newValue)
}
/**
* Mask any value for which any of the following is true:
* 1. The key Matches the hardcoded regexes above
* 2. The key Matches a regex in any config_secrets.conf in the classpath
* 3. The setting has a final value populated from an environment variable
*/
def masked(config: Config): Config = {
config.entrySet.asScala.foldLeft(config) {
case (conf, configValue) if shouldMask(configValue) => maskValue(conf, configValue)
case (conf, _) => conf
}
}
// setting explain = true will include the source from which each value was set, but will not be valid json
def displayConfig(config: Config, explain: Boolean = false): String = {
val renderOptions =
if (explain) ConfigRenderOptions.defaults else ConfigRenderOptions.concise
masked(config).root().render(renderOptions)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment