-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathrustfs.nix
More file actions
279 lines (248 loc) · 9.39 KB
/
rustfs.nix
File metadata and controls
279 lines (248 loc) · 9.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# Copyright 2024 RustFS Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{ config
, lib
, pkgs
, ...
}:
let
cfg = config.services.rustfs;
# Helper to handle volumes as list or string
volumesStr =
if builtins.isList cfg.volumes
then lib.concatStringsSep "," cfg.volumes
else cfg.volumes;
volumesList =
if builtins.isList cfg.volumes
then cfg.volumes
else [ cfg.volumes ];
in
{
imports = [
(lib.mkRenamedOptionModule
[ "services" "rustfs" "accessKey" ]
[ "services" "rustfs" "accessKeyFile" ]
)
(lib.mkRenamedOptionModule
[ "services" "rustfs" "secretKey" ]
[ "services" "rustfs" "secretKeyFile" ]
)
];
options.services.rustfs = {
enable = lib.mkEnableOption "RustFS object storage server";
package = lib.mkOption {
type = lib.types.package;
default = pkgs.rustfs;
description = "RustFS package providing the rustfs binary";
};
user = lib.mkOption {
type = lib.types.str;
default = "rustfs";
description = "User account under which RustFS runs.";
};
group = lib.mkOption {
type = lib.types.str;
default = "rustfs";
description = "Group under which RustFS runs.";
};
extraEnvironmentVariables = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = "Additional environment variables for the RustFS service.";
};
accessKeyFile = lib.mkOption {
type = lib.types.path;
example = "/run/secrets/rustfs-access-key";
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 must be readable by root/systemd (not by the rustfs service user directly); systemd reads it
via LoadCredential and exposes a copy in the service's credential directory ($CREDENTIALS_DIRECTORY).
For security best practices, use secret management tools like sops-nix, agenix, or NixOps keys.
'';
};
secretKeyFile = lib.mkOption {
type = lib.types.path;
example = "/run/secrets/rustfs-secret-key";
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 must be readable by root/systemd (not by the rustfs service user directly); systemd reads it
via LoadCredential and exposes a copy in the service's credential directory ($CREDENTIALS_DIRECTORY).
For security best practices, use secret management tools like sops-nix, agenix, or NixOps keys.
'';
};
volumes = lib.mkOption {
type = lib.types.either lib.types.str (lib.types.listOf lib.types.str);
default = [ "/var/lib/rustfs" ];
description = "List of paths or comma-separated string where RustFS stores data.";
};
address = lib.mkOption {
type = lib.types.str;
default = ":9000";
description = "Network address for the API server (e.g., :9000).";
};
consoleEnable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether to enable the RustFS management console.";
};
consoleAddress = lib.mkOption {
type = lib.types.str;
default = ":9001";
description = "Network address for the management console (e.g., :9001).";
};
logLevel = lib.mkOption {
type = lib.types.str;
default = "info";
description = "Log level (error, warn, info, debug, trace).";
};
logDirectory = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = ''
Directory where RustFS service logs are written to files.
If null (default), logs are written to systemd journal only.
Set to a path (e.g., "/var/log/rustfs") to enable file logging.
'';
};
tlsDirectory = lib.mkOption {
type = lib.types.path;
default = "/etc/rustfs/tls";
description = "Directory containing TLS certificates.";
};
};
config = lib.mkIf cfg.enable {
users.groups = lib.mkIf (cfg.group == "rustfs") {
rustfs = { };
};
users.users = lib.mkIf (cfg.user == "rustfs") {
rustfs = {
group = cfg.group;
isSystemUser = true;
description = "RustFS service user";
};
};
systemd.tmpfiles.rules = [
"d ${cfg.tlsDirectory} 0750 ${cfg.user} ${cfg.group} -"
] ++ (map (vol: "d ${vol} 0750 ${cfg.user} ${cfg.group} -") volumesList)
++ (lib.optional (cfg.logDirectory != null) "d ${cfg.logDirectory} 0750 ${cfg.user} ${cfg.group} -");
systemd.services.rustfs = {
description = "RustFS Object Storage Server";
documentation = [ "https://rustfs.com/docs/" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
# Environment variables
environment = {
RUSTFS_VOLUMES = volumesStr;
RUSTFS_ADDRESS = cfg.address;
RUSTFS_CONSOLE_ENABLE = lib.boolToString cfg.consoleEnable;
RUSTFS_CONSOLE_ADDRESS = cfg.consoleAddress;
RUST_LOG = cfg.logLevel;
# Use %d to reference the credentials directory set by LoadCredential
RUSTFS_ACCESS_KEY_FILE = "%d/access-key";
RUSTFS_SECRET_KEY_FILE = "%d/secret-key";
} // lib.optionalAttrs (cfg.logDirectory != null) {
RUSTFS_OBS_LOG_DIRECTORY = cfg.logDirectory;
} // cfg.extraEnvironmentVariables;
serviceConfig = {
User = cfg.user;
Group = cfg.group;
Type = "simple";
# Main service executable
ExecStart = "${cfg.package}/bin/rustfs";
# Security: Use LoadCredential to securely pass secrets to the service.
# This avoids permission issues with the service user reading secret files directly,
# and keeps secrets out of environment variables (which can leak).
# The credentials are available in the directory referenced by %d placeholder.
LoadCredential = [
"access-key:${cfg.accessKeyFile}"
"secret-key:${cfg.secretKeyFile}"
];
# Resource Limits and Performance
LimitNOFILE = 1048576;
LimitNPROC = 32768;
# Restart settings for better reliability
Restart = "always";
RestartSec = "10s";
TimeoutStartSec = "60s";
TimeoutStopSec = "30s";
# Security Hardening
# Minimize capabilities - RustFS doesn't need any special capabilities
CapabilityBoundingSet = "";
# Restrict device access
DevicePolicy = "closed";
# Prevent privilege escalation
NoNewPrivileges = true;
# Use private /dev
PrivateDevices = true;
# Use private /tmp
PrivateTmp = true;
# Use private user namespace for better isolation
PrivateUsers = true;
# Protect system clock
ProtectClock = true;
# Protect cgroup filesystem
ProtectControlGroups = true;
# Don't allow access to home directories
ProtectHome = true;
# Protect hostname from changes
ProtectHostname = true;
# Protect kernel logs
ProtectKernelLogs = true;
# Protect kernel modules
ProtectKernelModules = true;
# Protect kernel tunables
ProtectKernelTunables = true;
# Make /proc minimal
ProtectProc = "invisible";
# Make system directories read-only except for paths we explicitly allow
ProtectSystem = "strict";
# Restrict /proc access
ProcSubset = "pid";
# Restrict network address families to what's needed
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
# Restrict namespaces
RestrictNamespaces = true;
# Prevent realtime scheduling
RestrictRealtime = true;
# Prevent setuid/setgid
RestrictSUIDSGID = true;
# Restrict to native system calls only
SystemCallArchitectures = "native";
# Allow only safe system calls
SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
# Prevent memory mapping executable
MemoryDenyWriteExecute = true;
# Prevent personality changes
LockPersonality = true;
# Set restrictive umask
UMask = "0077";
# Grant write access to necessary directories
ReadWritePaths = [ cfg.tlsDirectory ] ++ volumesList
++ lib.optional (cfg.logDirectory != null) cfg.logDirectory;
# Logging: Default to systemd journal, optionally write to files
StandardOutput =
if cfg.logDirectory != null
then "append:${cfg.logDirectory}/rustfs.log"
else "journal";
StandardError =
if cfg.logDirectory != null
then "append:${cfg.logDirectory}/rustfs-err.log"
else "journal";
};
};
};
}