Enhancements: Example Config File, Default Path, and Error Handling Improvements (#199)

Add default helper config
Improving java-spiffe-helper Runner and Config logic
Improve error handling in java-spiffe-helper
Update README

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
This commit is contained in:
Max Lambrecht 2024-02-05 12:13:31 -03:00 committed by GitHub
parent 56bb734de3
commit 666766a90f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 134 additions and 102 deletions

View File

@ -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

View File

@ -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.

View File

@ -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 <arg>", 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;
}

View File

@ -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}.
* <p>
* 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);
}
}

View File

@ -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

View File

@ -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 <arg>", 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 <arg>", 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"));
}
}

View File

@ -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();
}

View File

@ -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();
}