diff --git a/conf/java-spiffe-helper.properties b/conf/java-spiffe-helper.properties new file mode 100644 index 0000000..b8fccd6 --- /dev/null +++ b/conf/java-spiffe-helper.properties @@ -0,0 +1,25 @@ +# Example java-spiffe-helper configuration + +# KeyStore Path +keyStorePath = keystore.p12 + +# Password for the KeyStore +keyStorePass = REPLACE_WITH_YOUR_KEYSTORE_PASSWORD + +# Password for the private key within the KeyStore +keyPass = REPLACE_WITH_YOUR_PRIVATE_KEY_PASSWORD + +# Path to the TrustStore file +trustStorePath = truststore.p12 + +# TrustStore Password: Password for the TrustStore +trustStorePass = REPLACE_WITH_YOUR_TRUSTSTORE_PASSWORD + +# KeyStore Type: 'pkcs12' (default) or 'jks' +keyStoreType = pkcs12 + +# Key Alias: Alias of the key within the KeyStore (Default: `spiffe`) +keyAlias = spiffe + +# SPIFFE Socket Path: Path to the SPIRE Agent's public API socket +spiffeSocketPath = unix:/tmp/spire-agent/public/api.sock diff --git a/java-spiffe-helper/README.md b/java-spiffe-helper/README.md index f3385a3..c0cbebf 100644 --- a/java-spiffe-helper/README.md +++ b/java-spiffe-helper/README.md @@ -10,15 +10,14 @@ The Helper automatically gets the SVID updates and stores them in the KeyStore a On Linux: -`java -jar java-spiffe-helper-0.7.0-linux-x86_64.jar -c helper.conf` +`java -jar java-spiffe-helper-0.8.4-linux-x86_64.jar` On Mac OS: -`java -jar java-spiffe-helper-0.7.0-osx-x86_64.jar -c helper.conf` +`java -jar java-spiffe-helper-0.8.4-osx-x86_64.jar` -Either `-c` or `--config` should be used to pass the path to the config file. - -(The jar can be downloaded from [Github releases](https://github.com/spiffe/java-spiffe/releases/tag/v0.7.0)) +You can run the utility with the `-c` or `--config` option to specify the path to the configuration file. By default, it +will look for a configuration file named `conf/java-spiffe-helper.properties` in the current working directory. ## Config file @@ -39,20 +38,19 @@ spiffeSocketPath = unix:/tmp/agent.sock ### Configuration Properties - |Configuration | Description | Default value | - |------------------|--------------------------------------------------------------------------------| ------------- | - |`keyStorePath` | Path to the Java KeyStore File for storing the Private Key and chain of certs | none | - |`keyStorePass` | Password to protect the Java KeyStore File | none | - |`keyPass` | Password to protect the Private Key entry in the KeyStore | none | - |`trustStorePath` | Path to the Java TrustStore File for storing the trusted bundles | none | - |`trustStorePass` | Password to protect the Java TrustStore File | none | - |`keyStoreType` | Java KeyStore Type. (`pkcs12` and `jks` are supported). Case insensitive. | pkcs12 | - |`keyAlias` | Alias for the Private Key entry | spiffe | - |`spiffeSocketPath`| Path the Workload API | Read from the system variable: SPIFFE_ENDPOINT_SOCKET | - -KeyStore and TrustStore **must** be in separate files. If `keyStorePath` and `trustStorePath` points to the same file, an error -is shown -. +| Configuration | Description | Default value | + |--------------------|-------------------------------------------------------------------------------|-------------------------------------------------------| +| `keyStorePath` | Path to the Java KeyStore File for storing the Private Key and chain of certs | none | +| `keyStorePass` | Password to protect the Java KeyStore File | none | +| `keyPass` | Password to protect the Private Key entry in the KeyStore | none | +| `trustStorePath` | Path to the Java TrustStore File for storing the trusted bundles | none | +| `trustStorePass` | Password to protect the Java TrustStore File | none | +| `keyStoreType` | Java KeyStore Type. (`pkcs12` and `jks` are supported). Case insensitive. | pkcs12 | +| `keyAlias` | Alias for the Private Key entry | spiffe | +| `spiffeSocketPath` | Path the Workload API | Read from the system variable: SPIFFE_ENDPOINT_SOCKET | + +KeyStore and TrustStore **must** be in separate files. If `keyStorePath` and `trustStorePath` points to the same file, +an error is shown. If the store files do not exist, they are created. The default and **recommended KeyStore Type** is `PKCS12`. The same type is used for both KeyStore and TrustStore. diff --git a/java-spiffe-helper/src/main/java/io/spiffe/helper/cli/Config.java b/java-spiffe-helper/src/main/java/io/spiffe/helper/cli/Config.java index f3f54c9..fdc9081 100644 --- a/java-spiffe-helper/src/main/java/io/spiffe/helper/cli/Config.java +++ b/java-spiffe-helper/src/main/java/io/spiffe/helper/cli/Config.java @@ -4,11 +4,7 @@ import io.spiffe.helper.exception.RunnerException; import io.spiffe.helper.keystore.KeyStoreHelper.KeyStoreOptions; import io.spiffe.helper.keystore.KeyStoreType; import lombok.val; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.DefaultParser; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.*; import org.apache.commons.lang3.StringUtils; import java.io.IOException; @@ -16,17 +12,18 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.InvalidParameterException; import java.util.Properties; class Config { + private static final String DEFAULT_CONFIG_FILENAME = "conf/java-spiffe-helper.properties"; + static final Option CONFIG_FILE_OPTION = Option.builder("c") - .longOpt("config") - .hasArg(true) - .required(true) - .build(); + .longOpt("config") + .hasArg(true) + .required(false) + .build(); private Config() { } @@ -42,17 +39,17 @@ class Config { return properties; } - static String getCliConfigOption(final String... args) throws RunnerException { + static String getCliConfigOption(final String... args) throws ParseException { final Options cliOptions = new Options(); cliOptions.addOption(CONFIG_FILE_OPTION); CommandLineParser parser = new DefaultParser(); - try { - val cmd = parser.parse(cliOptions, args); - return cmd.getOptionValue("config"); - } catch (ParseException e) { - val error = String.format("%s. Use -c, --config ", e.getMessage()); - throw new RunnerException(error); - } + + CommandLine cmd = parser.parse(cliOptions, args); + return cmd.getOptionValue("config", getDefaultConfigPath()); + } + + private static String getDefaultConfigPath() { + return Paths.get(System.getProperty("user.dir"), DEFAULT_CONFIG_FILENAME).toString(); } static KeyStoreOptions createKeyStoreOptions(final Properties properties) { @@ -89,7 +86,7 @@ class Config { static String getProperty(final Properties properties, final String key) { final String value = properties.getProperty(key); if (StringUtils.isBlank(value)) { - throw new InvalidParameterException(String.format("Missing value for config property: %s", key)); + throw new IllegalArgumentException(String.format("Missing value for config property: %s", key)); } return value; } diff --git a/java-spiffe-helper/src/main/java/io/spiffe/helper/cli/Runner.java b/java-spiffe-helper/src/main/java/io/spiffe/helper/cli/Runner.java index edc2fa0..def729e 100644 --- a/java-spiffe-helper/src/main/java/io/spiffe/helper/cli/Runner.java +++ b/java-spiffe-helper/src/main/java/io/spiffe/helper/cli/Runner.java @@ -6,13 +6,14 @@ import io.spiffe.helper.exception.RunnerException; import io.spiffe.helper.keystore.KeyStoreHelper; import lombok.extern.java.Log; import lombok.val; +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.exception.ExceptionUtils; import java.nio.file.Paths; -import java.security.InvalidParameterException; import java.security.KeyStoreException; /** - * Entry point of the CLI to run the KeyStoreHelper. + * Entry point of the java-spiffe-helper CLI application. */ @Log public class Runner { @@ -20,15 +21,19 @@ public class Runner { private Runner() { } - /** - * Entry method of the CLI to run the {@link KeyStoreHelper}. - *

