Skip to content

DND-1415: native in-pod HTTPS/TLS support (GH #14) - #26

Merged
CRThaze merged 3 commits into
mainfrom
CRThaze/DND-1415/native-tls
Jul 22, 2026
Merged

DND-1415: native in-pod HTTPS/TLS support (GH #14)#26
CRThaze merged 3 commits into
mainfrom
CRThaze/DND-1415/native-tls

Conversation

@CRThaze

@CRThaze CRThaze commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Why

GH #14 asks for native HTTPS: configure a PKCS12 keystore path + password, ideally via a Secret. Today the chart binds plain HTTP only (s3proxy.endpoint=http://...), so TLS can only be terminated at the ingress. This adds in-pod TLS termination via S3Proxy's secure-endpoint.

What

New config.tls block. When config.tls.enabled=true:

  • S3Proxy serves HTTPS only on service.targetPort via s3proxy.secure-endpoint (the plaintext endpoint is not bound). The container/Service port is named https.
  • tcpSocket liveness/readiness probes are unchanged: they only check the TCP accept, so they work against a TLS port without a handshake.
  • The keystore password is kept out of the ConfigMap; it is merged into the backend properties from the Secret by the existing config-merge initContainer.

Keystore + password sources (both supported):

  • Existing Secret (recommended, also the cert-manager path): keystore.existingSecret / secretKey and keystorePassword.existingSecret / secretKey. Point these at the Secret a cert-manager Certificate (spec.keystores.pkcs12) produces.
  • Inline: keystore.value (base64 PKCS12, stored in the chart's Secret data:) and keystorePassword.value (stored in secret-common.properties). Convenient for testing.

PKCS12 is the expected format (Jetty's default keystore type); S3Proxy exposes only path + password, so no keystore type is set. JKS also works if supplied.

Verified against S3Proxy source (s3proxy-3.3.0)

  • Property names s3proxy.secure-endpoint / s3proxy.keystore-path / s3proxy.keystore-password (S3ProxyConstants.java).
  • HTTP and HTTPS connectors are independent and either may be used alone; at least one is required (S3Proxy.java).
  • With a secure endpoint set, keystore path + password are mandatory and are handed to Jetty's SslContextFactory.Server.

Testing

CI coverage added:

  • test-values/tls.yaml (inline) + test-values/tls-existing-secret.yaml in the lint-render matrix (both code paths).
  • A tls functional leg in functional-test.yaml (self-signed keystore Secret, HTTPS round-trip).

Notes

  • Chart 0.2.0 to 0.3.0 (minor; new feature).
  • Backward compatible: TLS defaults off; existing releases keep plaintext HTTP and the http port name. The non-TLS render is unchanged apart from the chart version label and the recomputed checksums.

Add config.tls to terminate HTTPS in the pod via S3Proxy's secure-endpoint,
using a PKCS12 (or JKS) keystore + password supplied by an existing Secret
(recommended; also the cert-manager path) or inline. When enabled, the proxy
serves HTTPS only on service.targetPort (port named https); tcpSocket probes
are unchanged (they do not perform a TLS handshake). The keystore password is
kept out of the ConfigMap: it is merged from the Secret by the config-merge
initContainer (inline via secret-common.properties, existingSecret via a
mounted file).

- values/configmap/secret/deployment/service: config.tls block, secure-endpoint
  + keystore-path, keystore mount at /tls, password via secret-merge, https port.
- CI: test-values/tls.yaml + tls-existing-secret.yaml (lint-render) and a kind
  functional TLS leg that builds a self-signed keystore Secret and round-trips
  over HTTPS. smoke-test.sh gains an optional SCHEME arg.
- README / override example: TLS / HTTPS section. Chart 0.2.0 -> 0.3.0.

Verified: helm lint, kubeconform -strict, helm-polish, shellcheck, actionlint,
non-TLS render unchanged, and a kind end-to-end HTTPS round-trip.
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Replace the duplicated ternary at the top of deployment.yaml and service.yaml
with a shared s3proxy.portName helper in _helpers.tpl. Render is byte-identical
for both TLS-on and TLS-off.
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

- Convert .cursorrules to CLAUDE.md and strengthen the README rule: never
  hand-edit or regenerate README.md in a PR. Edit README.md.gotmpl + values.yaml;
  the release workflow renders and commits README.md on merge, and
  preview-readme previews the diff on the PR.
- Revert the generated README.md in this PR back to base (let the release
  action render it).
- Indent the ternary in the s3proxy.portName helper body (cosmetic; render
  identical for TLS on and off).
@github-actions

Copy link
Copy Markdown

📝 README.md Preview

The following changes to README.md will be applied when this PR is merged:

Click to expand diff
diff --git a/README.md b/README.md
index 6d94a16..572a68f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # s3proxy
 
-![Version: 0.2.0](https://img.shields.io/badge/Version-0.2.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.3.0](https://img.shields.io/badge/AppVersion-3.3.0-informational?style=flat-square)
+![Version: 0.3.0](https://img.shields.io/badge/Version-0.3.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.3.0](https://img.shields.io/badge/AppVersion-3.3.0-informational?style=flat-square)
 
 A Helm chart for deploying S3Proxy - Access other storage backends via the S3 API
 
@@ -583,6 +583,48 @@ The following section lists the configurable parameters of the s3proxy chart and
 			<td><code>bool</code></td>
 			<td><code>false</code></td>
 		</tr>
+		<tr>
+			<td><code>config.tls.enabled</code></td>
+			<td>Enable native in-pod HTTPS (S3Proxy <code>secure-endpoint</code>). When enabled, S3Proxy serves HTTPS only on <code>service.targetPort</code> (the plaintext endpoint is not bound), so TLS is terminated in the pod rather than at the ingress. Requires a PKCS12 (or JKS) keystore and its password. <code>tcpSocket</code> health probes are unaffected (they do not perform a TLS handshake).</td>
+			<td><code>bool</code></td>
+			<td><code>false</code></td>
+		</tr>
+		<tr>
+			<td><code>config.tls.keystore.existingSecret</code></td>
+			<td>Name of an existing Secret holding the keystore file (binary PKCS12/JKS). Takes precedence over <code>value</code>. Use this for a customer-provided keystore or a cert-manager-issued one (<code>Certificate.spec.keystores.pkcs12</code>).</td>
+			<td><code>string</code></td>
+			<td><code>""</code></td>
+		</tr>
+		<tr>
+			<td><code>config.tls.keystore.secretKey</code></td>
+			<td>Key within <code>keystore.existingSecret</code> (or, when <code>value</code> is used, within the chart's own Secret) that holds the keystore file</td>
+			<td><code>string</code></td>
+			<td><code>"keystore.p12"</code></td>
+		</tr>
+		<tr>
+			<td><code>config.tls.keystore.value</code></td>
+			<td>Inline base64-encoded keystore, stored in the chart's own Secret and mounted as a file. Used only when <code>keystore.existingSecret</code> is empty. Convenient for testing; prefer <code>existingSecret</code> in production.</td>
+			<td><code>string</code></td>
+			<td><code>""</code></td>
+		</tr>
+		<tr>
+			<td><code>config.tls.keystorePassword.existingSecret</code></td>
+			<td>Name of an existing Secret holding the keystore password. Takes precedence over <code>value</code>.</td>
+			<td><code>string</code></td>
+			<td><code>""</code></td>
+		</tr>
+		<tr>
+			<td><code>config.tls.keystorePassword.secretKey</code></td>
+			<td>Key within <code>keystorePassword.existingSecret</code> that holds the keystore password</td>
+			<td><code>string</code></td>
+			<td><code>"keystore-password"</code></td>
+		</tr>
+		<tr>
+			<td><code>config.tls.keystorePassword.value</code></td>
+			<td>Inline keystore password, stored in the chart's own Secret and merged into the backend properties by the secret-merge initContainer (kept out of the ConfigMap). Used only when <code>keystorePassword.existingSecret</code> is empty.</td>
+			<td><code>string</code></td>
+			<td><code>""</code></td>
+		</tr>
 		<tr>
 			<td><code>config.virtualHost</code></td>
 			<td>Virtual Host configuration</td>
@@ -958,6 +1000,76 @@ aws --endpoint-url http://localhost:8080 s3 cp test.txt s3://test-bucket/
 aws --endpoint-url http://localhost:8080 s3 ls s3://test-bucket/
 ```
 
+## TLS / HTTPS (native, in-pod)
+
+By default S3Proxy binds plain HTTP and TLS is expected to be terminated at the
+ingress. Set `config.tls.enabled=true` to have **S3Proxy itself terminate HTTPS in
+the pod** (S3Proxy's `secure-endpoint`). When enabled:
+
+- S3Proxy serves **HTTPS only** on `service.targetPort` (the plaintext endpoint is
+  not bound). The container/Service port is named `https`.
+- The health probes are `tcpSocket`, which only check the TCP accept (no TLS
+  handshake), so they keep working unchanged against the TLS port.
+- A **PKCS12** keystore is expected (Jetty's default keystore type; JKS also works
+  if supplied). S3Proxy exposes only the keystore path and password, so the chart
+  does not set a keystore type.
+- The keystore **password is never written to the ConfigMap**; it is merged into
+  the backend properties from the Secret by the config-merge initContainer.
+
+### Option A: existing Secret (recommended; also the cert-manager path)
+
+Reference a Secret that already holds the keystore file and the password. This is
+also how a cert-manager `Certificate` with `spec.keystores.pkcs12` delivers a
+keystore (point `keystore.existingSecret` at that Secret, and
+`keystorePassword.existingSecret` at the password Secret it references):
+
+```yaml
+config:
+  tls:
+    enabled: true
+    keystore:
+      existingSecret: my-tls        # holds the PKCS12 archive
+      secretKey: keystore.p12
+    keystorePassword:
+      existingSecret: my-tls        # holds the password (may be the same Secret)
+      secretKey: keystore-password
+```
+
+### Option B: inline keystore + password
+
+Provide the base64-encoded keystore and the password inline; both are stored in the
+chart's own Secret. Convenient for testing; prefer Option A in production.
+
+```yaml
+config:
+  tls:
+    enabled: true
+    keystore:
+      value: "<base64-encoded PKCS12 keystore>"
+    keystorePassword:
+      value: "changeit"
+```
+
+Create a PKCS12 keystore, for example:
+
+```bash
+# From an existing cert + key:
+openssl pkcs12 -export -inkey tls.key -in tls.crt \
+  -out keystore.p12 -passout pass:changeit
+# Inline value:
+base64 -w0 keystore.p12
+# Or as an existing Secret (Option A):
+kubectl create secret generic my-tls \
+  --from-file=keystore.p12=keystore.p12 \
+  --from-literal=keystore-password=changeit
+```
+
+Testing over HTTPS (the CI keystore above is self-signed, so skip verification):
+
+```bash
+aws --endpoint-url https://localhost:9000 --no-verify-ssl s3 ls
+```
+
 ## CORS Configuration
 
 To enable CORS support:

Note: This is an automated preview generated by helm-docs. The changes will be automatically applied upon merge.

@github-actions

Copy link
Copy Markdown

📊 Helm Render Diff Summary

Chart charts/s3proxy rendered with Kubernetes 1.29.0. Informational only — this check never fails the PR.

Values File Chart Path Changes Status
test-values/autoscaling.yaml charts/s3proxy +19 -8 🔄 Changes Detected (summary)
test-values/azureblob.yaml charts/s3proxy +18 -7 🔄 Changes Detected (summary)
test-values/b2.yaml charts/s3proxy +18 -7 🔄 Changes Detected (summary)
test-values/filesystem.yaml charts/s3proxy +19 -8 🔄 Changes Detected (summary)
test-values/gcs.yaml charts/s3proxy +18 -7 🔄 Changes Detected (summary)
test-values/ingress.yaml charts/s3proxy +21 -10 🔄 Changes Detected (summary)
test-values/multi-backend.yaml charts/s3proxy +18 -7 🔄 Changes Detected (summary)
test-values/openstack-swift.yaml charts/s3proxy +18 -7 🔄 Changes Detected (summary)
test-values/rackspace.yaml charts/s3proxy +18 -7 🔄 Changes Detected (summary)
test-values/s3.yaml charts/s3proxy +18 -7 🔄 Changes Detected (summary)
test-values/tls-existing-secret.yaml charts/s3proxy +292 -0 🔄 Changes Detected (summary)
test-values/tls.yaml charts/s3proxy +290 -0 🔄 Changes Detected (summary)
test-values/transient.yaml charts/s3proxy +18 -7 🔄 Changes Detected (summary)

ℹ️ Per-scenario diffs are omitted from this comment because the full render diff exceeds GitHub's comment size limit. See the complete diffs in the workflow job summary.

@CRThaze
CRThaze merged commit 013aee4 into main Jul 22, 2026
22 checks passed
@CRThaze CRThaze mentioned this pull request Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant