refactor: Issue #9 improvements and documentation reorganization#15
refactor: Issue #9 improvements and documentation reorganization#15
Conversation
- Remove manual binary stripping and binutils - Clarify sourceProvenance for pre-compiled binaries - Migrate to environment attribute set (nixpkgs best practice) - Replace shell script with %d placeholder - Default to systemd journal logging - Move detailed docs to docs/, remove duplicates - Update all documentation references
There was a problem hiding this comment.
Pull request overview
This PR addresses Issue #9 by tightening the NixOS module’s secret handling and systemd hardening, and reorganizes project documentation into a dedicated docs/ directory.
Changes:
- Refactors the NixOS module service definition (systemd environment attrset, LoadCredential usage, optional file logging with journal default, extended hardening).
- Simplifies the flake packaging by removing manual binary stripping and clarifying binary provenance metadata.
- Reorganizes and expands documentation (security, migration, improvements, changelog) and updates examples.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| nixos/rustfs.nix | Updates option docs, makes logDirectory optional, refactors systemd env/logging/tmpfiles rules, expands hardening. |
| flake.nix | Removes binutils/manual strip and clarifies sourceProvenance documentation for prebuilt binaries. |
| examples/nixos-configuration.nix | Expands “secure production” example (file-based secrets, firewall, logging guidance). |
| examples/flake.nix | Updates example to use file-based secrets paths and adds demo secret provisioning. |
| docs/SECURITY.md | Adds comprehensive security guidance and configuration examples. |
| docs/MIGRATION.md | Adds migration guide away from plain-text secrets to file-based secrets. |
| docs/IMPROVEMENTS.md | Adds detailed explanation of the security/performance refactors and rationale. |
| docs/README.md | Adds docs index/entrypoint. |
| docs/REORGANIZATION.md | Documents the doc structure changes and moved/removed files. |
| README.md | Updates top-level README with security notice and links to new docs. |
| CHANGELOG.md | Adds changelog including security improvements and behavior changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| description = '' | ||
| Path to a file containing the access key for client authentication. | ||
| Use a runtime path (e.g. /run/secrets/…) to prevent the secret from being copied into the Nix store. | ||
| The file should be readable by the rustfs service user and contain only the access key without any trailing whitespace. | ||
| For security best practices, use secret management tools like sops-nix, agenix, or NixOps keys. |
There was a problem hiding this comment.
The option description says the secret file must be readable by the rustfs service user, but the module actually loads it via systemd LoadCredential (PID1 reads the source file and exposes a separate credential file to the service). This wording is misleading and may cause users to unnecessarily grant the service direct read access to the original secret file; consider updating it to reflect the real requirement (readable by systemd/root) and that the service reads from the credential directory instead.
examples/flake.nix
Outdated
| # Create secret files for demonstration purposes | ||
| # In production, use sops-nix, agenix, or other secret management | ||
| environment.etc."rustfs-secrets/access-key" = { | ||
| text = "admin-access-key"; | ||
| mode = "0400"; | ||
| }; | ||
| environment.etc."rustfs-secrets/secret-key" = { | ||
| text = "secure-secret-key"; | ||
| mode = "0400"; | ||
| }; |
There was a problem hiding this comment.
This example writes secret values via environment.etc.<name>.text, which places the secret contents into the Nix store (defeating the purpose of moving to file-based secrets to keep them out of /nix/store). Even for a demo, this pattern is insecure/misleading; please switch the example to use an out-of-store path (e.g. /run/secrets/...) and document how to create/populate those secrets (or show a minimal sops-nix/agenix example).
docs/SECURITY.md
Outdated
| systemd.tmpfiles.rules = [ | ||
| "d ${cfg.logDirectory} 0750 ${cfg.user} ${cfg.group} -" | ||
| "d ${cfg.tlsDirectory} 0750 ${cfg.user} ${cfg.group} -" | ||
| "d ${volume} 0750 ${cfg.user} ${cfg.group} -" # For each volume | ||
| ]; |
There was a problem hiding this comment.
This tmpfiles example uses ${cfg.logDirectory} unconditionally, but the module now defaults logDirectory = null and only adds the tmpfiles rule when it is non-null. Consider updating the snippet to match the current module behavior (make logDirectory optional in the example).
| systemd.tmpfiles.rules = [ | |
| "d ${cfg.logDirectory} 0750 ${cfg.user} ${cfg.group} -" | |
| "d ${cfg.tlsDirectory} 0750 ${cfg.user} ${cfg.group} -" | |
| "d ${volume} 0750 ${cfg.user} ${cfg.group} -" # For each volume | |
| ]; | |
| systemd.tmpfiles.rules = | |
| (lib.optional (cfg.logDirectory != null) | |
| "d ${cfg.logDirectory} 0750 ${cfg.user} ${cfg.group} -") | |
| ++ [ | |
| "d ${cfg.tlsDirectory} 0750 ${cfg.user} ${cfg.group} -" | |
| "d ${volume} 0750 ${cfg.user} ${cfg.group} -" # For each volume | |
| ]; |
| ### Changed | ||
|
|
||
| - **BREAKING**: Removed `services.rustfs.accessKey` option (security risk) | ||
| - **BREAKING**: Removed `services.rustfs.secretKey` option (security risk) | ||
| - **BREAKING**: `accessKeyFile` is now required instead of optional | ||
| - **BREAKING**: `secretKeyFile` is now required instead of optional | ||
| - Default `volumes` changed from `"/tmp/rustfs"` to `"/var/lib/rustfs"` (persistent storage) |
There was a problem hiding this comment.
The PR description says there are no breaking changes, but this changelog section explicitly marks multiple BREAKING changes (removal of services.rustfs.accessKey/secretKey and making accessKeyFile/secretKeyFile required). Please reconcile this: either update the PR description (and release notes) to acknowledge the breaking change, or adjust the module/docs if it is intended to remain backward compatible.
| description = '' | ||
| Path to a file containing the secret key for client authentication. | ||
| Use a runtime path (e.g. /run/secrets/…) to prevent the secret from being copied into the Nix store. | ||
| The file should be readable by the rustfs service user and contain only the secret key without any trailing whitespace. | ||
| For security best practices, use secret management tools like sops-nix, agenix, or NixOps keys. |
There was a problem hiding this comment.
Same as accessKeyFile: the description currently states the file should be readable by the rustfs service user, but the module uses LoadCredential so systemd reads the source file and the service consumes the copied credential. Please align this description with how secrets are actually consumed to avoid encouraging overly broad file permissions.
docs/SECURITY.md
Outdated
| - **Memory protections**: `MemoryDenyWriteExecute = true` | ||
| - **Network restrictions**: Only AF_INET, AF_INET6, and AF_UNIX allowed | ||
|
|
||
| See the full list in [nixos/rustfs.nix](./nixos/rustfs.nix). |
There was a problem hiding this comment.
This link is broken due to the docs being in docs/: ./nixos/rustfs.nix resolves to docs/nixos/rustfs.nix which doesn't exist. It should point to ../nixos/rustfs.nix (or another correct relative path).
| See the full list in [nixos/rustfs.nix](./nixos/rustfs.nix). | |
| See the full list in [nixos/rustfs.nix](../nixos/rustfs.nix). |
| networking.firewall = { | ||
| enable = true; | ||
| allowedTCPPorts = [ 9000 ]; # API port | ||
| allowedTCPPorts = [ 9001 ]; # Console port (consider restricting to local only) | ||
|
|
||
| # Or use interfaces for more granular control | ||
| interfaces.eth0.allowedTCPPorts = [ 9000 9001 ]; | ||
| }; |
There was a problem hiding this comment.
The firewall example sets allowedTCPPorts twice; in Nix this will overwrite the first assignment, so only one port would actually be opened. Please combine the ports into a single list (or show interface-specific rules) to make the example correct.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
docs/MIGRATION.md
Outdated
| If you encounter issues: | ||
|
|
||
| 1. Check the [SECURITY.md](./SECURITY.md) documentation | ||
| 2. Review the [example configuration](./examples/nixos-configuration.nix) |
There was a problem hiding this comment.
This link is broken from within docs/MIGRATION.md: ./examples/nixos-configuration.nix resolves under docs/, but the examples directory is at the repo root. Update it to ../examples/nixos-configuration.nix (or another correct relative path).
| 2. Review the [example configuration](./examples/nixos-configuration.nix) | |
| 2. Review the [example configuration](../examples/nixos-configuration.nix) |
docs/IMPROVEMENTS.md
Outdated
| cfg.logDirectory | ||
| cfg.tlsDirectory | ||
| ] ++ volumesList; |
There was a problem hiding this comment.
This ReadWritePaths snippet includes cfg.logDirectory unconditionally, but the module now defaults logDirectory = null and only adds it when non-null. Please update the documentation snippet to reflect the current implementation (e.g., include tlsDirectory and volumes always, and only add logDirectory optionally).
| cfg.logDirectory | |
| cfg.tlsDirectory | |
| ] ++ volumesList; | |
| cfg.tlsDirectory | |
| ] ++ lib.optional (cfg.logDirectory != null) cfg.logDirectory | |
| ++ volumesList; |
…xample (#16) * Initial plan * fix: combine duplicate allowedTCPPorts into single list in firewall example Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>
… optional logDirectory (#17) * Initial plan * fix: address remaining review comments - secrets handling, broken links, optional logDirectory Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> * fix: clarify CHANGELOG deprecated accessKey/secretKey semantics and required file path Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> * fix: remove hardcoded secrets from activation script example; align README with LoadCredential behavior Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>
|
@copilot fix ci error |
… arity (#18) * Initial plan * fix: resolve CI errors — invalid checkout@v6 and mkRenamedOptionModule 3-arg call Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> * fix: apply nixpkgs-fmt to all 3 failing files and restore checkout@v6 Co-authored-by: houseme <4829346+houseme@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>
Summary
Implements all Issue #9 feedback for Nix best practices and reorganizes documentation by removing duplicates and improving structure.
Code Improvements
Documentation Changes
docs/directory (SECURITY.md, MIGRATION.md, IMPROVEMENTS.md)Breaking Changes
None - Fully backward compatible
Testing
Closes #9