underpost 3.2.90 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ghpkg.ci.yml +7 -1
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -9
- package/CHANGELOG.md +110 -1
- package/CLI-HELP.md +139 -9
- package/README.md +5 -2
- package/bin/build.js +7 -5
- package/bin/deploy.js +1 -1
- package/deploy/lib/logging.sh +96 -0
- package/deploy/pwa-microservices-template/deploy.sh +72 -0
- package/deploy/release/deploy.sh +62 -0
- package/docker-compose.yml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +52 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/package.json +5 -5
- package/scripts/audit-selinux.sh +64 -0
- package/scripts/coverall-test.sh +24 -0
- package/scripts/gpu-diag.sh +0 -0
- package/scripts/ip-info.sh +0 -0
- package/scripts/k3s-node-setup.sh +18 -15
- package/scripts/kubeadm-node-setup.sh +12 -23
- package/scripts/link-local-underpost-cli.sh +0 -0
- package/scripts/lxd-vm-setup.sh +0 -0
- package/scripts/maas-nat-firewalld.sh +0 -0
- package/scripts/nat-iptables.sh +2 -0
- package/scripts/rhel-grpc-setup.sh +0 -0
- package/scripts/rocky-kickstart.sh +25 -9
- package/scripts/test-monitor.sh +1 -1
- package/src/cli/baremetal.js +1 -2
- package/src/cli/cloud-init.js +1 -1
- package/src/cli/cluster.js +73 -68
- package/src/cli/db.js +9 -2
- package/src/cli/deploy.js +21 -5
- package/src/cli/docker-compose.js +1 -1
- package/src/cli/env.js +1 -1
- package/src/cli/image.js +0 -1
- package/src/cli/index.js +121 -9
- package/src/cli/lxd.js +1 -1
- package/src/cli/monitor.js +1 -1
- package/src/cli/release.js +57 -22
- package/src/cli/repository.js +11 -9
- package/src/cli/run.js +36 -9
- package/src/cli/ssh.js +198 -77
- package/src/cli/system.js +26 -13
- package/src/cli/test.js +1 -1
- package/src/cli/vultr.js +583 -0
- package/src/cli/wireguard.js +2125 -0
- package/src/client-builder/client-build.js +20 -14
- package/src/db/mongo/MongooseDB.js +4 -0
- package/src/index.js +25 -1
- package/src/projects/underpost/catalog-underpost.js +4 -1
- package/src/server/backup.js +1 -1
- package/src/server/conf.js +18 -108
- package/src/server/cron.js +249 -51
- package/src/server/dns.js +100 -6
- package/src/server/environment.js +98 -0
- package/src/server/forward-proxy.js +549 -0
- package/src/server/middlewares.js +56 -1
- package/src/server/process.js +0 -1
- package/src/server/selinux.js +185 -0
- package/src/server/systemd.js +205 -0
- package/src/server/underpost-compression.js +186 -0
- package/src/server/underpost-gateway.js +20 -10
- package/src/server/underpost-ingress.js +18 -2
- package/test/selinux.test.js +71 -0
- package/test/underpost-gateway.test.js +41 -0
- package/test/underpost-ingress.test.js +52 -0
- package/test/wireguard-edge.test.js +1177 -0
package/src/cli/ssh.js
CHANGED
|
@@ -8,6 +8,14 @@ import { generateRandomPasswordSelection } from '../client/components/core/Commo
|
|
|
8
8
|
import { pbcopy, shellExec } from '../server/process.js';
|
|
9
9
|
import { loggerFactory } from '../server/logger.js';
|
|
10
10
|
import { waitForPort } from '../server/conf.js';
|
|
11
|
+
import {
|
|
12
|
+
runSELinuxCommands,
|
|
13
|
+
selinuxPackagesCommandFactory,
|
|
14
|
+
selinuxRestoreconCommandFactory,
|
|
15
|
+
selinuxSshContextCommandsFactory,
|
|
16
|
+
selinuxSshPortCommandsFactory,
|
|
17
|
+
shellArgumentFactory,
|
|
18
|
+
} from '../server/selinux.js';
|
|
11
19
|
import fs from 'fs-extra';
|
|
12
20
|
import Underpost from '../index.js';
|
|
13
21
|
|
|
@@ -57,7 +65,7 @@ class UnderpostSSH {
|
|
|
57
65
|
* @returns {boolean} True if user exists, false otherwise
|
|
58
66
|
*/
|
|
59
67
|
checkUserExists: (username) => {
|
|
60
|
-
const result = shellExec(`id -u ${username} 2>/dev/null || echo "not_found"`, {
|
|
68
|
+
const result = shellExec(`id -u ${shellArgumentFactory(username)} 2>/dev/null || echo "not_found"`, {
|
|
61
69
|
silent: true,
|
|
62
70
|
stdout: true,
|
|
63
71
|
}).trim();
|
|
@@ -73,7 +81,7 @@ class UnderpostSSH {
|
|
|
73
81
|
* @returns {string} User's home directory path
|
|
74
82
|
*/
|
|
75
83
|
getUserHome: (username) => {
|
|
76
|
-
return shellExec(`getent passwd ${username} | cut -d: -f6`, {
|
|
84
|
+
return shellExec(`getent passwd ${shellArgumentFactory(username)} | cut -d: -f6`, {
|
|
77
85
|
silent: true,
|
|
78
86
|
stdout: true,
|
|
79
87
|
}).trim();
|
|
@@ -90,11 +98,13 @@ class UnderpostSSH {
|
|
|
90
98
|
* @returns {void}
|
|
91
99
|
*/
|
|
92
100
|
createSystemUser: (username, password, groups) => {
|
|
93
|
-
|
|
94
|
-
shellExec(`
|
|
101
|
+
if (!/^[a-z_][a-z0-9_-]*[$]?$/i.test(username)) throw new TypeError('Invalid system username');
|
|
102
|
+
shellExec(`useradd -m -s /bin/bash ${shellArgumentFactory(username)}`);
|
|
103
|
+
shellExec(`printf '%s\n' ${shellArgumentFactory(`${username}:${password}`)} | chpasswd`, { disableLog: true });
|
|
95
104
|
if (groups) {
|
|
96
105
|
for (const group of groups.split(',').map((g) => g.trim())) {
|
|
97
|
-
|
|
106
|
+
if (!/^[a-z_][a-z0-9_-]*[$]?$/i.test(group)) throw new TypeError('Invalid system group');
|
|
107
|
+
shellExec(`usermod -aG ${shellArgumentFactory(group)} ${shellArgumentFactory(username)}`);
|
|
98
108
|
}
|
|
99
109
|
}
|
|
100
110
|
},
|
|
@@ -109,8 +119,8 @@ class UnderpostSSH {
|
|
|
109
119
|
*/
|
|
110
120
|
ensureSSHDirectory: (sshDir) => {
|
|
111
121
|
if (!fs.existsSync(sshDir)) {
|
|
112
|
-
shellExec(`mkdir -p ${sshDir}`);
|
|
113
|
-
shellExec(`chmod 700 ${sshDir}`);
|
|
122
|
+
shellExec(`mkdir -p ${shellArgumentFactory(sshDir)}`);
|
|
123
|
+
shellExec(`chmod 700 ${shellArgumentFactory(sshDir)}`);
|
|
114
124
|
}
|
|
115
125
|
},
|
|
116
126
|
|
|
@@ -126,11 +136,12 @@ class UnderpostSSH {
|
|
|
126
136
|
* @returns {void}
|
|
127
137
|
*/
|
|
128
138
|
setSSHFilePermissions: (sshDir, username, keyPath, pubKeyPath) => {
|
|
129
|
-
shellExec(`chmod 600 ${sshDir}/authorized_keys`);
|
|
130
|
-
shellExec(`chmod 644 ${sshDir}/known_hosts`);
|
|
131
|
-
if (keyPath) shellExec(`chmod 600 ${keyPath}`);
|
|
132
|
-
if (pubKeyPath) shellExec(`chmod 644 ${pubKeyPath}`);
|
|
133
|
-
shellExec(`chown -R ${username}:${username} ${sshDir}`);
|
|
139
|
+
shellExec(`chmod 600 ${shellArgumentFactory(`${sshDir}/authorized_keys`)}`);
|
|
140
|
+
shellExec(`chmod 644 ${shellArgumentFactory(`${sshDir}/known_hosts`)}`);
|
|
141
|
+
if (keyPath) shellExec(`chmod 600 ${shellArgumentFactory(keyPath)}`);
|
|
142
|
+
if (pubKeyPath) shellExec(`chmod 644 ${shellArgumentFactory(pubKeyPath)}`);
|
|
143
|
+
shellExec(`chown -R ${shellArgumentFactory(`${username}:${username}`)} ${shellArgumentFactory(sshDir)}`);
|
|
144
|
+
runSELinuxCommands(selinuxSshContextCommandsFactory({ sshDirectory: sshDir }), { execute: shellExec });
|
|
134
145
|
},
|
|
135
146
|
|
|
136
147
|
/**
|
|
@@ -144,13 +155,12 @@ class UnderpostSSH {
|
|
|
144
155
|
* @returns {void}
|
|
145
156
|
*/
|
|
146
157
|
configureAuthorizedKeys: (sshDir, pubKeyPath, disablePassword) => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
158
|
+
const key = fs.readFileSync(pubKeyPath, 'utf8').trim();
|
|
159
|
+
const entry = disablePassword ? `no-port-forwarding,no-X11-forwarding,no-agent-forwarding ${key}` : key;
|
|
160
|
+
const authorizedKeysPath = `${sshDir}/authorized_keys`;
|
|
161
|
+
shellExec(
|
|
162
|
+
`grep -qxF ${shellArgumentFactory(entry)} ${shellArgumentFactory(authorizedKeysPath)} || printf '%s\n' ${shellArgumentFactory(entry)} >> ${shellArgumentFactory(authorizedKeysPath)}`,
|
|
163
|
+
);
|
|
154
164
|
},
|
|
155
165
|
|
|
156
166
|
/**
|
|
@@ -164,9 +174,13 @@ EOF`);
|
|
|
164
174
|
* @returns {void}
|
|
165
175
|
*/
|
|
166
176
|
configureKnownHosts: (sshDir, port, host) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
177
|
+
const sshPort = Number(port);
|
|
178
|
+
if (!Number.isInteger(sshPort) || sshPort < 1 || sshPort > 65535)
|
|
179
|
+
throw new RangeError('SSH port must be 1-65535');
|
|
180
|
+
const knownHostsPath = shellArgumentFactory(`${sshDir}/known_hosts`);
|
|
181
|
+
shellExec(`ssh-keyscan -p ${sshPort} -H localhost >> ${knownHostsPath}`);
|
|
182
|
+
shellExec(`ssh-keyscan -p ${sshPort} -H 127.0.0.1 >> ${knownHostsPath}`);
|
|
183
|
+
if (host) shellExec(`ssh-keyscan -p ${sshPort} -H ${shellArgumentFactory(host)} >> ${knownHostsPath}`);
|
|
170
184
|
},
|
|
171
185
|
|
|
172
186
|
/**
|
|
@@ -181,10 +195,78 @@ EOF`);
|
|
|
181
195
|
*/
|
|
182
196
|
configureSudoAccess: (username, password, disablePassword) => {
|
|
183
197
|
if (disablePassword) {
|
|
184
|
-
|
|
198
|
+
if (!/^[a-z_][a-z0-9_-]*[$]?$/i.test(username)) throw new TypeError('Invalid sudo username');
|
|
199
|
+
const sudoersPath = `/etc/sudoers.d/90_${username}`;
|
|
200
|
+
const temporaryPath = `/tmp/underpost-sudoers-${process.pid}-${username}`;
|
|
201
|
+
const rule = `${username} ALL=(ALL) NOPASSWD: ALL`;
|
|
202
|
+
try {
|
|
203
|
+
shellExec(`printf '%s\n' ${shellArgumentFactory(rule)} > ${shellArgumentFactory(temporaryPath)}`);
|
|
204
|
+
shellExec(`sudo visudo -cf ${shellArgumentFactory(temporaryPath)}`);
|
|
205
|
+
shellExec(
|
|
206
|
+
`sudo install -m 0440 -o root -g root ${shellArgumentFactory(temporaryPath)} ${shellArgumentFactory(sudoersPath)}`,
|
|
207
|
+
);
|
|
208
|
+
runSELinuxCommands([selinuxRestoreconCommandFactory(sudoersPath, { recursive: false })], {
|
|
209
|
+
execute: shellExec,
|
|
210
|
+
});
|
|
211
|
+
} finally {
|
|
212
|
+
fs.removeSync(temporaryPath);
|
|
213
|
+
}
|
|
185
214
|
} else {
|
|
186
|
-
shellExec(`
|
|
215
|
+
shellExec(`printf '%s\n' ${shellArgumentFactory(`${username}:${password}`)} | sudo chpasswd`, {
|
|
216
|
+
disableLog: true,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Initializes the default SSH files and credentials for a local SSH service.
|
|
223
|
+
* @method
|
|
224
|
+
* @function initializeDefaultSshConfig
|
|
225
|
+
* @memberof UnderpostSSH
|
|
226
|
+
* @param {object} params - SSH configuration values.
|
|
227
|
+
* @param {string} params.userHome - Home directory of the SSH user.
|
|
228
|
+
* @param {string} params.user - SSH user name.
|
|
229
|
+
* @param {string} params.password - SSH key passphrase and user password.
|
|
230
|
+
* @param {string} params.host - Host used in the SSH key comment.
|
|
231
|
+
* @param {number} params.port - SSH port.
|
|
232
|
+
* @param {boolean} params.disablePassword - Whether to configure passwordless sudo.
|
|
233
|
+
* @param {string} [params.controllerPubKeyPath='./engine-private/deploy/id_rsa.pub'] - Preferred controller public key to authorize.
|
|
234
|
+
* @returns {void}
|
|
235
|
+
*/
|
|
236
|
+
initializeDefaultSshConfig: ({
|
|
237
|
+
userHome,
|
|
238
|
+
user,
|
|
239
|
+
password,
|
|
240
|
+
host,
|
|
241
|
+
port,
|
|
242
|
+
disablePassword,
|
|
243
|
+
controllerPubKeyPath = './engine-private/deploy/id_rsa.pub',
|
|
244
|
+
}) => {
|
|
245
|
+
const sshDir = `${userHome}/.ssh`;
|
|
246
|
+
const keyPath = `${sshDir}/id_rsa`;
|
|
247
|
+
const pubKeyPath = `${sshDir}/id_rsa.pub`;
|
|
248
|
+
|
|
249
|
+
Underpost.ssh.ensureSSHDirectory(sshDir);
|
|
250
|
+
fs.ensureFileSync(`${sshDir}/authorized_keys`);
|
|
251
|
+
fs.ensureFileSync(`${sshDir}/known_hosts`);
|
|
252
|
+
|
|
253
|
+
if (!fs.existsSync(keyPath)) {
|
|
254
|
+
shellExec(
|
|
255
|
+
`ssh-keygen -t ed25519 -f ${shellArgumentFactory(keyPath)} -N ${shellArgumentFactory(password)} -q -C ${shellArgumentFactory(`${user}@${host}`)}`,
|
|
256
|
+
{ disableLog: true },
|
|
257
|
+
);
|
|
187
258
|
}
|
|
259
|
+
if (!fs.existsSync(pubKeyPath))
|
|
260
|
+
shellExec(
|
|
261
|
+
`ssh-keygen -y -f ${shellArgumentFactory(keyPath)} -P ${shellArgumentFactory(password)} > ${shellArgumentFactory(pubKeyPath)}`,
|
|
262
|
+
{ disableLog: true },
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const authorizedKeyPath = fs.existsSync(controllerPubKeyPath) ? controllerPubKeyPath : pubKeyPath;
|
|
266
|
+
Underpost.ssh.configureAuthorizedKeys(sshDir, authorizedKeyPath, disablePassword);
|
|
267
|
+
Underpost.ssh.configureKnownHosts(sshDir, port, host);
|
|
268
|
+
Underpost.ssh.configureSudoAccess(user, password, disablePassword);
|
|
269
|
+
Underpost.ssh.setSSHFilePermissions(sshDir, user, keyPath, pubKeyPath);
|
|
188
270
|
},
|
|
189
271
|
|
|
190
272
|
/**
|
|
@@ -489,7 +571,44 @@ EOF`);
|
|
|
489
571
|
|
|
490
572
|
// Handle start server
|
|
491
573
|
if (options.start) {
|
|
492
|
-
|
|
574
|
+
if (!options.deployId) {
|
|
575
|
+
Underpost.ssh.initializeDefaultSshConfig({
|
|
576
|
+
userHome,
|
|
577
|
+
user: options.user,
|
|
578
|
+
password: options.password,
|
|
579
|
+
host: options.host,
|
|
580
|
+
port: options.port,
|
|
581
|
+
disablePassword: options.disablePassword,
|
|
582
|
+
});
|
|
583
|
+
} else {
|
|
584
|
+
// Hardening sshd on a host where the account does not exist produces a
|
|
585
|
+
// daemon that starts cleanly and refuses the only user it was meant to
|
|
586
|
+
// admit. Nothing downstream reports that, so the run has to.
|
|
587
|
+
if (!Underpost.ssh.checkUserExists(options.user))
|
|
588
|
+
throw new Error(
|
|
589
|
+
`[ssh] user '${options.user}' does not exist on this host; ` +
|
|
590
|
+
`run: node bin ssh --deploy-id ${options.deployId} --user ${options.user} --user-add`,
|
|
591
|
+
);
|
|
592
|
+
Underpost.ssh.chmod({ user: options.user });
|
|
593
|
+
// The registry records the key pair this deploy authenticates with, so
|
|
594
|
+
// a box whose authorized_keys was lost converges here rather than
|
|
595
|
+
// needing the key re-issued — which would invalidate every other host
|
|
596
|
+
// holding it.
|
|
597
|
+
const recordedPubKey = confNode?.users?.[options.user]?.publicKeyCopyPath;
|
|
598
|
+
if (recordedPubKey && fs.existsSync(recordedPubKey)) {
|
|
599
|
+
const sshDir = `${userHome}/.ssh`;
|
|
600
|
+
Underpost.ssh.ensureSSHDirectory(sshDir);
|
|
601
|
+
fs.ensureFileSync(`${sshDir}/authorized_keys`);
|
|
602
|
+
Underpost.ssh.configureAuthorizedKeys(sshDir, recordedPubKey, options.disablePassword);
|
|
603
|
+
Underpost.ssh.chmod({ user: options.user });
|
|
604
|
+
logger.info(`Authorized key present for ${options.user}`, { source: recordedPubKey });
|
|
605
|
+
} else if (recordedPubKey) {
|
|
606
|
+
logger.warn('Recorded public key is missing; authorized_keys left untouched', {
|
|
607
|
+
user: options.user,
|
|
608
|
+
expected: recordedPubKey,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
493
612
|
Underpost.ssh.initService({ port: options.port });
|
|
494
613
|
}
|
|
495
614
|
|
|
@@ -848,12 +967,26 @@ EOF
|
|
|
848
967
|
* Sets ownership to specified user for ~/.ssh/ and contents.
|
|
849
968
|
*/
|
|
850
969
|
chmod: ({ user }) => {
|
|
851
|
-
|
|
852
|
-
shellExec(`sudo chmod
|
|
853
|
-
shellExec(
|
|
854
|
-
|
|
970
|
+
const sshDirectory = `${Underpost.ssh.getUserHome(user)}/.ssh`;
|
|
971
|
+
shellExec(`sudo chmod 700 ${shellArgumentFactory(sshDirectory)}`);
|
|
972
|
+
shellExec(
|
|
973
|
+
`if [ -f ${shellArgumentFactory(`${sshDirectory}/authorized_keys`)} ]; then sudo chmod 600 ${shellArgumentFactory(`${sshDirectory}/authorized_keys`)}; fi`,
|
|
974
|
+
);
|
|
975
|
+
shellExec(
|
|
976
|
+
`if [ -f ${shellArgumentFactory(`${sshDirectory}/known_hosts`)} ]; then sudo chmod 644 ${shellArgumentFactory(`${sshDirectory}/known_hosts`)}; fi`,
|
|
977
|
+
);
|
|
978
|
+
shellExec(
|
|
979
|
+
`if [ -f ${shellArgumentFactory(`${sshDirectory}/id_rsa`)} ]; then sudo chmod 600 ${shellArgumentFactory(`${sshDirectory}/id_rsa`)}; fi`,
|
|
980
|
+
);
|
|
855
981
|
shellExec(`sudo chmod 600 /etc/ssh/ssh_host_ed25519_key`);
|
|
856
|
-
shellExec(`chown -R ${user}:${user}
|
|
982
|
+
shellExec(`sudo chown -R ${shellArgumentFactory(`${user}:${user}`)} ${shellArgumentFactory(sshDirectory)}`);
|
|
983
|
+
runSELinuxCommands(
|
|
984
|
+
[
|
|
985
|
+
...selinuxSshContextCommandsFactory({ sshDirectory }),
|
|
986
|
+
selinuxRestoreconCommandFactory('/etc/ssh/ssh_host_ed25519_key', { recursive: false }),
|
|
987
|
+
],
|
|
988
|
+
{ execute: shellExec },
|
|
989
|
+
);
|
|
857
990
|
},
|
|
858
991
|
|
|
859
992
|
/**
|
|
@@ -866,7 +999,7 @@ EOF
|
|
|
866
999
|
* @description
|
|
867
1000
|
* Configures SSH daemon with hardened security settings:
|
|
868
1001
|
* - Disables password authentication (key-only)
|
|
869
|
-
* -
|
|
1002
|
+
* - Allows key-only root login
|
|
870
1003
|
* - Enables ED25519 host key
|
|
871
1004
|
* - Disables X11 forwarding and TCP forwarding
|
|
872
1005
|
* - Sets client alive intervals to prevent ghost connections
|
|
@@ -878,69 +1011,57 @@ EOF
|
|
|
878
1011
|
* - Displays service status with colored output
|
|
879
1012
|
*/
|
|
880
1013
|
initService: ({ port }) => {
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
# ==============================================================
|
|
886
|
-
|
|
887
|
-
# --- Network Settings ---
|
|
888
|
-
Port ${port ? port : '22'}
|
|
889
|
-
# Explicitly listen on all interfaces (IPv4 and IPv6)
|
|
890
|
-
|
|
891
|
-
# --- Host Keys ---
|
|
892
|
-
# ED25519 is the modern standard (Fast & Secure)
|
|
1014
|
+
const sshPort = Number(port || 22);
|
|
1015
|
+
const portCommands = selinuxSshPortCommandsFactory({ port: sshPort });
|
|
1016
|
+
const temporaryPath = `/tmp/underpost-sshd-config-${process.pid}`;
|
|
1017
|
+
const configuration = `Port ${sshPort}
|
|
893
1018
|
HostKey /etc/ssh/ssh_host_ed25519_key
|
|
894
|
-
# RSA is kept for compatibility with older clients (Optional)
|
|
895
|
-
# HostKey /etc/ssh/ssh_host_rsa_key
|
|
896
|
-
|
|
897
|
-
# --- Logging ---
|
|
898
1019
|
SyslogFacility AUTHPRIV
|
|
899
|
-
# VERBOSE logs the key fingerprint used for login (Audit trail)
|
|
900
1020
|
LogLevel VERBOSE
|
|
901
|
-
|
|
902
|
-
# --- Authentication & Security ---
|
|
903
|
-
# STRICTLY KEY-BASED AUTHENTICATION
|
|
904
1021
|
PubkeyAuthentication yes
|
|
905
1022
|
PasswordAuthentication no
|
|
906
|
-
|
|
1023
|
+
KbdInteractiveAuthentication no
|
|
907
1024
|
PermitEmptyPasswords no
|
|
908
|
-
|
|
909
|
-
# PREVENT ROOT LOGIN
|
|
910
|
-
# Administrators should log in as a standard user and use 'sudo'
|
|
911
|
-
PermitRootLogin no
|
|
912
|
-
|
|
913
|
-
# Security checks on ownership of ~/.ssh/ files
|
|
1025
|
+
PermitRootLogin prohibit-password
|
|
914
1026
|
StrictModes yes
|
|
915
1027
|
MaxAuthTries 3
|
|
916
1028
|
LoginGraceTime 60
|
|
917
|
-
|
|
918
|
-
# --- PAM (Pluggable Authentication Modules) ---
|
|
919
|
-
# REQUIRED: Must be 'yes' on RHEL for proper session/SELinux handling.
|
|
920
|
-
# Since PasswordAuthentication is 'no', PAM will not ask for passwords.
|
|
921
1029
|
UsePAM yes
|
|
922
|
-
|
|
923
|
-
# --- Session & Network Health ---
|
|
924
|
-
# Disconnect idle sessions after 5 minutes (300s * 0) to prevent ghost connections
|
|
925
1030
|
ClientAliveInterval 300
|
|
926
1031
|
ClientAliveCountMax 0
|
|
927
|
-
|
|
928
|
-
# --- Feature Restrictions ---
|
|
929
|
-
# Disable GUI forwarding unless explicitly needed
|
|
930
1032
|
X11Forwarding no
|
|
931
|
-
# Disable DNS checks for faster logins (unless you use Host based auth)
|
|
932
1033
|
UseDNS no
|
|
933
|
-
# Disable tunneling unless needed
|
|
934
1034
|
PermitTunnel no
|
|
935
1035
|
AllowTcpForwarding no
|
|
936
|
-
|
|
937
|
-
# --- Subsystem ---
|
|
1036
|
+
Include /etc/ssh/sshd_config.d/*.conf
|
|
938
1037
|
Subsystem sftp /usr/libexec/openssh/sftp-server
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
);
|
|
942
|
-
|
|
943
|
-
|
|
1038
|
+
`;
|
|
1039
|
+
|
|
1040
|
+
fs.writeFileSync(temporaryPath, configuration, { mode: 0o600 });
|
|
1041
|
+
try {
|
|
1042
|
+
if (sshPort !== 22) {
|
|
1043
|
+
shellExec(
|
|
1044
|
+
`if command -v dnf >/dev/null 2>&1 && ! command -v semanage >/dev/null 2>&1; then ${selinuxPackagesCommandFactory()}; fi`,
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
runSELinuxCommands(portCommands, { execute: shellExec });
|
|
1048
|
+
shellExec(`sudo ssh-keygen -A`);
|
|
1049
|
+
shellExec(`sudo /usr/sbin/sshd -t -f ${shellArgumentFactory(temporaryPath)}`);
|
|
1050
|
+
shellExec(`sudo install -m 0600 -o root -g root ${shellArgumentFactory(temporaryPath)} /etc/ssh/sshd_config`);
|
|
1051
|
+
runSELinuxCommands(
|
|
1052
|
+
[selinuxRestoreconCommandFactory(['/etc/ssh/sshd_config', '/etc/ssh/ssh_host_ed25519_key'])],
|
|
1053
|
+
{ execute: shellExec },
|
|
1054
|
+
);
|
|
1055
|
+
if (sshPort !== 22) {
|
|
1056
|
+
shellExec(
|
|
1057
|
+
`if command -v firewall-cmd >/dev/null 2>&1 && sudo firewall-cmd --state >/dev/null 2>&1; then sudo firewall-cmd --permanent --add-port=${sshPort}/tcp && sudo firewall-cmd --reload; fi`,
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
shellExec(`sudo systemctl enable sshd`);
|
|
1061
|
+
shellExec(`sudo systemctl restart sshd`);
|
|
1062
|
+
} finally {
|
|
1063
|
+
fs.removeSync(temporaryPath);
|
|
1064
|
+
}
|
|
944
1065
|
|
|
945
1066
|
const status = shellExec(`sudo systemctl status sshd`, { silent: true, stdout: true });
|
|
946
1067
|
if (status.match('running')) console.log(status.replaceAll(`running`, `running`.green));
|
package/src/cli/system.js
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import fs from 'fs-extra';
|
|
11
|
+
import {
|
|
12
|
+
selinuxEnforcingCommandsFactory,
|
|
13
|
+
selinuxPackagesCommandFactory,
|
|
14
|
+
selinuxSshContextCommandsFactory,
|
|
15
|
+
shellArgumentFactory,
|
|
16
|
+
} from '../server/selinux.js';
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
19
|
* @class UnderpostSystemProvisionig
|
|
@@ -196,6 +202,8 @@ logdir /var/log/chrony
|
|
|
196
202
|
|
|
197
203
|
// Install essential system tools (avoiding duplicates from container packages)
|
|
198
204
|
`dnf -y install --allowerasing bzip2 openssh-server nano vim-enhanced less openssl-devel git gnupg2 libnsl perl`,
|
|
205
|
+
selinuxPackagesCommandFactory({ sudo: false }),
|
|
206
|
+
...selinuxEnforcingCommandsFactory({ sudo: false }),
|
|
199
207
|
`dnf clean all`,
|
|
200
208
|
|
|
201
209
|
// Install Node.js
|
|
@@ -218,19 +226,24 @@ logdir /var/log/chrony
|
|
|
218
226
|
* @memberof UnderpostSystemProvisionig
|
|
219
227
|
* @returns {string[]} An array of shell commands.
|
|
220
228
|
*/
|
|
221
|
-
user: () =>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
229
|
+
user: ({ username = 'root', publicKeyPath = '/home/dd/engine/engine-private/deploy/id_rsa.pub' } = {}) => {
|
|
230
|
+
if (!/^[a-z_][a-z0-9_-]*[$]?$/i.test(username)) throw new TypeError('Invalid Rocky Linux username');
|
|
231
|
+
const home = username === 'root' ? '/root' : `/home/${username}`;
|
|
232
|
+
const sshDirectory = `${home}/.ssh`;
|
|
233
|
+
const publicKey = fs.readFileSync(publicKeyPath, 'utf8').trim();
|
|
234
|
+
const userCommand =
|
|
235
|
+
username === 'root'
|
|
236
|
+
? `usermod -s /bin/bash root`
|
|
237
|
+
: `id -u ${shellArgumentFactory(username)} >/dev/null 2>&1 || useradd -m -s /bin/bash -G wheel ${shellArgumentFactory(username)}`;
|
|
238
|
+
return [
|
|
239
|
+
userCommand,
|
|
240
|
+
`install -d -m 0700 -o ${shellArgumentFactory(username)} -g ${shellArgumentFactory(username)} ${shellArgumentFactory(sshDirectory)}`,
|
|
241
|
+
`printf '%s\n' ${shellArgumentFactory(publicKey)} > ${shellArgumentFactory(`${sshDirectory}/authorized_keys`)}`,
|
|
242
|
+
`chown ${shellArgumentFactory(`${username}:${username}`)} ${shellArgumentFactory(`${sshDirectory}/authorized_keys`)}`,
|
|
243
|
+
`chmod 0600 ${shellArgumentFactory(`${sshDirectory}/authorized_keys`)}`,
|
|
244
|
+
...selinuxSshContextCommandsFactory({ sshDirectory, sudo: false }),
|
|
245
|
+
];
|
|
246
|
+
},
|
|
234
247
|
/**
|
|
235
248
|
* @method timezone
|
|
236
249
|
* @description Generates shell commands for configuring the system timezone on Rocky Linux.
|
package/src/cli/test.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import fs from 'fs-extra';
|
|
8
8
|
import { timer } from '../client/components/core/CommonJs.js';
|
|
9
9
|
import { MariaDB } from '../db/mariadb/MariaDB.js';
|
|
10
|
-
import { getNpmRootPath } from '../server/
|
|
10
|
+
import { getNpmRootPath } from '../server/environment.js';
|
|
11
11
|
import { actionInitLog, loggerFactory, setUpInfo } from '../server/logger.js';
|
|
12
12
|
import { pbcopy, shellExec } from '../server/process.js';
|
|
13
13
|
import Underpost from '../index.js';
|