Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,26 @@ nd_builtin_cache: true
|-------|------|---------|-------------|
| `url` | string | - | Base URL of the service |
| `credentials.bearer.token` | string | - | Bearer token for authentication |
| `credentials.client_tls.cert` | path | - | PEM file path with the client certificate (mTLS) |
| `credentials.client_tls.private_key` | path | - | PKCS#8 PEM file path with the client private key |
| `credentials.client_tls.private_key_passphrase` | string | - | (Reserved — encrypted PEM keys not supported. Use a JKS / PKCS#12 keystore instead.) |
| `credentials.client_tls.cert_reread_interval_seconds` | int | - | Interval to reload the client cert/key from disk for rotation |
| `credentials.client_tls.keystore.path` | path | - | JKS / PKCS#12 keystore path holding the client cert + key |
| `credentials.client_tls.keystore.password` | string | - | Keystore password |
| `credentials.client_tls.keystore.key_password` | string | (defaults to `password`) | Password for the private key entry |
| `credentials.client_tls.keystore.type` | string | inferred from extension, else `PKCS12` | Keystore type (`JKS`, `PKCS12`) |
| `tls.ca_cert` | path | - | PEM file path with trust roots used to verify the server cert |
| `tls.system_ca_required` | boolean | false | Also trust the JVM default trust store in addition to `ca_cert` / `truststore` |
| `tls.truststore.path` | path | - | JKS / PKCS#12 truststore path used to verify the server cert |
| `tls.truststore.password` | string | - | Truststore password |
| `tls.truststore.type` | string | inferred from extension, else `PKCS12` | Truststore type (`JKS`, `PKCS12`) |
| `response_header_timeout_seconds` | int | 10 | HTTP response header timeout |
| `allow_insecure_tls` | boolean | false | Allow insecure TLS (dev only) |

Any string value may reference an environment variable with `${VAR}`; the SDK substitutes it during config load (matches Go-OPA). Use `\${VAR}` to keep a literal `${VAR}` in the file.

See [opa-services/README.md](opa-services/README.md#tls-and-mtls) for a full mTLS walkthrough, including JKS / PKCS#12 keystores and the programmatic `setSslContext` escape hatch for HSM-backed or rotated keys.

#### Bundles

| Field | Type | Default | Description |
Expand Down
123 changes: 123 additions & 0 deletions opa-services/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,129 @@ status:
service: acmecorp
```

### TLS and mTLS

Services support two related TLS blocks, mirroring Go-OPA:

- `services.<name>.tls` — trust roots used to verify the server certificate.
- `services.<name>.credentials.client_tls` — client certificate and key presented during the TLS handshake (mTLS).

Both apply to all HTTP traffic for the service: bundle downloads, decision-log uploads, status reports, and discovery.

```yaml
services:
acmecorp:
url: https://policy.example.com
tls:
ca_cert: /etc/ssl/corp-ca.pem
system_ca_required: true
credentials:
client_tls:
cert: /etc/ssl/client.pem
private_key: /etc/ssl/client-key.pem
cert_reread_interval_seconds: 3600
```

| Field | Description |
|-------|-------------|
| `tls.ca_cert` | Path to a PEM file containing one or more trust roots for verifying the server. |
| `tls.truststore.{path,password,type}` | Java-native JKS / PKCS#12 truststore (alternative to `ca_cert`). Mutually exclusive with `ca_cert`. |
| `tls.system_ca_required` | When `true`, the JVM's default trust store is also trusted in addition to `ca_cert` / `truststore`. |
| `credentials.client_tls.cert` | Path to a PEM file with the client certificate (and any intermediates). |
| `credentials.client_tls.private_key` | Path to an unencrypted PKCS#8 PEM file with the client private key. |
| `credentials.client_tls.cert_reread_interval_seconds` | If set, the cert and key are reloaded from disk on this interval to support runtime rotation. |
| `credentials.client_tls.keystore.{path,password,key_password,type}` | JKS / PKCS#12 keystore alternative (path is mutually exclusive with `cert` / `private_key`; supports password-protected keys). |

Only **unencrypted PKCS#8** PEM private keys are accepted by the file-based loader (the JDK has no first-class support for legacy PKCS#1 / SEC1 / encrypted PEMs without third-party crypto). Convert PKCS#1 keys with:

```sh
openssl pkcs8 -topk8 -nocrypt -in key.pem -out key-pkcs8.pem
```

For encrypted or password-protected keys, use a **JKS / PKCS#12 keystore** instead:

```yaml
services:
acmecorp:
url: https://policy.example.com
tls:
truststore:
path: /etc/ssl/truststore.jks
password: ${TRUSTSTORE_PASSWORD}
type: JKS
credentials:
client_tls:
keystore:
path: /etc/ssl/client.p12
password: ${KEYSTORE_PASSWORD}
key_password: ${KEY_PASSWORD}
```

Programmatic equivalent (file-based mTLS):

```java
Config config = new Config()
.addService(new Config.ServiceConfig()
.setName("acmecorp")
.setUrl("https://policy.example.com")
.setTls(new Config.TlsConfig()
.setCaCert("/etc/ssl/corp-ca.pem")
.setSystemCaRequired(true))
.setCredentials(new Config.CredentialsConfig()
.setClientTls(new Config.ClientTlsConfig()
.setCert("/etc/ssl/client.pem")
.setPrivateKey("/etc/ssl/client-key.pem")
.setCertRereadIntervalSeconds(3600))));
```

Programmatic equivalent (in-memory keystore from a secret manager — no files on disk):

```java
KeyStore clientStore = loadFromVault();
KeyStore trustStore = loadCaTrust();

Config.ServiceConfig service = new Config.ServiceConfig()
.setName("acmecorp")
.setUrl("https://policy.example.com")
.setTls(new Config.TlsConfig()
.setTruststore(new Config.TruststoreConfig().setKeyStore(trustStore)))
.setCredentials(new Config.CredentialsConfig()
.setClientTls(new Config.ClientTlsConfig()
.setKeystore(new Config.KeystoreConfig()
.setKeyStore(clientStore)
.setKeyPassword("vault-issued-key-pw"))));
```

For keystores that cannot be expressed any other way (HSM-backed keys, custom `KeyManager` chains), supply a fully constructed `SSLContext` directly. When set, file-based and keystore TLS fields are rejected during validation:

```java
SSLContext sslContext = buildSslContextFromHsm();

Config.ServiceConfig service = new Config.ServiceConfig()
.setName("acmecorp")
.setUrl("https://policy.example.com")
.setSslContext(sslContext);
Comment thread
sspaink marked this conversation as resolved.
```

### Environment-variable interpolation

Any string in YAML / JSON config may reference an environment variable with `${VAR}`. The SDK substitutes references at load time, matching Go-OPA's behaviour, so secrets stay out of committed config files:

```yaml
services:
acmecorp:
url: https://policy.example.com
credentials:
bearer:
token: ${OPA_BEARER_TOKEN}
tls:
truststore:
path: /etc/ssl/truststore.jks
password: ${TRUSTSTORE_PASSWORD}
```

Missing variables produce a `ConfigurationException` at startup — silent empty substitution would mask credential and TLS misconfiguration. Escape with a leading backslash to keep a literal `${VAR}` in the config (`\${VAR}`).

### Lifecycle Management

```java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.github.open_policy_agent.opa.bundle.Bundle;
import io.github.open_policy_agent.opa.config.Config;
import io.github.open_policy_agent.opa.config.ConfigurationException;
import io.github.open_policy_agent.opa.config.EnvInterpolator;
import io.github.open_policy_agent.opa.logging.Logger;
import io.github.open_policy_agent.opa.mapper.RegoMapper;
import io.github.open_policy_agent.opa.metrics.Metrics;
Expand Down Expand Up @@ -708,7 +709,16 @@ public Opa build() {
}
if (config == null) {
try {
config = YAML_MAPPER.readValue(configIn, Config.class);
// Read fully so we can apply ${VAR} env-var interpolation before parsing. Avoids
// plaintext secrets in committed YAML; matches Go-OPA's config-loading behaviour.
char[] buf = new char[8192];
StringBuilder raw = new StringBuilder();
int n;
while ((n = configIn.read(buf)) > 0) {
raw.append(buf, 0, n);
}
String interpolated = EnvInterpolator.interpolate(raw.toString());
config = YAML_MAPPER.readValue(interpolated, Config.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand Down
Loading
Loading