Skip to content

Replace step-cli with certs.generateCertificate for TLS certificates#187

Open
attilaolah wants to merge 3 commits intoattilaolah:mainfrom
dorn-haus:fix/tls-certificate
Open

Replace step-cli with certs.generateCertificate for TLS certificates#187
attilaolah wants to merge 3 commits intoattilaolah:mainfrom
dorn-haus:fix/tls-certificate

Conversation

@attilaolah
Copy link
Copy Markdown
Owner

@attilaolah attilaolah commented Mar 30, 2026

Summary by CodeRabbit

  • Chores
    • Simplified certificate handling and installation for easier maintenance.
  • Security
    • Private TLS key is no longer stored in build outputs; it is generated at activation with restricted permissions.
    • CA certificate is installed into the system trust store as a managed text artifact.
  • New
    • Activation step now generates the server TLS certificate on the device at activation time.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

📝 Walkthrough

Walkthrough

Replaced build-time TLS/CA generation (previous pkgs.runCommand + step-cli) with a static caCrt produced via pkgs.writeText. Switched trust input to security.pki.certificateFiles referencing the CA file. Added system.activationScripts.generateTlsCerts which creates /etc/tls/tls.key (0600) and generates /etc/tls/tls.crt at activation using openssl req -x509 with an ED25519 key and configured SANs, ensuring the private key is created outside the Nix store.

Changes

Cohort / File(s) Summary
TLS provisioning & activation
hosts/home/configuration.nix
Removed build-time pkgs.runCommand + step-cli certificate generation and embedding of CA/server certs/keys into the derivation. Added pkgs.writeText caCrt (PEM) and switched security.pki.certificatessecurity.pki.certificateFiles to trust the CA file. Stopped installing tls.crt/tls.key from the build output; instead added system.activationScripts.generateTlsCerts which creates /etc/tls/tls.key (ED25519, 0600) and generates /etc/tls/tls.crt via openssl req -x509 with SANs at activation time, keeping the private key out of the Nix store.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant NixBuild as Nix build
participant WriteText as pkgs.writeText
participant Store as Nix store
participant Activation as system.activationScripts
participant OpenSSL as openssl
participant ETC as /etc/tls

NixBuild->>WriteText: create `caCrt` (PEM) text output
WriteText-->>Store: emit `ca.crt`
Store-->>NixBuild: provide path to `ca.crt`
NixBuild->>Activation: include `generateTlsCerts` activationScript
Activation->>OpenSSL: run `openssl req -x509` (ED25519 key, SANs)
OpenSSL-->>Activation: produce `tls.crt` and `tls.key`
Activation->>ETC: write `/etc/tls/tls.key` (0600) and `/etc/tls/tls.crt`
Store-->>ETC: install `/etc/tls/ca.crt` from `caCrt`

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰
I hop through configs, nibble at the store,
Moved secret keys outside the build door,
CA sits in text, safe and bright,
Keys born at activation, hidden from sight,
A tidy hop — activation does the chore. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title references 'certs.generateCertificate' which does not appear in the actual changes; instead, an 'openssl req' command is used in a new activation script. Update the title to accurately reflect the implementation, such as 'Move TLS certificate generation from Nix to runtime activation script' or 'Replace step-cli with openssl-based certificate generation'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hosts/home/configuration.nix`:
- Around line 21-23: The tlsKey is being written into the global Nix store with
pkgs.writeText (tlsKey), making the private key world-readable; change the
approach so the private key is emitted outside the Nix store at activation time
with proper 0600 permissions—remove pkgs.writeText for tlsKey and instead create
the key file from an activation script (system.activationScripts) or manage it
via systemd.tmpfiles.rules so the file is placed under /etc (or another secure
path) with owner root and mode 0600; leave caCert and tlsCert as pkgs.writeText
if public, but ensure tlsKey is generated/copied during activation with secure
ownership and permissions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 609e0a87-9f29-4e13-912e-def07e46994e

📥 Commits

Reviewing files that changed from the base of the PR and between addf555 and 70dbcc4.

📒 Files selected for processing (1)
  • hosts/home/configuration.nix

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hosts/home/configuration.nix`:
- Around line 13-14: The certificate generation is using a store-side file
(subjectKey = pkgs.lib.fileContents ./tls.key) while activation replaces
/etc/tls/tls.key, causing crt/key mismatch; update the cert generation so it
reads the same activation-time key (the one written to /etc/tls/tls.key) instead
of ./tls.key — either change certs = pkgs.certificates.generateCertificate to
use pkgs.lib.fileContents on /etc/tls/tls.key or move the certificate creation
into the activation phase (so the code that writes /etc/tls/tls.key also runs
pkgs.certificates.generateCertificate against that same file), and make the
corresponding change where hosts/home/services/nginx.nix serves /etc/tls/tls.crt
to ensure the key and cert are produced from the same source.
- Line 23: The current code defines caCrt via pkgs.writeText ("ca.crt")
producing a file path but then uses the incorrect Nix option; replace usage of
security.pki.certificates (which expects PEM strings) with
security.pki.certificateFiles and pass [ caCrt ] instead. Locate the caCrt
binding and any reference where security.pki.certificates is used and change
that reference to security.pki.certificateFiles = [ caCrt ]; leaving the
pkgs.writeText call intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 720f0469-7597-43fa-b17d-fc0805b4ca2c

