-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hide secrets in configuration #7442
Comments
Relates to the use case in eclipse/microprofile-config#451 |
We just added support for this in SmallRye Config: smallrye/smallrye-config#269 Not released yet, but once done I'll integrate it in Quarkus. |
There is now a way to support this in SmallRye Config latest version, which is also available in Quarkus. How do we envision this being used in Quarkus? In SmallRye Config, you can list secret keys with the API What are your thoughts @dmlloyd ? |
Quarkus configuration values which are secrets should have a type which unambiguously identifies the value as a secret, e.g. a Another thing to think about is that it's not really possible to list all property names which are secret, because in many cases the property name might be a pattern (part of a Manually listing property names is not a good solution in any event. |
Sure. I think there are two angles:
For Quarkus configuration, since we control it, we could implement the type For user custom configuration, right now, we don't have a good way to set this up. You can only do this in the |
I have written a Secret Config Source (using jasypt library for encryption), with a separate secrets.properties in the config directory, it works with {encrypted} and {plain} prefixes to the property values, {plain} will encrypt the string and dump it to the log (not sure if this is the best option, but works), I like the dynamic decryption mechanism and might look at implementing it: `
} |
Hi everybody, we were searching for a solution for encrypting certain configuration properties to obscure their plain text in our client app (native image). The properties should be read from our existing We came up with the following ConfigSourceInterceptor, that uses a hard-coded AES key/iv to decrypt base64-encoded, secret strings, if they are prefixed with Such a ConfigSourceInterceptor needs to be registered via the resource (see documentation):
In this example containing the string
Please feel free to use and extend this for your own apps. Note: Currently this interceptor is not thread safe. I investigated via debugger, that its methods are typically only called from Quarkus main thread. If you experience access via different threads, please let me know. This would require a little refactoring for the package com.example;
import io.smallrye.config.ConfigSourceInterceptor;
import io.smallrye.config.ConfigSourceInterceptorContext;
import io.smallrye.config.ConfigValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;
/**
* {@link ConfigSourceInterceptor} to decrypt secret configuration properties on the fly using a hard-coded key/iv.
*/
public class SecretsConfigInterceptor implements ConfigSourceInterceptor {
private static final Logger logger = LoggerFactory.getLogger(SecretsConfigInterceptor.class);
private static final String PREFIX = "secret.";
static final String ALGORITHM = "AES/CBC/PKCS5Padding";
static final String KEY_ALGORITHM = "AES";
// TODO enter your IV
static final byte[] INIT_VECTOR = new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00};
// TODO enter your KEY
static final byte[] KEY = new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00};
private IvParameterSpec ivParameterSpec;
private Key keySpec;
private Cipher cipher;
public SecretsConfigInterceptor() {
try {
cipher = Cipher.getInstance(ALGORITHM);
} catch (Exception e) {
throw new IllegalStateException("Could not initialize Cipher " + ALGORITHM, e);
}
ivParameterSpec = new IvParameterSpec(INIT_VECTOR);
keySpec = new SecretKeySpec(KEY, KEY_ALGORITHM);
}
@Override
public ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {
String secretName = PREFIX + name;
ConfigValue configValue = context.proceed(secretName);
if (configValue != null) {
// Found a "secret" value
return decryptValue(name, configValue);
}
// Fall back to standard behavior
return context.proceed(name);
}
/**
* Decrypts a ConfigValue
*
* @param name the original name to use for the decrypted ConfigValue
* @param configValue the config value
* @return the decrypted config value with the original name
*/
ConfigValue decryptValue(String name, ConfigValue configValue) {
ConfigValue newConfigValue = configValue;
String value = configValue.getValue();
if (value != null && !"".equals(value)) {
logger.debug("Decrypting {}", configValue.getName());
try {
// Secret comes as base64 encrypted string, decoding it
byte[] decoded = Base64.getDecoder().decode(value);
// decrypting the decoded secret with the master key and iv
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
byte[] decrypted = cipher.doFinal(decoded);
// We will actually feed a String into the properties file, as this is a password
String newValue = new String(decrypted, StandardCharsets.UTF_8);
ConfigValue decryptedValue = new ConfigValue.ConfigValueBuilder().
withName(name)
.withValue(newValue)
.withConfigSourceName(configValue.getConfigSourceName())
.withConfigSourceOrdinal(configValue.getConfigSourceOrdinal())
.withLineNumber(configValue.getLineNumber())
.withRawValue(configValue.getRawValue())
.build();
newConfigValue = decryptedValue;
} catch (Exception e) {
throw new RuntimeException("Could not decrypt secret " + name + ": ", e);
}
}
return newConfigValue;
}
} |
@dthommes Thanks. I'll have a look. |
In previous solutions, you seem to be happy with storing the decrypted password to a String, when clearly char[] are preferred to narrow the attack window: https://www.techiedelight.com/why-character-array-preferred-over-string-storing-passwords/ Shouldn't we make org.eclipse.microprofile.config.ConfigValue.java#getValue() generic, with String being the 'default', but others (like char[]) also being ok? |
@radcortez am I right to assume that we don't currently integrate with this in Quarkus? |
Right now, not automatically. Users can add their own We have another issue open to provide better integration: |
Gotcha, thanks! |
Description
We have configuration properties which may contain passwords or other kinds of secrets. At least one means of viewing or dumping the running configuration does, or will soon, exist (see #7430). We don't want secrets to appear in the output as this could be a security problem.
We talked about vault-style configuration sources - of which we now have at least one. The vault config source protects against its contents from being iterated by the simple measure of returning no property names, so querying a vault entry by name directly returns its value but there's no way to know what names are present in the vault. This works to the extent that the vault contents cannot be queried, however, it doesn't prevent the keys from being found if other config sources have the same key, or refer to the key indirectly via a property expression. We can mitigate the latter problem by ensuring that all config viewers suppress expansion (see this example).
However, there is still the basic problem that passwords and secrets are generally encoded simply as strings (which is to say, using a vault is not mandatory), and the user often provides a plain value which is iterable and viewable.
We need some way to "lock" the configuration so that secrets do not appear unless explicitly enabled.
Implementation ideas
Idea 1: forbid clear secrets
We could fail build or startup if a property which is known to be secret contains a value that is not an expression. This could push users towards using a vault.
We cannot prevent users from simply putting the clear value somewhere else in the configuration and then referring to that location via expression though, so this approach is questionable.
This idea requires that the properties which are secret be enumerated during build.
Idea 2: hide the values
We can acknowledge the reality that the configuration can and will contain secrets no matter what we do, and introduce another
ConfigSource
wrapper which intercepts requests for properties which are known to be secret. It will return a placeholder value like<secret>
for these properties unless it is explicitly disabled e.g.:We could automatically enable access for our own configuration object population and the MicroProfile spec CDI configuration handling. Beyond that we would require documentation for users who wish to store secret values.
This idea also requires that the properties which are secret be enumerated during build.
Appendix: what are we even doing with strings
We should not use
String
to hold secret values. I'd recommend usingTwoWayPassword
ClearPassword
(with an associated converter) as the type for all client password values, andOneWayPassword
Password
(with a converter based on modular crypt) for all server password values. I'd also recommend directly using JCEKey
subtypes for public/private/secret keys, if and when we ever spec those, in plain base64 (like SSH) or PEM format depending on the use case and expected key type. Let the converters do the parsing and validation work in a unified way here.By doing this, we can automatically detect what properties are secret right in the extension loader and automatically generate corresponding build items which can configure the appropriate mechanism. Otherwise we would need a different mechanism (e.g. an annotation) which adds more chance for user error. Using types to carry semantic information about the configuration value is idiomatic for MP Config, so I'd recommend using this approach rather than something else.
The text was updated successfully, but these errors were encountered: