Skip to content

Commit a3372c5

Browse files
committed
Restrict adding invalid (from ConfigVariableExpander point of view) key names to the keystore through keystore CLI. Keysotre now warns on invalid keys if they were already added.
1 parent 45319e8 commit a3372c5

File tree

6 files changed

+71
-133
lines changed

6 files changed

+71
-133
lines changed

docs/reference/keystore.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ bin/logstash-keystore add ES_USER ES_PWD
136136
When prompted, enter a value for each key.
137137
138138
::::{note}
139-
Key values are limited to ASCII characters. It includes digits, letters, and a few special symbols.
139+
Key values are limited to ASCII characters. It can only accept digits, letters and optional `.` (dots) and/or `_` (underscores) symbols included between or after.
140140
::::
141141
142142

logstash-core/src/main/java/org/logstash/secret/KeyValidator.java

Lines changed: 0 additions & 63 deletions
This file was deleted.

logstash-core/src/main/java/org/logstash/secret/SecretIdentifier.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020

2121
package org.logstash.secret;
2222

23+
import org.apache.logging.log4j.Logger;
24+
import org.apache.logging.log4j.LogManager;
25+
import org.apache.logging.log4j.util.Strings;
26+
2327
import java.util.Locale;
2428
import java.util.regex.Pattern;
2529

