Created
June 21, 2019 18:54
-
-
Save randomstatistic/43cfec78fcb945b6aa9b6faef65d22b9 to your computer and use it in GitHub Desktop.
Mask sensitive TypeSafe-Config values for rendering
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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