📥 Commits

Reviewing files that changed from the base of the PR and between 70dbcc4 and fa609de.

📒 Files selected for processing (1)
  • hosts/home/configuration.nix

1. Move cert/key generation to activation time together
   - Certificate and private key are now generated together in activation script
   - This ensures they are always paired and the key never enters Nix store

2. Fix security.pki option usage
   - Changed pki.certificates (for PEM strings) to pki.certificateFiles (for file paths)

3. Generate self-signed cert using openssl in activation script
   - Uses openssl req -x509 to create both cert and key in one command
   - Includes proper subjectAltNames for localhost, home, and IP addresses
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hosts/home/configuration.nix`:
- Around line 56-57: The nginx cert being served is self-signed and doesn't
chain to the advertised CA (caCrt) because the activation script uses openssl
req -x509 without signing with the CA; fix by either (A) generating a CSR for
tls.key and signing it with caCrt/caKey (use openssl req to create a CSR and
openssl ca or openssl x509 -req with -CA and -CAkey to produce /etc/tls/tls.crt
so it chains to caCrt), or (B) if you don't intend to provide a CA, remove caCrt
from pki.certificateFiles and from environment.etc so the system and nginx
configuration stop advertising that CA; update the activation script (the
openssl req invocation) and the pki.certificateFiles/caCrt references
accordingly.
- Around line 108-115: The activation snippet invokes the openssl binary by bare
name (the line starting with "openssl req -x509 -newkey ed25519 ..."), but PATH
in activation is limited to core utilities so this will fail; update the command
to call the absolute store path for OpenSSL (use ${pkgs.openssl}/bin/openssl or
builtins.toString pkgs.openssl concatenated with /bin/openssl) wherever the
openssl invocation appears in the activation script so the certificate
generation runs reliably during activation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 70b694bc-e5f8-43fe-9477-718ff302873e

📥 Commits

Reviewing files that changed from the base of the PR and between fa609de and 9b07259.

📒 Files selected for processing (1)
  • hosts/home/configuration.nix

Comment on lines +56 to +57
# Use certificateFiles for file paths, certificates for PEM strings
pki.certificateFiles = [ caCrt ];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let me check if the files exist and get their sizes
stat hosts/home/configuration.nix 2>/dev/null && wc -l hosts/home/configuration.nix
stat hosts/home/services/nginx.nix 2>/dev/null && wc -l hosts/home/services/nginx.nix

Repository: attilaolah/os

Length of output: 41


🏁 Script executed:

# Read the configuration.nix file to examine the relevant lines
cat -n hosts/home/configuration.nix

Repository: attilaolah/os

Length of output: 4144


🏁 Script executed:

# Read the nginx.nix file to see how certificates are configured
cat -n hosts/home/services/nginx.nix

Repository: attilaolah/os

Length of output: 633


The certificate nginx serves is self-signed and unrelated to the trusted CA.

Lines 56–57 publish caCrt as a trusted root CA, and line 87 places it at /etc/tls/ca.crt. However, the activation script at lines 108–115 uses openssl req -x509 without -CA or -CAkey, which generates a standalone self-signed certificate with no relationship to caCrt. When nginx (line 8 in services/nginx.nix) presents /etc/tls/tls.crt to clients, those clients will reject it because the certificate is not signed by the advertised CA. Either sign /etc/tls/tls.key with caCrt by generating a CSR and using -CA/-CAkey, or remove caCrt from security.pki.certificateFiles and environment.etc if it is not intended to validate the nginx certificate. (docs.openssl.org)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hosts/home/configuration.nix` around lines 56 - 57, The nginx cert being
served is self-signed and doesn't chain to the advertised CA (caCrt) because the
activation script uses openssl req -x509 without signing with the CA; fix by
either (A) generating a CSR for tls.key and signing it with caCrt/caKey (use
openssl req to create a CSR and openssl ca or openssl x509 -req with -CA and
-CAkey to produce /etc/tls/tls.crt so it chains to caCrt), or (B) if you don't
intend to provide a CA, remove caCrt from pki.certificateFiles and from
environment.etc so the system and nginx configuration stop advertising that CA;
update the activation script (the openssl req invocation) and the
pki.certificateFiles/caCrt references accordingly.