- * In the args needs to be passed the config file option as: "-c" and "path_to_config_file" - * - * @param args contains the option with the config file path - * @throws RunnerException is there is an error configuring or creating the KeyStoreHelper. - */ - public static void main(final String ...args) throws RunnerException { + public static void main(final String... args) { + try { + runApplication(args); + } catch (RunnerException e) { + log.severe(ExceptionUtils.getStackTrace(e)); + System.exit(1); + } catch (ParseException | IllegalArgumentException e) { + log.severe(e.getMessage()); + System.exit(1); + } + } + + static void runApplication(final String... args) throws RunnerException, ParseException { try { val configFilePath = Config.getCliConfigOption(args); val properties = Config.parseConfigFileProperties(Paths.get(configFilePath)); @@ -36,8 +41,7 @@ public class Runner { try (val keyStoreHelper = KeyStoreHelper.create(options)) { keyStoreHelper.run(true); } - } catch (SocketEndpointAddressException | KeyStoreHelperException | RunnerException | InvalidParameterException | KeyStoreException e) { - log.severe(e.getMessage()); + } catch (SocketEndpointAddressException | KeyStoreHelperException | KeyStoreException e) { throw new RunnerException(e); } } diff --git a/java-spiffe-helper/src/main/java/io/spiffe/helper/keystore/KeyStore.java b/java-spiffe-helper/src/main/java/io/spiffe/helper/keystore/KeyStore.java index 0f49020..4d0d3d3 100644 --- a/java-spiffe-helper/src/main/java/io/spiffe/helper/keystore/KeyStore.java +++ b/java-spiffe-helper/src/main/java/io/spiffe/helper/keystore/KeyStore.java @@ -44,23 +44,29 @@ class KeyStore { private java.security.KeyStore loadKeyStore() throws KeyStoreException { try { - val keyStore = java.security.KeyStore.getInstance(keyStoreType.value()); - - // Initialize KeyStore - if (Files.exists(keyStoreFilePath)) { - try (final InputStream inputStream = Files.newInputStream(keyStoreFilePath)) { - keyStore.load(inputStream, keyStorePassword.toCharArray()); - } - } else { - //create new keyStore - keyStore.load(null, keyStorePassword.toCharArray()); - } - return keyStore; + return loadKeyStoreFromFile(); } catch (IOException | NoSuchAlgorithmException | CertificateException e) { throw new KeyStoreException("KeyStore cannot be created", e); } } + private java.security.KeyStore loadKeyStoreFromFile() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + val keyStore = java.security.KeyStore.getInstance(keyStoreType.value()); + + // Initialize KeyStore + if (Files.exists(keyStoreFilePath)) { + try (final InputStream inputStream = Files.newInputStream(keyStoreFilePath)) { + keyStore.load(inputStream, keyStorePassword.toCharArray()); + } catch (IOException e) { + throw new KeyStoreException("KeyStore cannot be opened", e); + } + } else { + // Create a new KeyStore if it doesn't exist + keyStore.load(null, keyStorePassword.toCharArray()); + } + return keyStore; + } + /** * Store a private key and X.509 certificate chain in a Java KeyStore diff --git a/java-spiffe-helper/src/test/java/io/spiffe/helper/cli/ConfigTest.java b/java-spiffe-helper/src/test/java/io/spiffe/helper/cli/ConfigTest.java index 9c6ceee..aaf8d55 100644 --- a/java-spiffe-helper/src/test/java/io/spiffe/helper/cli/ConfigTest.java +++ b/java-spiffe-helper/src/test/java/io/spiffe/helper/cli/ConfigTest.java @@ -4,6 +4,7 @@ import io.spiffe.helper.exception.RunnerException; import io.spiffe.helper.keystore.KeyStoreHelper; import io.spiffe.helper.keystore.KeyStoreType; import lombok.val; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.Test; @@ -12,8 +13,7 @@ import java.nio.file.Paths; import java.util.Properties; import static io.spiffe.utils.TestUtils.toUri; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; class ConfigTest { @@ -56,7 +56,7 @@ class ConfigTest { try { String option = Config.getCliConfigOption("-c", "test"); assertEquals("test", option); - } catch (RunnerException e) { + } catch (ParseException e) { fail(); } } @@ -66,27 +66,28 @@ class ConfigTest { try { String option = Config.getCliConfigOption("--config", "example"); assertEquals("example", option); - } catch (RunnerException e) { + } catch (ParseException e) { fail(); } } - @Test - void getCliConfigOption_unknownOption() { - try { - String option = Config.getCliConfigOption("-a", "test"); - } catch (RunnerException e) { - assertEquals("Unrecognized option: -a. Use -c, --config ", e.getMessage()); - } - } - @Test void testGetCliConfigOption_unknownLongOption() { try { Config.getCliConfigOption("--unknown", "example"); fail("expected parse exception"); - } catch (RunnerException e) { - assertEquals("Unrecognized option: --unknown. Use -c, --config ", e.getMessage()); + } catch (ParseException e) { + assertTrue(e.getMessage().startsWith("Unrecognized option: --unknown")); + } + } + + @Test + void getCliConfigOption_unknownOption() { + try { + String option = Config.getCliConfigOption("-a", "test"); + fail("expected parse exception"); + } catch (ParseException e) { + assertTrue(e.getMessage().startsWith("Unrecognized option: -a")); } } diff --git a/java-spiffe-helper/src/test/java/io/spiffe/helper/cli/RunnerTest.java b/java-spiffe-helper/src/test/java/io/spiffe/helper/cli/RunnerTest.java index b60b761..a7419b3 100644 --- a/java-spiffe-helper/src/test/java/io/spiffe/helper/cli/RunnerTest.java +++ b/java-spiffe-helper/src/test/java/io/spiffe/helper/cli/RunnerTest.java @@ -2,6 +2,7 @@ package io.spiffe.helper.cli; import io.spiffe.helper.exception.RunnerException; import lombok.val; +import org.apache.commons.cli.ParseException; import org.junit.jupiter.api.Test; import java.io.File; @@ -17,70 +18,70 @@ import static org.junit.jupiter.api.Assertions.fail; class RunnerTest { @Test - void test_Main_KeyStorePathIsMissing() throws URISyntaxException { + void test_Main_KeyStorePathIsMissing() throws URISyntaxException, RunnerException, ParseException { final Path path = Paths.get(toUri("testdata/cli/missing-keystorepath.conf")); try { - Runner.main("-c", path.toString()); + Runner.runApplication("-c", path.toString()); fail("expected exception: property is missing"); - } catch (RunnerException e) { - assertEquals("Missing value for config property: keyStorePath", e.getCause().getMessage()); + } catch (IllegalArgumentException e) { + assertEquals("Missing value for config property: keyStorePath", e.getMessage()); } } @Test - void test_Main_KeyStorePassIsMissing() throws URISyntaxException { + void test_Main_KeyStorePassIsMissing() throws URISyntaxException, RunnerException, ParseException { final Path path = Paths.get(toUri("testdata/cli/missing-keystorepass.conf")); try { - Runner.main("-c", path.toString()); + Runner.runApplication("-c", path.toString()); fail("expected exception: property is missing"); - } catch (RunnerException e) { - assertEquals("Missing value for config property: keyStorePass", e.getCause().getMessage()); + } catch (IllegalArgumentException e) { + assertEquals("Missing value for config property: keyStorePass", e.getMessage()); } } @Test - void test_Main_KeyPassIsMissing() throws URISyntaxException { + void test_Main_KeyPassIsMissing() throws URISyntaxException, RunnerException, ParseException { final Path path = Paths.get(toUri("testdata/cli/missing-keypass.conf")); try { - Runner.main("-c", path.toString()); + Runner.runApplication("-c", path.toString()); fail("expected exception: property is missing"); - } catch (RunnerException e) { - assertEquals("Missing value for config property: keyPass", e.getCause().getMessage()); + } catch (IllegalArgumentException e) { + assertEquals("Missing value for config property: keyPass", e.getMessage()); } } @Test - void test_Main_TrustStorePathIsMissing() throws URISyntaxException { + void test_Main_TrustStorePathIsMissing() throws URISyntaxException, RunnerException, ParseException { final Path path = Paths.get(toUri("testdata/cli/missing-truststorepath.conf")); try { - Runner.main("-c", path.toString()); + Runner.runApplication("-c", path.toString()); fail("expected exception: property is missing"); - } catch (RunnerException e) { - assertEquals("Missing value for config property: trustStorePath", e.getCause().getMessage()); + } catch (IllegalArgumentException e) { + assertEquals("Missing value for config property: trustStorePath", e.getMessage()); } } @Test - void test_Main_TrustStorePassIsMissing() throws URISyntaxException { + void test_Main_TrustStorePassIsMissing() throws URISyntaxException, RunnerException, ParseException { final Path path = Paths.get(toUri("testdata/cli/missing-truststorepass.conf")); try { - Runner.main("-c", path.toString()); + Runner.runApplication("-c", path.toString()); fail("expected exception: property is missing"); - } catch (RunnerException e) { - assertEquals("Missing value for config property: trustStorePass", e.getCause().getMessage()); + } catch (IllegalArgumentException e) { + assertEquals("Missing value for config property: trustStorePass", e.getMessage()); } } @Test - void test_Main_throwsExceptionIfTheKeystoreCannotBeCreated() throws URISyntaxException, IOException { + void test_Main_throwsExceptionIfTheKeystoreCannotBeCreated() throws URISyntaxException, IOException, ParseException { val file = new File("keystore123.p12"); file.createNewFile(); val configPath = Paths.get(toUri("testdata/cli/correct.conf")); try { - Runner.main("-c", configPath.toString()); + Runner.runApplication("-c", configPath.toString()); } catch (RunnerException e) { - assertEquals("KeyStore cannot be created", e.getCause().getMessage()); + assertEquals("KeyStore cannot be opened", e.getCause().getMessage()); } finally { file.delete(); } diff --git a/java-spiffe-helper/src/test/java/io/spiffe/helper/keystore/KeyStoreTest.java b/java-spiffe-helper/src/test/java/io/spiffe/helper/keystore/KeyStoreTest.java index b779f1f..aaf398c 100644 --- a/java-spiffe-helper/src/test/java/io/spiffe/helper/keystore/KeyStoreTest.java +++ b/java-spiffe-helper/src/test/java/io/spiffe/helper/keystore/KeyStoreTest.java @@ -203,7 +203,7 @@ public class KeyStoreTest { .keyStorePassword("example") .build(); } catch (KeyStoreException e) { - assertEquals("KeyStore cannot be created", e.getMessage()); + assertEquals("KeyStore cannot be opened", e.getMessage()); } finally { file.delete(); }