- * - An explicit literal in the request body ({@code config}) -- treated as
+ *
- An explicit literal in the request body ({@code config}), treated as
* either a filesystem path or raw base64/JSON content.
- * - A named config in the request body ({@code configName}) -- looked up
+ *
- A named config in the request body ({@code configName}), looked up
* against the configured directory and environment variable prefix
* (in that order).
* - The service default ({@code keeper.config} from properties).
@@ -123,7 +123,8 @@ public KSM resolve(String config, String configName) {
*
* @param raw The value to interpret.
* @return The resolved config. Never {@code null}.
- * @throws IllegalArgumentException if {@code raw} is none of the three.
+ * @throws IllegalArgumentException if {@code raw} is none of the three
+ * or names an existing file that cannot be read.
*/
public KSM interpret(String raw) {
Objects.requireNonNull(raw, "raw");
@@ -145,6 +146,9 @@ public KSM interpret(String raw) {
path.toString());
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to read Keeper config file.", e);
+ throw new IllegalArgumentException(
+ "config points at " + path + " but reading it failed: "
+ + e.getMessage(), e);
}
}
diff --git a/src/main/java/com/byteskeptical/credcat/config/TlsConfig.java b/src/main/java/com/byteskeptical/credcat/config/TlsConfig.java
new file mode 100644
index 0000000..8a3b35a
--- /dev/null
+++ b/src/main/java/com/byteskeptical/credcat/config/TlsConfig.java
@@ -0,0 +1,318 @@
+package com.byteskeptical.credcat.config;
+
+import com.byteskeptical.credcat.util.Checks;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsParameters;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.TrustManagerFactory;
+
+/**
+ * Optional TLS for the embedded server, resolved from the
+ * {@code server.tls.*} properties.
+ *
+ * Disabled by default. When {@code server.tls.enabled=true} the server
+ * identity is read from a keystore ({@code server.tls.keystore}) and the
+ * keystore password is resolved in two steps:
+ *
+ *
+ * - {@code server.tls.keystore_password}: a literal in the properties
+ * file. Convenient for development; avoid in production.
+ * - {@code server.tls.keystore_password_env}: the name of an
+ * environment variable holding the password. The recommended option.
+ *
+ *
+ * Mutual TLS is available via {@code server.tls.client_auth} (NONE, WANT,
+ * NEED) with an optional dedicated truststore; without one, the JVM's default
+ * trust anchors are used. Enabled protocols can be pinned with
+ * {@code server.tls.protocols}.
+ *
+ * Construction only parses; the keystore is opened by
+ * {@link #createConfigurator()} so a bad TLS setup fails loudly at server
+ * startup rather than on the first request. This class never logs key
+ * material or passwords.
+ */
+public final class TlsConfig {
+
+ private static final Logger LOGGER = Logger.getLogger(TlsConfig.class.getName());
+
+ /** The instance handed out when TLS is disabled. */
+ private static final TlsConfig DISABLED = new TlsConfig(
+ false, ClientAuth.NONE, null, null, null, List.of(), null, null, null);
+
+ private final boolean enabled;
+ private final ClientAuth clientAuth;
+ private final char[] keystorePassword;
+ private final List protocols;
+ private final Path keystorePath;
+ private final Path truststorePath;
+ private final char[] truststorePassword;
+ private final String keystoreType;
+ private final String truststoreType;
+
+ /**
+ * How the server treats client certificates during the handshake.
+ */
+ public enum ClientAuth {
+ /** Never request a client certificate. The default. */
+ NONE,
+ /** Request a certificate; proceed without one. */
+ WANT,
+ /** Require a certificate; refuse the handshake without one. */
+ NEED;
+
+ /**
+ * Parser for the client auth configuration value. Ignores case,
+ * falls back to {@link #NONE} when the input is null, blank, or
+ * unrecognized.
+ *
+ * @param value The textual value to parse. May be {@code null}.
+ * @return A non-null ClientAuth.
+ */
+ static ClientAuth parse(String value) {
+ String trimmed = Checks.trimToNull(value);
+
+ if (trimmed == null) {
+ return NONE;
+ }
+
+ try {
+ return ClientAuth.valueOf(trimmed.toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException e) {
+ LOGGER.log(Level.WARNING,
+ "Unrecognized server.tls.client_auth ''{0}''; using NONE.",
+ trimmed);
+ return NONE;
+ }
+ }
+ }
+
+ private TlsConfig(boolean enabled, ClientAuth clientAuth,
+ Path keystorePath, String keystoreType, char[] keystorePassword,
+ List protocols,
+ Path truststorePath, String truststoreType,
+ char[] truststorePassword) {
+ this.clientAuth = clientAuth;
+ this.enabled = enabled;
+ this.keystorePassword = keystorePassword;
+ this.keystorePath = keystorePath;
+ this.keystoreType = keystoreType;
+ this.protocols = protocols;
+ this.truststorePassword = truststorePassword;
+ this.truststorePath = truststorePath;
+ this.truststoreType = truststoreType;
+ }
+
+ /**
+ * Builds a TlsConfig from the {@code server.tls.*} properties.
+ *
+ * @param props The loaded application properties.
+ * @return An enabled TlsConfig, or the disabled instance when
+ * {@code server.tls.enabled} is unset or false.
+ * @throws IllegalArgumentException if TLS is enabled but the keystore
+ * path or its password cannot be resolved.
+ */
+ public static TlsConfig fromProperties(Properties props) {
+ boolean enabled = Boolean.parseBoolean(
+ props.getProperty("server.tls.enabled", "false"));
+
+ if (!enabled) {
+ return DISABLED;
+ }
+
+ String keystoreProp = Checks.trimToNull(
+ props.getProperty("server.tls.keystore"));
+ if (keystoreProp == null) {
+ throw new IllegalArgumentException(
+ "server.tls.enabled is true but server.tls.keystore is not set.");
+ }
+
+ char[] keystorePassword = resolvePassword(props,
+ "server.tls.keystore_password",
+ "server.tls.keystore_password_env");
+ if (keystorePassword == null) {
+ throw new IllegalArgumentException(
+ "server.tls.enabled is true but no keystore password was "
+ + "resolved. Set server.tls.keystore_password or point "
+ + "server.tls.keystore_password_env at an environment "
+ + "variable holding it.");
+ }
+
+ String protocolsProp = Checks.trimToNull(
+ props.getProperty("server.tls.protocols"));
+ List protocols = protocolsProp == null
+ ? List.of()
+ : Arrays.stream(protocolsProp.split(","))
+ .map(Checks::trimToNull)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toUnmodifiableList());
+
+ String truststoreProp = Checks.trimToNull(
+ props.getProperty("server.tls.truststore"));
+
+ return new TlsConfig(
+ true,
+ ClientAuth.parse(props.getProperty("server.tls.client_auth")),
+ Path.of(keystoreProp),
+ props.getProperty("server.tls.keystore_type", "PKCS12"),
+ keystorePassword,
+ protocols,
+ truststoreProp != null ? Path.of(truststoreProp) : null,
+ props.getProperty("server.tls.truststore_type", "PKCS12"),
+ resolvePassword(props,
+ "server.tls.truststore_password",
+ "server.tls.truststore_password_env"));
+ }
+
+ /**
+ * Is TLS on for this deployment?.
+ *
+ * @return {@code true} when the server should listen for HTTPS.
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * Opens the keystore (and truststore, when configured), builds the
+ * SSLContext, and wraps it in a configurator that applies the protocol
+ * pinning and client auth policy to every handshake.
+ *
+ * @return A configurator ready for {@code HttpsServer.setHttpsConfigurator}.
+ * @throws IOException if a store file cannot be read.
+ * @throws GeneralSecurityException if a store cannot be opened with the
+ * resolved password or the SSLContext cannot be initialized.
+ * @throws IllegalStateException if invoked while TLS is disabled.
+ */
+ public HttpsConfigurator createConfigurator()
+ throws IOException, GeneralSecurityException {
+ if (!enabled) {
+ throw new IllegalStateException("TLS is not enabled.");
+ }
+
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(
+ KeyManagerFactory.getDefaultAlgorithm());
+ try {
+ KeyStore keystore = loadStore(
+ keystorePath, keystoreType, keystorePassword);
+ kmf.init(keystore, keystorePassword);
+ } finally {
+ Arrays.fill(keystorePassword, '\0');
+ }
+
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ if (truststorePath != null) {
+ try {
+ tmf.init(loadStore(
+ truststorePath, truststoreType, truststorePassword));
+ } finally {
+ if (truststorePassword != null) {
+ Arrays.fill(truststorePassword, '\0');
+ }
+ }
+ } else {
+ // null keystore == the JVM's default trust anchors (cacerts).
+ tmf.init((KeyStore) null);
+
+ if (clientAuth != ClientAuth.NONE) {
+ LOGGER.log(Level.WARNING,
+ "Client auth is {0} but no server.tls.truststore is "
+ + "set; client certificates will be validated against "
+ + "the JVM default trust anchors.", clientAuth);
+ }
+ }
+
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+ LOGGER.log(Level.INFO,
+ "TLS enabled (keystore: {0}, client auth: {1}{2}).",
+ new Object[] {
+ keystorePath,
+ clientAuth,
+ protocols.isEmpty() ? "" : ", protocols: " + protocols });
+
+ return new HttpsConfigurator(context) {
+ @Override
+ public void configure(HttpsParameters params) {
+ SSLParameters ssl = getSSLContext().getDefaultSSLParameters();
+
+ if (!protocols.isEmpty()) {
+ ssl.setProtocols(protocols.toArray(new String[0]));
+ }
+
+ switch (clientAuth) {
+ case NEED:
+ ssl.setNeedClientAuth(true);
+ break;
+ case WANT:
+ ssl.setWantClientAuth(true);
+ break;
+ case NONE:
+ default:
+ break;
+ }
+
+ params.setSSLParameters(ssl);
+ }
+ };
+ }
+
+ /**
+ * Loads a keystore file with the given type and password.
+ */
+ private static KeyStore loadStore(Path path, String type, char[] password)
+ throws IOException, GeneralSecurityException {
+ KeyStore store = KeyStore.getInstance(type);
+
+ try (InputStream is = Files.newInputStream(path)) {
+ store.load(is, password);
+ }
+
+ return store;
+ }
+
+ /**
+ * Resolves a password from a literal property first, then from the
+ * environment variable named by the companion {@code *_env} property.
+ *
+ * @return The password, or {@code null} when neither source yields one.
+ */
+ private static char[] resolvePassword(
+ Properties props, String literalKey, String envKey) {
+ String literal = Checks.trimToNull(props.getProperty(literalKey));
+ if (literal != null) {
+ return literal.toCharArray();
+ }
+
+ String envName = Checks.trimToNull(props.getProperty(envKey));
+ if (envName != null) {
+ String value = Checks.trimToNull(System.getenv(envName));
+ if (value != null) {
+ return value.toCharArray();
+ }
+
+ LOGGER.log(Level.WARNING,
+ "{0} points at ''{1}'' but that variable is unset or blank.",
+ new Object[] { envKey, envName });
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/com/byteskeptical/credcat/file/FileHandler.java b/src/main/java/com/byteskeptical/credcat/file/FileHandler.java
index de19ed8..76d58a9 100644
--- a/src/main/java/com/byteskeptical/credcat/file/FileHandler.java
+++ b/src/main/java/com/byteskeptical/credcat/file/FileHandler.java
@@ -12,6 +12,7 @@
import java.nio.file.Path;
import java.util.Base64;
import java.util.Objects;
+import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -120,6 +121,11 @@ public SecretResponse.FileInfo handle(KeeperFile file) throws IOException {
Metadata m = metadataFor(file);
Path target = downloadDir.resolve(sanitize(m.name));
+ if (Files.exists(target)) {
+ target = downloadDir.resolve(
+ UUID.randomUUID() + "_" + target.getFileName());
+ }
+
try (OutputStream os = new BufferedOutputStream(
Files.newOutputStream(target), BUFFER_SIZE)) {
os.write(bytes);
@@ -133,12 +139,20 @@ public SecretResponse.FileInfo handle(KeeperFile file) throws IOException {
m.name, target.toString(), m.mime, (long) bytes.length);
}
+ /**
+ * Keeps an attachment name inside the download directory: separators
+ * become underscores, and the bare dot names ({@code "."},
+ * {@code ".."}) that would resolve to the directory itself or its
+ * parent fall back to {@code "unnamed"}.
+ */
private static String sanitize(String name) {
if (Checks.isNullOrBlank(name)) {
return "unnamed";
}
- return name.replace('/', '_').replace('\\', '_');
+ String safe = name.replace('/', '_').replace('\\', '_');
+
+ return ".".equals(safe) || "..".equals(safe) ? "unnamed" : safe;
}
}
diff --git a/src/main/java/com/byteskeptical/credcat/model/KeeperRequest.java b/src/main/java/com/byteskeptical/credcat/model/KeeperRequest.java
index 7768add..56f5b88 100644
--- a/src/main/java/com/byteskeptical/credcat/model/KeeperRequest.java
+++ b/src/main/java/com/byteskeptical/credcat/model/KeeperRequest.java
@@ -98,7 +98,7 @@ public String getSaveLocation() {
/**
* Sets the save location for downloaded files.
*
- * @param saveLocation The location to save downloaded files too.
+ * @param saveLocation The location to save downloaded files to.
*/
public void setSaveLocation(String saveLocation) {
this.saveLocation = saveLocation;
@@ -107,14 +107,14 @@ public void setSaveLocation(String saveLocation) {
/**
* Gets the list of titles to search for.
*
- * @return The list of titles cooresponding to records for lookup.
+ * @return The list of titles corresponding to records for lookup.
*/
public List getTitles() {
return titles;
}
/**
- * Sets the list of titles to preform a record search on.
+ * Sets the list of titles to perform a record search on.
*
* @param titles A list with titles to search records for.
*/
@@ -125,14 +125,14 @@ public void setTitles(List titles) {
/**
* Gets the list of uid(s) to search for.
*
- * @return The list of uid(s) cooresponding to records for lookup.
+ * @return The list of uid(s) corresponding to records for lookup.
*/
public List getUids() {
return uids;
}
/**
- * Sets the list of uid(s) to preform a record search on.
+ * Sets the list of uid(s) to perform a record search on.
*
* @param uids A list with uid(s) to search records for.
*/
diff --git a/src/main/java/com/byteskeptical/credcat/util/Checks.java b/src/main/java/com/byteskeptical/credcat/util/Checks.java
index c5c1d40..9bfce5f 100644
--- a/src/main/java/com/byteskeptical/credcat/util/Checks.java
+++ b/src/main/java/com/byteskeptical/credcat/util/Checks.java
@@ -9,14 +9,14 @@
* Null-tolerant value checks and normalizations.
*
*
- * - {@link #isBase64(String)} -- valid standard base64?
- * - {@link #isJson(String)} -- valid JSON?
- * - {@link #isNullOrBlank(String)} -- treats whitespace as missing. The
+ *
- {@link #isBase64(String)} valid standard base64?
+ * - {@link #isJson(String)} valid JSON?
+ * - {@link #isNullOrBlank(String)} treats whitespace as missing. The
* right default for config values, request fields, identifiers.
- * - {@link #isNullOrEmpty(Collection)} -- the parallel emptiness check
+ *
- {@link #isNullOrEmpty(Collection)} the parallel emptiness check
* for any {@link Collection}; collections have no {@code isBlank()}
* equivalent.
- * - {@link #trimToNull(String)} -- both checks and returns the cleaned
+ *
- {@link #trimToNull(String)} both checks and returns the cleaned
* value in one pass. The right tool for property reads from
* hand-edited files where trailing whitespace is a real risk.
*
diff --git a/src/main/resources/credcat.properties b/src/main/resources/credcat.properties
index 636042b..2401cf9 100644
--- a/src/main/resources/credcat.properties
+++ b/src/main/resources/credcat.properties
@@ -11,7 +11,7 @@
# - filesystem path to a config file (e.g. /etc/credcat/keeper.json)
# - literal base64-encoded config string
# - literal JSON config string
-#keeper.config=/etc/credcat/configs/devops.json
+#keeper.config=/etc/credcat/configs/dev.json
# Optional: Directory holding multiple named configs (e.g. dev.json, qa.json,
# prod.json). A request can pick which one to use via "configName": "dev".
@@ -64,3 +64,40 @@ server.port=8888
# Reject request bodies larger than this many bytes with HTTP 413.
# Default 1 MiB.
#server.max_request_bytes=1048576
+
+# ===========
+# TLS / HTTPS
+# ===========
+
+# Serve HTTPS instead of plain HTTP. Default: Off.
+#server.tls.enabled=false
+
+# Required when TLS is enabled. Keystore holding the server certificate and
+# private key.
+#server.tls.keystore=/etc/credcat/tls/server.p12
+
+# Keystore password, resolved in order:
+# 1. server.tls.keystore_password literal value (dev convenience)
+# 2. server.tls.keystore_password_env NAME of an env var holding it
+# Prefer the env var route in production; keeps the password out of this file
+# PKCS12 is the default and recommended type.
+#server.tls.keystore_password=
+#server.tls.keystore_password_env=CREDCAT_KEYSTORE_PASSWORD
+#server.tls.keystore_type=PKCS12
+
+# Optional: Mutual TLS. NONE never asks for a client certificate, WANT asks
+# but proceeds without one, NEED refuses the handshake without one.
+# Default: NONE.
+#server.tls.client_auth=NONE
+
+# Optional: Pin the enabled protocol versions, comma-separated. When unset,
+# the JVM defaults apply.
+#server.tls.protocols=TLSv1.3,TLSv1.2
+
+# Optional: Truststore of client CAs, only consulted when client_auth is WANT
+# or NEED. Without one, the JVM default trust anchors are used. Password
+# resolution mirrors the keystore, literal first, then env var name.
+#server.tls.truststore=/etc/credcat/tls/clients.p12
+#server.tls.truststore_password=
+#server.tls.truststore_password_env=
+#server.tls.truststore_type=PKCS12
From 61838ed4e8851d61a3bc0caeac82a10801ffcd88 Mon Sep 17 00:00:00 2001
From: byteskeptical <40208858+byteskeptical@users.noreply.github.com>
Date: Wed, 1 Jul 2026 02:59:27 +0000
Subject: [PATCH 2/2] adding tls support to server mode, adding .base64
extenstion to Keeper config resolution, adding ability to set
credcat.properties location using environment varibles or -D runtime flag,
updating README.md
---
README.md | 53 ++++++++++---------
pom.xml | 6 +--
.../byteskeptical/credcat/SecretsService.java | 19 +++++--
.../credcat/config/KeeperConfig.java | 4 +-
src/main/resources/credcat.properties | 18 +++----
5 files changed, 58 insertions(+), 42 deletions(-)
diff --git a/README.md b/README.md
index 9cdff7c..b415433 100644
--- a/README.md
+++ b/README.md
@@ -77,10 +77,9 @@
## About The Project
-Extending access to Keeper secrets manager for api retrival in
-distributed or disconnected processes. Serves as a quality of life
-abstraction to diminish the scourge of hard-coded, insecurely
-handled credentials in our code bases.
+Extending access to Keeper secrets manager for api retrival in distributed or
+disconnected processes. Serves as a quality of life abstraction to diminish the
+scourge of hard-coded, insecurely handled credentials in our code bases.
(back to top)
@@ -99,14 +98,14 @@ _Java is like a bad relationship. It's too object-oriented_
## Getting Started
-Compiling is not necessary as release binaries are available. If
-you're so inclined the sections below are for you.
+Compiling is not necessary as release binaries are available. If you're so
+inclined the sections below are for you.
### Prerequisites
-Your going to need a compiler, I recommend anything not Oracle java.
-Depending on your os, the installation process will vary. Additional
-packages like maven will be needed to utilize the provided pom file.
+Your going to need a compiler, I recommend anything not Oracle java. Depending
+on your os, the installation process will vary. Additional packages like maven
+will be needed to utilize the provided pom file.
#### CentOS
* bash
@@ -188,8 +187,11 @@ packages like maven will be needed to utilize the provided pom file.
### Configuration
Every knob lives in `credcat.properties`. All are optional and fall back to sane
-defaults, so an empty file is a working file. The `server.*` settings are ignored
-in stand-alone mode.
+defaults, so an empty file is a working file. The `server.*` settings are
+ignored in stand-alone mode. This file can be placed in the current working
+directory of the jar file to avoid needing to recompile on change. That location
+can be overriden by setting the `CREDCAT_CONFIG_FILE` environment variable or
+the `-Dcredcat.config.file` flag on init.
```properties
# Keeper
@@ -224,9 +226,9 @@ in stand-alone mode.
server.tls.truststore_type=PKCS12 # BKS | JKS | KeychainStore | PKCS12
```
-A named lookup (`configName`) is resolved against `keeper.config.dir` first, then
-the `keeper.config.env` prefix; the literal `config` parameter always wins when both
-are present, and the `keeper.config` default backs them all.
+A named lookup `configName` is resolved against `keeper.config.dir` first, then
+the `keeper.config.env` prefix. The literal `config` parameter always wins when both
+are present and the `keeper.config` default backs them all.
@@ -242,15 +244,16 @@ parameter to switch between pre-defined choices stashed in either a directory or
through environment variables. The `config`, `configName` and `clientKey`
parameters are your means to alternate between application vaults.
-Pass one or more of either titles and/or record uid's to retrieve multiple records
-at once. Exact matches only.
+Pass one or more of either titles and/or record uid's to retrieve multiple
+records at once. Exact matches only.
-Attached files are handed back however your deployment prefers, set globally with
-the `file.transport` property or overridden per-request with `fileTransport`:
+Attached files are handed back however your deployment prefers, set globally
+with the `file.transport` property or overridden per-request with
+`fileTransport`:
* `disk` written to the save location, whose path is returned in the response.
-* `inline` base64 encoded straight into the response; nothing touches the disk.
-* `none` skipped entirely; only the file's metadata comes back.
+* `inline` base64 encoded straight into the response, nothing touches the disk.
+* `none` skipped entirely, only the file's metadata comes back.
```sh
Usage: java -jar credcat.jar [ -server | '{ "config": ".keeper/config.base64", "titles": ["RECORD_TITLE"], "uids": ["RECORD_UID"] }' ]
@@ -267,7 +270,7 @@ the `file.transport` property or overridden per-request with `fileTransport`:
2. Whether passing title or uid, records are returned nested under its respective uid.
Using the `disk` transport:
```sh
- java -cp "target/classes:target/dependency/*" com.byteskeptical.credcat.SecretsService "$ADVANCED"
+ java -cp "target/classes:target/lib/*" com.byteskeptical.credcat.SecretsService "$ADVANCED"
java -jar target/credcat.jar "$UID_ONLY"
```
```json
@@ -282,9 +285,9 @@ the `file.transport` property or overridden per-request with `fileTransport`:
},
"chnmGhEC39YCHhNy1pA8vg" : {
"fields" : {
- "password" : [ "be0d988f-063c-d654-ad1b-a54337f87233" ],
- "login" : [ "integration.ucaas.call.metadata" ],
- "fileref" : [ "3HcX3vCCvHBTBcOqCgCnsQ", "cGBiPmG_9GlZszFbsQmJea" ]
+ "password" : [ "be0d988f-063c-d654-ad1b-a54337f87233" ],
+ "login" : [ "integration.ucaas.call.metadata" ],
+ "fileref" : [ "3HcX3vCCvHBTBcOqCgCnsQ", "cGBiPmG_9GlZszFbsQmJea" ]
},
"files" : [ {
"name" : "ascii-art.txt",
@@ -327,7 +330,7 @@ the `file.transport` property or overridden per-request with `fileTransport`:
3. Running in server mode accepts the same request payload, passed by the http client of your choice.
You can set your preferred host and port in the credcat properties file.
```sh
- java -cp "target/classes:target/dependency/*" -server
+ java -cp "target/classes:target/lib/*" -server
java -jar target/credcat.jar -server
```
```sh
diff --git a/pom.xml b/pom.xml
index 5a1ad4d..2ce0532 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
credcat
jar
https://${source.host}/${source.account}/${project.name}/
- 1.2.0
+ 1.1.0
@@ -65,7 +65,7 @@
false
- ${project.build.directory}/dependency
+ ${project.build.directory}/lib
false
@@ -103,7 +103,7 @@
true
- dependency/
+ lib/
${project.groupId}.credcat.SecretsService
diff --git a/src/main/java/com/byteskeptical/credcat/SecretsService.java b/src/main/java/com/byteskeptical/credcat/SecretsService.java
index eadd512..055ba2c 100644
--- a/src/main/java/com/byteskeptical/credcat/SecretsService.java
+++ b/src/main/java/com/byteskeptical/credcat/SecretsService.java
@@ -54,6 +54,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -101,7 +102,7 @@ public class SecretsService {
private static final String CONFIG = "credcat.properties";
private static final String DOMAIN = "keepersecurity.com";
private static final String LOGGING = "logging.properties";
- private static final String VERSION = "1.2.0";
+ private static final String VERSION = "1.1.0";
static {
initLogging();
@@ -156,8 +157,13 @@ static class AppConfig {
}
// Load from Filesystem
- File fsConfig = new File(CONFIG);
- if (fsConfig.exists() && fsConfig.isFile()) {
+ String override = Checks.trimToNull(System.getProperty("credcat.config.file"));
+ if (override == null) {
+ override = Checks.trimToNull(System.getenv("CREDCAT_CONFIG_FILE"));
+ }
+
+ File fsConfig = new File(override != null ? override : CONFIG);
+ if (fsConfig.isFile()) {
try (FileInputStream fis = new FileInputStream(fsConfig)) {
props.load(fis);
LOGGER.log(Level.INFO,
@@ -166,11 +172,16 @@ static class AppConfig {
);
} catch (IOException e) {
LOGGER.log(Level.SEVERE,
- "Found the config " + CONFIG
+ "Found the config " + fsConfig.getAbsolutePath()
+ " but failed to read it. Check the permissions.", e
);
throw e;
}
+ } else if (override != null) {
+ throw new FileNotFoundException(
+ "credcat.config.file points at " + fsConfig.getAbsolutePath()
+ + " but no readable file was found there."
+ );
}
this.autoClean = Boolean.parseBoolean(
diff --git a/src/main/java/com/byteskeptical/credcat/config/KeeperConfig.java b/src/main/java/com/byteskeptical/credcat/config/KeeperConfig.java
index dae4061..bd71b32 100644
--- a/src/main/java/com/byteskeptical/credcat/config/KeeperConfig.java
+++ b/src/main/java/com/byteskeptical/credcat/config/KeeperConfig.java
@@ -36,7 +36,9 @@ public class KeeperConfig {
private static final Logger LOGGER = Logger.getLogger(KeeperConfig.class.getName());
/** Filename extensions tried during named lookup. */
- private static final List EXTENSIONS = List.of(".json", ".b64", "");
+ private static final List EXTENSIONS = List.of(
+ ".json", ".b64", "base64", ""
+ );
private final String configContent;
private final Path configDir;
diff --git a/src/main/resources/credcat.properties b/src/main/resources/credcat.properties
index 2401cf9..21a3801 100644
--- a/src/main/resources/credcat.properties
+++ b/src/main/resources/credcat.properties
@@ -15,7 +15,7 @@
# Optional: Directory holding multiple named configs (e.g. dev.json, qa.json,
# prod.json). A request can pick which one to use via "configName": "dev".
-# Tries .json, .b64, then no-extension when resolving a name.
+# Tries .json, .b64, .base64, then no-extension when resolving a name.
#keeper.config.dir=/etc/credcat/configs
# Optional: Environment variable prefix for named configs. Looking up
@@ -39,15 +39,15 @@
file.clean=true
# Default file transport. Per-request override is "fileTransport" in JSON body.
-# DISK -- write to keeper.files and return paths (best for CLI / shared FS)
-# INLINE -- base64-encode into the JSON response (required on managed /
-# restricted platforms)
-# NONE -- skip file downloads, return metadata only
+# DISK write to keeper.files and return paths (best for CLI / shared FS)
+# INLINE base64-encode into the JSON response (required on managed /
+# restricted platforms)
+# NONE skip file downloads, return metadata only
file.transport=INLINE
# Optional: Defaults to OS temp directory:
-# Windows: %USERPROFILE%\AppData\Local\Temp\credcat_*
-# Linux/Mac: /tmp/credcat_*
+# Windows: %USERPROFILE%\AppData\Local\Temp\credcat_*
+# Linux/Mac: /tmp/credcat_*
#keeper.files=
# ================
@@ -77,9 +77,9 @@ server.port=8888
#server.tls.keystore=/etc/credcat/tls/server.p12
# Keystore password, resolved in order:
-# 1. server.tls.keystore_password literal value (dev convenience)
+# 1. server.tls.keystore_password literal value
# 2. server.tls.keystore_password_env NAME of an env var holding it
-# Prefer the env var route in production; keeps the password out of this file
+# Prefer the env var route in production. Keeps the password out of this file
# PKCS12 is the default and recommended type.
#server.tls.keystore_password=
#server.tls.keystore_password_env=CREDCAT_KEYSTORE_PASSWORD