From 3a4ebbfd0216513e48781980554f0254d8862ffa Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Tue, 17 Oct 2023 08:59:00 +0200 Subject: [PATCH] fix: Optionally hide rendered environment variables (#798) * new OriginType.ENV_VARIABLE * deserialization of unknown originType ordinal, but this doesn't really help for compatibility if serialized with new version and deserialized with old version --- build.sbt | 6 ++- .../typesafe/config/ConfigRenderOptions.java | 43 ++++++++++++++++--- .../config/impl/AbstractConfigValue.java | 16 ++++++- .../com/typesafe/config/impl/ConfigImpl.java | 6 ++- .../typesafe/config/impl/ConfigString.java | 16 ++++--- .../com/typesafe/config/impl/OriginType.java | 3 +- .../config/impl/SimpleConfigOrigin.java | 14 +++++- config/src/test/resources/env-variables.conf | 4 ++ config/src/test/scala/Rendering.scala | 4 ++ .../com/typesafe/config/impl/ConfigTest.scala | 29 +++++++++++++ 10 files changed, 122 insertions(+), 19 deletions(-) create mode 100644 config/src/test/resources/env-variables.conf diff --git a/build.sbt b/build.sbt index 631d5c8d9..e0b42307e 100644 --- a/build.sbt +++ b/build.sbt @@ -95,7 +95,11 @@ lazy val configLib = Project("config", file("config")) "CONFIG_FORCE_a__c" -> "3", "CONFIG_FORCE_a___c" -> "4", "CONFIG_FORCE_akka_version" -> "foo", - "CONFIG_FORCE_akka_event__handler__dispatcher_max__pool__size" -> "10") + "CONFIG_FORCE_akka_event__handler__dispatcher_max__pool__size" -> "10", + "SECRET_A" -> "A", // ConfigTest.renderShowEnvVariableValues + "SECRET_B" -> "B", // ConfigTest.renderShowEnvVariableValues + "SECRET_C" -> "C" // ConfigTest.renderShowEnvVariableValues + ) OsgiKeys.exportPackage := Seq("com.typesafe.config", "com.typesafe.config.impl") publish := sys.error("use publishSigned instead of plain publish") diff --git a/config/src/main/java/com/typesafe/config/ConfigRenderOptions.java b/config/src/main/java/com/typesafe/config/ConfigRenderOptions.java index 7b07fabd6..997307895 100644 --- a/config/src/main/java/com/typesafe/config/ConfigRenderOptions.java +++ b/config/src/main/java/com/typesafe/config/ConfigRenderOptions.java @@ -21,13 +21,15 @@ public final class ConfigRenderOptions { private final boolean comments; private final boolean formatted; private final boolean json; + private final boolean showEnvVariableValues; private ConfigRenderOptions(boolean originComments, boolean comments, boolean formatted, - boolean json) { + boolean json, boolean showEnvVariableValues) { this.originComments = originComments; this.comments = comments; this.formatted = formatted; this.json = json; + this.showEnvVariableValues = showEnvVariableValues; } /** @@ -38,7 +40,7 @@ private ConfigRenderOptions(boolean originComments, boolean comments, boolean fo * @return the default render options */ public static ConfigRenderOptions defaults() { - return new ConfigRenderOptions(true, true, true, true); + return new ConfigRenderOptions(true, true, true, true, true); } /** @@ -48,7 +50,7 @@ public static ConfigRenderOptions defaults() { * @return the concise render options */ public static ConfigRenderOptions concise() { - return new ConfigRenderOptions(false, false, false, true); + return new ConfigRenderOptions(false, false, false, true, true); } /** @@ -64,7 +66,7 @@ public ConfigRenderOptions setComments(boolean value) { if (value == comments) return this; else - return new ConfigRenderOptions(originComments, value, formatted, json); + return new ConfigRenderOptions(originComments, value, formatted, json, showEnvVariableValues); } /** @@ -97,7 +99,7 @@ public ConfigRenderOptions setOriginComments(boolean value) { if (value == originComments) return this; else - return new ConfigRenderOptions(value, comments, formatted, json); + return new ConfigRenderOptions(value, comments, formatted, json, showEnvVariableValues); } /** @@ -122,7 +124,7 @@ public ConfigRenderOptions setFormatted(boolean value) { if (value == formatted) return this; else - return new ConfigRenderOptions(originComments, comments, value, json); + return new ConfigRenderOptions(originComments, comments, value, json, showEnvVariableValues); } /** @@ -150,7 +152,32 @@ public ConfigRenderOptions setJson(boolean value) { if (value == json) return this; else - return new ConfigRenderOptions(originComments, comments, formatted, value); + return new ConfigRenderOptions(originComments, comments, formatted, value, showEnvVariableValues); + } + + /** + * Returns options with showEnvVariableValues toggled. This controls if values set from + * environment variables are included in the rendered string. + * + * @param value + * true to include environment variable values in the render + * @return options with requested setting for environment variables + */ + public ConfigRenderOptions setShowEnvVariableValues(boolean value) { + if (value == showEnvVariableValues) + return this; + else + return new ConfigRenderOptions(originComments, comments, formatted, json, value); + } + + /** + * Returns whether the options enable rendering of environment variable values. This method is mostly used + * by the config lib internally, not by applications. + * + * @return true if environment variable values should be rendered + */ + public boolean getShowEnvVariableValues() { + return showEnvVariableValues; } /** @@ -174,6 +201,8 @@ public String toString() { sb.append("formatted,"); if (json) sb.append("json,"); + if (showEnvVariableValues) + sb.append("showEnvVariableValues,"); if (sb.charAt(sb.length() - 1) == ',') sb.setLength(sb.length() - 1); sb.append(")"); diff --git a/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java b/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java index 90db92427..67728e257 100644 --- a/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java +++ b/config/src/main/java/com/typesafe/config/impl/AbstractConfigValue.java @@ -357,8 +357,20 @@ protected void render(StringBuilder sb, int indent, boolean atRoot, String atKey } protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { - Object u = unwrapped(); - sb.append(u.toString()); + if (hideEnvVariableValue(options)) { + sb.append(""); + } else { + Object u = unwrapped(); + sb.append(u.toString()); + } + } + + protected boolean hideEnvVariableValue(ConfigRenderOptions options) { + return !options.getShowEnvVariableValues() && origin.originType() == OriginType.ENV_VARIABLE; + } + + protected void appendHiddenEnvVariableValue(StringBuilder sb) { + sb.append("\"\""); } @Override diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java index 9e580a40f..05be7d268 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigImpl.java @@ -342,7 +342,7 @@ public static void reloadSystemPropertiesConfig() { } private static AbstractConfigObject loadEnvVariables() { - return PropertiesParser.fromStringMap(newSimpleOrigin("env variables"), System.getenv()); + return PropertiesParser.fromStringMap(newEnvVariable("env variables"), System.getenv()); } private static class EnvVariablesHolder { @@ -544,4 +544,8 @@ public static ConfigOrigin newFileOrigin(String filename) { public static ConfigOrigin newURLOrigin(URL url) { return SimpleConfigOrigin.newURL(url); } + + public static ConfigOrigin newEnvVariable(String description) { + return SimpleConfigOrigin.newEnvVariable(description); + } } diff --git a/config/src/main/java/com/typesafe/config/impl/ConfigString.java b/config/src/main/java/com/typesafe/config/impl/ConfigString.java index 89cc2789b..804bcf9c4 100644 --- a/config/src/main/java/com/typesafe/config/impl/ConfigString.java +++ b/config/src/main/java/com/typesafe/config/impl/ConfigString.java @@ -80,11 +80,15 @@ String transformToString() { @Override protected void render(StringBuilder sb, int indent, boolean atRoot, ConfigRenderOptions options) { - String rendered; - if (options.getJson()) - rendered = ConfigImplUtil.renderJsonString(value); - else - rendered = ConfigImplUtil.renderStringUnquotedIfPossible(value); - sb.append(rendered); + if (hideEnvVariableValue(options)) { + appendHiddenEnvVariableValue(sb); + } else { + String rendered; + if (options.getJson()) + rendered = ConfigImplUtil.renderJsonString(value); + else + rendered = ConfigImplUtil.renderStringUnquotedIfPossible(value); + sb.append(rendered); + } } } diff --git a/config/src/main/java/com/typesafe/config/impl/OriginType.java b/config/src/main/java/com/typesafe/config/impl/OriginType.java index c78b5ee0e..57d335d24 100644 --- a/config/src/main/java/com/typesafe/config/impl/OriginType.java +++ b/config/src/main/java/com/typesafe/config/impl/OriginType.java @@ -5,5 +5,6 @@ enum OriginType { GENERIC, FILE, URL, - RESOURCE + RESOURCE, + ENV_VARIABLE } diff --git a/config/src/main/java/com/typesafe/config/impl/SimpleConfigOrigin.java b/config/src/main/java/com/typesafe/config/impl/SimpleConfigOrigin.java index 9e05f9e91..37dc4c531 100644 --- a/config/src/main/java/com/typesafe/config/impl/SimpleConfigOrigin.java +++ b/config/src/main/java/com/typesafe/config/impl/SimpleConfigOrigin.java @@ -77,6 +77,10 @@ static SimpleConfigOrigin newResource(String resource) { return newResource(resource, null); } + static SimpleConfigOrigin newEnvVariable(String description) { + return new SimpleConfigOrigin(description, -1, -1, OriginType.ENV_VARIABLE, null, null, null); + } + @Override public SimpleConfigOrigin withLineNumber(int lineNumber) { if (lineNumber == this.lineNumber && lineNumber == this.endLineNumber) { @@ -139,6 +143,10 @@ public String description() { } } + OriginType originType() { + return originType; + } + @Override public boolean equals(Object other) { if (other instanceof SimpleConfigOrigin) { @@ -484,7 +492,11 @@ static SimpleConfigOrigin fromFields(Map m) throws IOEx Number originTypeOrdinal = (Number) m.get(SerializedField.ORIGIN_TYPE); if (originTypeOrdinal == null) throw new IOException("Missing ORIGIN_TYPE field"); - OriginType originType = OriginType.values()[originTypeOrdinal.byteValue()]; + OriginType originType; + if (originTypeOrdinal.byteValue() < OriginType.values().length) + originType = OriginType.values()[originTypeOrdinal.byteValue()]; + else + originType = OriginType.GENERIC; // ENV_VARIABLE was added in a later version String urlOrNull = (String) m.get(SerializedField.ORIGIN_URL); String resourceOrNull = (String) m.get(SerializedField.ORIGIN_RESOURCE); @SuppressWarnings("unchecked") diff --git a/config/src/test/resources/env-variables.conf b/config/src/test/resources/env-variables.conf new file mode 100644 index 000000000..5dfe44fe6 --- /dev/null +++ b/config/src/test/resources/env-variables.conf @@ -0,0 +1,4 @@ +secret = a +secret = ${?SECRET_A} +secrets = ["b", "c"] +secrets = [${?SECRET_B}, ${?SECRET_C}] diff --git a/config/src/test/scala/Rendering.scala b/config/src/test/scala/Rendering.scala index 1cc264be0..06d08264b 100644 --- a/config/src/test/scala/Rendering.scala +++ b/config/src/test/scala/Rendering.scala @@ -1,3 +1,5 @@ +package foo; + import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigRenderOptions @@ -6,11 +8,13 @@ object RenderExample extends App { val originComments = args.contains("--origin-comments") val comments = args.contains("--comments") val hocon = args.contains("--hocon") + val hideEnvVariableValues = args.contains("--hide-env-variable-values") val options = ConfigRenderOptions.defaults() .setFormatted(formatted) .setOriginComments(originComments) .setComments(comments) .setJson(!hocon) + .setShowEnvVariableValues(!hideEnvVariableValues) def render(what: String) { val conf = ConfigFactory.defaultOverrides() diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index b00a9adbf..350af6f9e 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -1207,6 +1207,35 @@ class ConfigTest extends TestUtils { } } + @Test + def renderShowEnvVariableValues(): Unit = { + val config = ConfigFactory.load("env-variables") + assertEquals("A", config.getString("secret")) + assertEquals("B", config.getStringList("secrets").get(0)) + assertEquals("C", config.getStringList("secrets").get(1)) + val hideRenderOpt = ConfigRenderOptions.defaults().setShowEnvVariableValues(false) + val rendered1 = config.root().render(hideRenderOpt) + assertTrue(rendered1.contains(""""secret" : """"")) + assertTrue(rendered1.contains( + """| "secrets" : [ + | # env variables + | "", + | # env variables + | "" + | ]""".stripMargin)) + + val showRenderOpt = ConfigRenderOptions.defaults() + val rendered2 = config.root().render(showRenderOpt) + assertTrue(rendered2.contains(""""secret" : "A"""")) + assertTrue(rendered2.contains( + """| "secrets" : [ + | # env variables + | "B", + | # env variables + | "C" + | ]""".stripMargin)) + } + @Test def serializeRoundTrip() { for (i <- 1 to 10) {