From 0fe084bcb29b48d032e53134fe006cc0449a441b Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Fri, 10 Apr 2026 19:49:07 +0200 Subject: [PATCH] fix: restrict key.pem file permissions to 0600 (owner-only) All private key writes now use writeAtomicRestricted which sets POSIX owner-read/write permissions after writing. Gracefully skips on non-POSIX filesystems (Windows dev). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../DockerCertificateManager.java | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/siegeln/cameleer/saas/provisioning/DockerCertificateManager.java b/src/main/java/net/siegeln/cameleer/saas/provisioning/DockerCertificateManager.java index dab06bf..3a76dfd 100644 --- a/src/main/java/net/siegeln/cameleer/saas/provisioning/DockerCertificateManager.java +++ b/src/main/java/net/siegeln/cameleer/saas/provisioning/DockerCertificateManager.java @@ -113,7 +113,7 @@ public class DockerCertificateManager implements CertificateManager { Files.createDirectories(stagedDir); writeAtomic(stagedDir.resolve("cert.pem"), certPem); - writeAtomic(stagedDir.resolve("key.pem"), decryptedKeyPem); + writeAtomicRestricted(stagedDir.resolve("key.pem"), decryptedKeyPem); if (caBundlePem != null && caBundlePem.length > 0) { writeAtomic(stagedDir.resolve("ca.pem"), caBundlePem); } else { @@ -153,7 +153,7 @@ public class DockerCertificateManager implements CertificateManager { // Move staged -> active (atomic swap via .wip) writeAtomic(certsDir.resolve("cert.pem"), Files.readAllBytes(stagedDir.resolve("cert.pem"))); - writeAtomic(certsDir.resolve("key.pem"), Files.readAllBytes(stagedDir.resolve("key.pem"))); + writeAtomicRestricted(certsDir.resolve("key.pem"), Files.readAllBytes(stagedDir.resolve("key.pem"))); if (Files.exists(stagedDir.resolve("ca.pem"))) { writeAtomic(certsDir.resolve("ca.pem"), Files.readAllBytes(stagedDir.resolve("ca.pem"))); } @@ -187,7 +187,7 @@ public class DockerCertificateManager implements CertificateManager { // Prev -> active writeAtomic(certsDir.resolve("cert.pem"), Files.readAllBytes(prevDir.resolve("cert.pem"))); - writeAtomic(certsDir.resolve("key.pem"), Files.readAllBytes(prevDir.resolve("key.pem"))); + writeAtomicRestricted(certsDir.resolve("key.pem"), Files.readAllBytes(prevDir.resolve("key.pem"))); if (Files.exists(prevDir.resolve("ca.pem"))) { writeAtomic(certsDir.resolve("ca.pem"), Files.readAllBytes(prevDir.resolve("ca.pem"))); } else { @@ -355,6 +355,23 @@ public class DockerCertificateManager implements CertificateManager { Files.move(wip, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); } + /** Write with owner-only permissions (0600) for private key files. */ + private void writeAtomicRestricted(Path target, byte[] data) throws IOException { + writeAtomic(target, data); + try { + var posix = Files.getFileAttributeView(target, java.nio.file.attribute.PosixFileAttributeView.class); + if (posix != null) { + posix.setPermissions(java.util.Set.of( + java.nio.file.attribute.PosixFilePermission.OWNER_READ, + java.nio.file.attribute.PosixFilePermission.OWNER_WRITE + )); + } + } catch (UnsupportedOperationException e) { + // Non-POSIX filesystem (e.g., Windows) — skip + log.debug("Cannot set POSIX permissions on {}: {}", target, e.getMessage()); + } + } + private void moveFile(Path source, Path target) throws IOException { Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); }