@@ -29,17 +33,22 @@
2933
*/
3034
public class SecretIdentifier {
3135

36+
// aligns with ConfigVariableExpander substitution pattern
37+
public final static Pattern KEY_PATTERN = Pattern.compile("[a-zA-Z_.][a-zA-Z0-9_.]*");
38+
3239
private final static Pattern colonPattern = Pattern.compile(":");
3340
private final static String VERSION = "v1";
3441
private final static Pattern urnPattern = Pattern.compile("urn:logstash:secret:"+ VERSION + ":.*$");
3542
private final String key;
3643

44+
private static final Logger logger = LogManager.getLogger(SecretIdentifier.class);
45+
3746
/**
3847
* Constructor
3948
*
4049
* @param key The unique part of the identifier. This is the key to reference the secret, and the key itself should not be sensitive. For example: {@code db.pass}
4150
*/
42-
public SecretIdentifier(String key) {
51+
public SecretIdentifier(final String key) {
4352
this.key = validateWithTransform(key, "key");
4453
}
4554

@@ -49,7 +58,7 @@ public SecretIdentifier(String key) {
4958
* @param urn The {@link String} formatted identifier obtained originally from {@link SecretIdentifier#toExternalForm()}
5059
* @return The {@link SecretIdentifier} object used to identify secrets, null if not valid external form.
5160
*/
52-
public static SecretIdentifier fromExternalForm(String urn) {
61+
public static SecretIdentifier fromExternalForm(final String urn) {
5362
if (urn == null || !urnPattern.matcher(urn).matches()) {
5463
throw new IllegalArgumentException("Invalid external form " + urn);
5564
}
@@ -64,8 +73,14 @@ public static SecretIdentifier fromExternalForm(String urn) {
6473
* @param partName The name of the part used for logging.
6574
* @return The validated and transformed part.
6675
*/
67-
private static String validateWithTransform(String key, String partName) {
68-
KeyValidator.validateKey(key, partName);
76+
private static String validateWithTransform(final String key, final String partName) {
77+
if (key == null || key.isEmpty() || Strings.isBlank(key)) {
78+
throw new IllegalArgumentException(String.format("%s may not be null or empty", partName));
79+
}
80+
81+
if (!KEY_PATTERN.matcher(key).matches()) {
82+
logger.warn(String.format("Invalid %s key appeared in keystore. Please remove it as it cannot be used in the pipelines", key));
83+
}
6984
return key.toLowerCase(Locale.US);
7085
}
7186

logstash-core/src/main/java/org/logstash/secret/cli/SecretStoreCli.java

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@
2626
import java.nio.CharBuffer;
2727
import java.nio.charset.CharsetEncoder;
2828
import java.nio.charset.StandardCharsets;
29-
import java.util.*;
29+
import java.util.Arrays;
30+
import java.util.Collection;
31+
import java.util.Collections;
32+
import java.util.EnumSet;
33+
import java.util.List;
34+
import java.util.Optional;
35+
import java.util.Set;
3036
import java.util.stream.Collectors;
3137

3238
import static org.logstash.secret.store.SecretStoreFactory.LOGSTASH_MARKER;
@@ -178,7 +184,7 @@ Set<CommandOptions> getValidOptions() {
178184
}
179185
}
180186

181-
public SecretStoreCli(Terminal terminal){
187+
public SecretStoreCli(Terminal terminal) {
182188
this(terminal, SecretStoreFactory.fromEnvironment());
183189
}
184190

@@ -189,9 +195,10 @@ public SecretStoreCli(Terminal terminal){
189195

190196
/**
191197
* Entry point to issue a command line command.
198+
*
192199
* @param primaryCommand The string representation of a {@link SecretStoreCli.Command}, if the String does not map to a {@link SecretStoreCli.Command}, then it will show the help menu.
193-
* @param config The configuration needed to work a secret store. May be null for help.
194-
* @param allArguments This can be either identifiers for a secret, or a sub command like --help. May be null.
200+
* @param config The configuration needed to work a secret store. May be null for help.
201+
* @param allArguments This can be either identifiers for a secret, or a sub command like --help. May be null.
195202
*/
196203
public void command(String primaryCommand, SecureConfig config, String... allArguments) {
197204
terminal.writeLine("");
@@ -212,7 +219,7 @@ public void command(String primaryCommand, SecureConfig config, String... allArg
212219
final CommandLine commandLine = commandParseResult.get();
213220
switch (commandLine.getCommand()) {
214221
case CREATE: {
215-
if (commandLine.hasOption(CommandOptions.HELP)){
222+
if (commandLine.hasOption(CommandOptions.HELP)) {
216223
terminal.writeLine("Creates a new keystore. For example: 'bin/logstash-keystore create'");
217224
return;
218225
}
@@ -227,7 +234,7 @@ public void command(String primaryCommand, SecureConfig config, String... allArg
227234
break;
228235
}
229236
case LIST: {
230-
if (commandLine.hasOption(CommandOptions.HELP)){
237+
if (commandLine.hasOption(CommandOptions.HELP)) {
231238
terminal.writeLine("List all secret identifiers from the keystore. For example: " +
232239
"`bin/logstash-keystore list`. Note - only the identifiers will be listed, not the secrets.");
233240
return;
@@ -239,7 +246,7 @@ public void command(String primaryCommand, SecureConfig config, String... allArg
239246
break;
240247
}
241248
case ADD: {
242-
if (commandLine.hasOption(CommandOptions.HELP)){
249+
if (commandLine.hasOption(CommandOptions.HELP)) {
243250
terminal.writeLine("Add secrets to the keystore. For example: " +
244251
"`bin/logstash-keystore add my-secret`, at the prompt enter your secret. You will use the identifier ${my-secret} in your Logstash configuration.");
245252
return;
@@ -251,6 +258,13 @@ public void command(String primaryCommand, SecureConfig config, String... allArg
251258
if (secretStoreFactory.exists(config.clone())) {
252259
final SecretStore secretStore = secretStoreFactory.load(config);
253260
for (String argument : commandLine.getArguments()) {
261+
if (!SecretIdentifier.KEY_PATTERN.matcher(argument).matches()) {
262+
final String errorMessage = String.format(
263+
"Invalid secret key %s provided. " +
264+
"It can only accept digits, letters with optional `.` " +
265+
"and/or `_` symbols included between or after.", argument);
266+
throw new IllegalArgumentException(errorMessage);
267+
}
254268
final SecretIdentifier id = new SecretIdentifier(argument);
255269
final byte[] existingValue = secretStore.retrieveSecret(id);
256270
if (existingValue != null) {
@@ -263,7 +277,7 @@ public void command(String primaryCommand, SecureConfig config, String... allArg
263277

264278
final String enterValueMessage = String.format("Enter value for %s: ", argument);
265279
char[] secret = null;
266-
while(secret == null) {
280+
while (secret == null) {
267281
terminal.write(enterValueMessage);
268282
final char[] readSecret = terminal.readSecret();
269283

@@ -288,7 +302,7 @@ public void command(String primaryCommand, SecureConfig config, String... allArg
288302
break;
289303
}
290304
case REMOVE: {
291-
if (commandLine.hasOption(CommandOptions.HELP)){
305+
if (commandLine.hasOption(CommandOptions.HELP)) {
292306
terminal.writeLine("Remove secrets from the keystore. For example: " +
293307
"`bin/logstash-keystore remove my-secret`");
294308
return;
@@ -314,7 +328,7 @@ public void command(String primaryCommand, SecureConfig config, String... allArg
314328
}
315329
}
316330

317-
private void printHelp(){
331+
private void printHelp() {
318332
terminal.writeLine("Usage:");
319333
terminal.writeLine("--------");
320334
terminal.writeLine("bin/logstash-keystore [option] command [argument]");

logstash-core/src/test/java/org/logstash/secret/KeyValidatorTest.java

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)