Comment on lines +108 to +115
openssl req -x509 -newkey ed25519 \
-keyout /etc/tls/tls.key \
-out /etc/tls/tls.crt \
-days 3650 \
-nodes \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,DNS:home,IP:127.0.0.1,IP:::1,IP:127.0.4.43" \
2>/dev/null
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the file structure and locate the file
fd -t f configuration.nix | head -20

Repository: attilaolah/os

Length of output: 85


🏁 Script executed:

# Check if the file exists and read the relevant section
if [ -f "hosts/home/configuration.nix" ]; then
  echo "=== File found, reading lines 100-120 ==="
  sed -n '100,120p' hosts/home/configuration.nix
else
  echo "File not found at hosts/home/configuration.nix"
  # Try to find it
  find . -name "configuration.nix" -type f
fi

Repository: attilaolah/os

Length of output: 865


🏁 Script executed:

# Let's understand the broader context - search for where this openssl command is used
rg -B 5 -A 5 "openssl req -x509" hosts/home/

Repository: attilaolah/os

Length of output: 763


🏁 Script executed:

# Check the NixOS activation-script handling and PATH initialization
# Let's see if there's any package declaration or imports that might affect this
rg -B 10 "openssl" hosts/home/configuration.nix | head -50

Repository: attilaolah/os

Length of output: 795


🏁 Script executed:

# Let's check if there's any context in the file about how packages are made available
# Look for buildInputs, nativeBuildInputs, or package declarations
rg -B 5 "buildInputs|nativeBuildInputs|pkgs\." hosts/home/configuration.nix | head -80

Repository: attilaolah/os

Length of output: 454


🏁 Script executed:

# Let's check the NixOS module structure to see if openssl is declared as a dependency
# Look at the entire file structure
wc -l hosts/home/configuration.nix

Repository: attilaolah/os

Length of output: 89


🏁 Script executed:

# Read more of the file to understand the overall structure and any package declarations
head -50 hosts/home/configuration.nix

Repository: attilaolah/os

Length of output: 1367


🏁 Script executed:

# Search for how install command is available - it's also a bare command in the activation script
# This will help us understand if the reviewer's claim is accurate
rg "install" hosts/home/configuration.nix

Repository: attilaolah/os

Length of output: 106


🌐 Web query:

NixOS activation-script PATH environment initialization openssl

💡 Result:

In NixOS, the system activation script (located at /nix/var/nix/profiles/system/activate or similar) initializes the PATH environment variable at the beginning of its execution. It starts by setting PATH=/empty to ensure a clean environment, then appends /bin and /sbin directories from a minimal set of essential packages defined in nixos/modules/system/activation/activation-script.nix. The path list includes (using pkgs.getBin or equivalent): - coreutils - gnugrep - findutils - glibcLocales or stdenv.cc.libc (for getent and nscd) - shadow - nettools (for hostname) - util-linux (for mount, mountpoint) This provides basic utilities needed for activation tasks like creating directories, symlinks, mounting filesystems, and managing users/groups. OpenSSL is NOT included in this minimal PATH by default, as it is not required for core activation operations. OpenSSL binaries would only be available if explicitly added via custom activation scripts or if running in a shell that sources additional environment setups (e.g., source ${config.system.build.setEnvironment}). For custom system.activationScripts, the PATH is inherited from this minimal setup. To use additional tools like openssl in a custom script, explicitly add them: PATH=$PATH:${lib.makeBinPath [ pkgs.openssl ]} or source ${config.system.build.setEnvironment} to include the full system environment (including environment.systemPackages). Sources confirm this behavior across NixOS versions, with minor variations in the exact package list (e.g., glibcLocales vs getent). No evidence of openssl in the default activation PATH.

Citations:


🏁 Script executed:

# Let's search for other uses of openssl in the repository to see patterns
rg "openssl" --type nix | grep -v "Binary" | head -20

Repository: attilaolah/os

Length of output: 279


🏁 Script executed:

# Let's look for how activation scripts typically access commands in NixOS examples
# Check if there's any documentation or patterns in the flake or other config files
fd -t f "\.nix$" | xargs rg "system\.activationScripts" -A 10 | head -100

Repository: attilaolah/os

Length of output: 763


🏁 Script executed:

# Check if there's any buildInputs or packages declared for the activation script
rg -B 20 "system.activationScripts.generateTlsCerts" hosts/home/configuration.nix | head -50

Repository: attilaolah/os

Length of output: 611


🏁 Script executed:

# Check if there's anything in the imports that might provide openssl
# Let's look at the services and programs imports
ls -la hosts/home/services/ hosts/home/programs/ 2>/dev/null | head -20

Repository: attilaolah/os

Length of output: 980


🏁 Script executed:

# Look for any environment.systemPackages or similar that might affect activation script PATH
rg "environment\.(systemPackages|variables)" hosts/home/configuration.nix

Repository: attilaolah/os

Length of output: 39


Call OpenSSL by store path in the activation snippet.

The activation script's PATH is initialized with only core utilities (coreutils, gnugrep, findutils, shadow, nettools, util-linux); openssl is not included. Using it by bare name will fail at activation time unless some unrelated code mutates PATH first. Use ${pkgs.openssl}/bin/openssl instead.

Suggested change
-    openssl req -x509 -newkey ed25519 \
+    ${pkgs.openssl}/bin/openssl req -x509 -newkey ed25519 \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hosts/home/configuration.nix` around lines 108 - 115, The activation snippet
invokes the openssl binary by bare name (the line starting with "openssl req
-x509 -newkey ed25519 ..."), but PATH in activation is limited to core utilities
so this will fail; update the command to call the absolute store path for
OpenSSL (use ${pkgs.openssl}/bin/openssl or builtins.toString pkgs.openssl
concatenated with /bin/openssl) wherever the openssl invocation appears in the
activation script so the certificate generation runs reliably during activation.

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