diff --git a/backend/index.js b/backend/index.js index b2e0f91..92cf760 100644 --- a/backend/index.js +++ b/backend/index.js @@ -238,9 +238,8 @@ const DEFAULT_PORTAINER_SSH_CONFIG = { host: '', port: 22, username: '', - extraSshArgs: [], - privateKey: '', - privateKeyPassphrase: '' + password: '', + extraSshArgs: [] }; const normalizeString = (value, fallback = '') => { @@ -283,14 +282,7 @@ const normalizeExtraArgs = (value, fallback = []) => { return Array.isArray(fallback) ? [...fallback] : []; }; -const normalizePrivateKey = (value, fallback = DEFAULT_PORTAINER_SSH_CONFIG.privateKey) => { - if (value === undefined) return fallback; - if (value === null) return ''; - if (typeof value !== 'string') return String(value); - return value.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim(); -}; - -const normalizePrivateKeyPassphrase = (value, fallback = DEFAULT_PORTAINER_SSH_CONFIG.privateKeyPassphrase) => { +const normalizePassword = (value, fallback = DEFAULT_PORTAINER_SSH_CONFIG.password) => { if (value === undefined) return fallback; if (value === null) return ''; return String(value); @@ -303,14 +295,14 @@ const getPortainerSshConfig = () => { } try { const parsed = stored.value ? JSON.parse(stored.value) : {}; - const decryptedKey = parsed.privateKeyEncrypted ? decryptSensitive(parsed.privateKeyEncrypted) : ''; + const decryptedPassword = parsed.passwordEncrypted ? decryptSensitive(parsed.passwordEncrypted) : null; + const fallbackPassword = parsed.password !== undefined ? parsed.password : null; return { host: normalizeString(parsed.host), port: normalizePort(parsed.port), username: normalizeString(parsed.username), - extraSshArgs: normalizeExtraArgs(parsed.extraSshArgs), - privateKey: decryptedKey, - privateKeyPassphrase: parsed.privateKeyPassphraseEncrypted ? decryptSensitive(parsed.privateKeyPassphraseEncrypted) : '' + password: normalizePassword(decryptedPassword ?? fallbackPassword), + extraSshArgs: normalizeExtraArgs(parsed.extraSshArgs) }; } catch (err) { console.warn('⚠️ [Maintenance] Konnte Portainer SSH Konfiguration nicht parsen:', err.message); @@ -324,8 +316,7 @@ const persistPortainerSshConfig = (config) => { port: config.port, username: config.username, extraSshArgs: config.extraSshArgs, - privateKeyEncrypted: config.privateKey ? encryptSensitive(config.privateKey) : null, - privateKeyPassphraseEncrypted: config.privateKeyPassphrase ? encryptSensitive(config.privateKeyPassphrase) : null + passwordEncrypted: config.password ? encryptSensitive(config.password) : null }; setSetting(PORTAINER_SSH_CONFIG_KEY, JSON.stringify(payload)); }; @@ -334,9 +325,8 @@ const mergeSshConfig = (base, overrides = {}) => ({ host: normalizeString(overrides.host, base.host), port: normalizePort(overrides.port, base.port), username: normalizeString(overrides.username, base.username), - extraSshArgs: normalizeExtraArgs(overrides.extraSshArgs, base.extraSshArgs), - privateKey: normalizePrivateKey(overrides.privateKey, base.privateKey), - privateKeyPassphrase: normalizePrivateKeyPassphrase(overrides.privateKeyPassphrase, base.privateKeyPassphrase) + password: normalizePassword(overrides.password, base.password), + extraSshArgs: normalizeExtraArgs(overrides.extraSshArgs, base.extraSshArgs) }); const savePortainerSshConfig = (payload = {}) => { @@ -351,36 +341,17 @@ const deletePortainerSshConfig = () => { return { ...DEFAULT_PORTAINER_SSH_CONFIG }; }; -const createTempPrivateKeyFile = (privateKey) => { - const normalized = normalizePrivateKey(privateKey, ''); - const content = normalized.endsWith('\n') ? normalized : `${normalized}\n`; - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'stackpulse-ssh-')); - const filePath = path.join(tmpDir, 'id_rsa'); - fs.writeFileSync(filePath, content, { mode: 0o600 }); - return { - filePath, - cleanup: () => { - try { - fs.unlinkSync(filePath); - } catch (err) { } - try { - fs.rmdirSync(tmpDir); - } catch (err) { } - } - }; -}; - -const createTempAskPassScript = (passphrase) => { +const createTempAskPassScript = (secret) => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'stackpulse-askpass-')); const scriptPath = path.join(tmpDir, 'askpass.sh'); - const scriptContent = "#!/bin/sh\nprintf '%s\\n' \"$STACKPULSE_SSH_PASS\"\n"; + const scriptContent = "#!/bin/sh\nprintf '%s\n' \"$STACKPULSE_SSH_PASS\"\n"; fs.writeFileSync(scriptPath, scriptContent, { mode: 0o700 }); return { env: { SSH_ASKPASS: scriptPath, SSH_ASKPASS_REQUIRE: 'force', DISPLAY: process.env.DISPLAY || ':9999', - STACKPULSE_SSH_PASS: passphrase + STACKPULSE_SSH_PASS: secret }, cleanup: () => { try { @@ -404,7 +375,10 @@ const buildSshCommandArgs = (config) => { const args = [ '-p', String(sshConfig.port), - '-o', 'StrictHostKeyChecking=no' + '-o', 'StrictHostKeyChecking=no', + '-o', 'PreferredAuthentications=password', + '-o', 'PubkeyAuthentication=no', + '-o', 'NumberOfPasswordPrompts=1' ]; const cleanupTasks = []; @@ -414,14 +388,14 @@ const buildSshCommandArgs = (config) => { } }; - if (!sshConfig.privateKeyPassphrase) { - args.push('-o', 'BatchMode=yes'); - } - - if (sshConfig.privateKey) { - const { filePath, cleanup: dispose } = createTempPrivateKeyFile(sshConfig.privateKey); + let envOverrides = {}; + if (sshConfig.password) { + const { env, cleanup: dispose } = createTempAskPassScript(sshConfig.password); + envOverrides = { ...envOverrides, ...env }; registerCleanup(dispose); - args.push('-i', filePath); + args.push('-o', 'BatchMode=no'); + } else { + args.push('-o', 'BatchMode=yes'); } if (sshConfig.extraSshArgs.length) { @@ -431,13 +405,6 @@ const buildSshCommandArgs = (config) => { } } - let envOverrides = {}; - if (sshConfig.privateKeyPassphrase) { - const { env, cleanup: dispose } = createTempAskPassScript(sshConfig.privateKeyPassphrase); - envOverrides = { ...envOverrides, ...env }; - registerCleanup(dispose); - } - args.push(`${sshConfig.username}@${sshConfig.host}`); const cleanup = () => { @@ -1237,8 +1204,7 @@ app.get('/api/maintenance/config', (req, res) => { port: ssh.port, username: ssh.username, extraSshArgs: ssh.extraSshArgs, - privateKeyStored: Boolean(ssh.privateKey), - privateKeyPassphraseStored: Boolean(ssh.privateKeyPassphrase) + passwordStored: Boolean(ssh.password) } }); }); @@ -1253,8 +1219,7 @@ app.put('/api/maintenance/ssh-config', (req, res) => { port: config.port, username: config.username, extraSshArgs: config.extraSshArgs, - privateKeyStored: Boolean(config.privateKey), - privateKeyPassphraseStored: Boolean(config.privateKeyPassphrase) + passwordStored: Boolean(config.password) } }); } catch (err) { @@ -1272,8 +1237,7 @@ app.delete('/api/maintenance/ssh-config', (req, res) => { port: config.port, username: config.username, extraSshArgs: config.extraSshArgs, - privateKeyStored: false, - privateKeyPassphraseStored: false + passwordStored: false } }); }); diff --git a/frontend/src/Maintenance.jsx b/frontend/src/Maintenance.jsx index e89ef16..86d0ded 100644 --- a/frontend/src/Maintenance.jsx +++ b/frontend/src/Maintenance.jsx @@ -95,8 +95,7 @@ const createEmptySshDraft = () => ({ host: '', port: '22', username: '', - privateKey: '', - privateKeyPassphrase: '', + password: '', extraSshArgs: '' }); @@ -127,10 +126,8 @@ export default function Maintenance() { const [updateActionError, setUpdateActionError] = useState(""); const [sshDraft, setSshDraft] = useState(() => createEmptySshDraft()); - const [sshKeyStored, setSshKeyStored] = useState(false); - const [sshPassphraseStored, setSshPassphraseStored] = useState(false); - const [showPrivateKey, setShowPrivateKey] = useState(false); - const [showPassphrase, setShowPassphrase] = useState(false); + const [sshPasswordStored, setSshPasswordStored] = useState(false); + const [showPassword, setShowPassword] = useState(false); const [sshSaving, setSshSaving] = useState(false); const [sshTesting, setSshTesting] = useState(false); const [sshDeleting, setSshDeleting] = useState(false); @@ -145,10 +142,8 @@ export default function Maintenance() { useEffect(() => { if (!sshConfig) { setSshDraft(createEmptySshDraft()); - setSshKeyStored(false); - setSshPassphraseStored(false); - setShowPrivateKey(false); - setShowPassphrase(false); + setSshPasswordStored(false); + setShowPassword(false); setSshTestResult(null); return; } @@ -156,14 +151,11 @@ export default function Maintenance() { host: sshConfig.host ?? '', port: String(sshConfig.port ?? '22'), username: sshConfig.username ?? '', - privateKey: '', - privateKeyPassphrase: '', + password: '', extraSshArgs: Array.isArray(sshConfig.extraSshArgs) ? sshConfig.extraSshArgs.join('\n') : '' }); - setSshKeyStored(Boolean(sshConfig.privateKeyStored)); - setSshPassphraseStored(Boolean(sshConfig.privateKeyPassphraseStored)); - setShowPrivateKey(false); - setShowPassphrase(false); + setSshPasswordStored(Boolean(sshConfig.passwordStored)); + setShowPassword(false); setSshTestResult(null); }, [sshConfig]); @@ -273,11 +265,8 @@ export default function Maintenance() { const handleSshDraftChange = useCallback((field, value) => { setSshDraft((prev) => ({ ...prev, [field]: value })); - if (field === 'privateKey') { - setSshKeyStored(false); - } - if (field === 'privateKeyPassphrase') { - setSshPassphraseStored(false); + if (field === 'password') { + setSshPasswordStored(false); } }, []); @@ -286,29 +275,21 @@ export default function Maintenance() { host: sshDraft.host.trim(), port: Number.parseInt(sshDraft.port, 10) || 22, username: sshDraft.username.trim(), - extraSshArgs: sshDraft.extraSshArgs + extraSshArgs: (sshDraft.extraSshArgs || '') .split(/\r?\n/) .map((entry) => entry.trim()) .filter(Boolean) }; - const rawKey = (sshDraft.privateKey ?? ''); - const sanitizedKey = rawKey.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim(); - if (sanitizedKey) { - normalized.privateKey = sanitizedKey; - } else if (!sshKeyStored) { - normalized.privateKey = ''; - } - - const rawPassphrase = sshDraft.privateKeyPassphrase ?? ''; - if (rawPassphrase) { - normalized.privateKeyPassphrase = rawPassphrase; - } else if (!sshPassphraseStored) { - normalized.privateKeyPassphrase = ''; + const rawPassword = sshDraft.password ?? ''; + if (rawPassword) { + normalized.password = rawPassword; + } else if (!sshPasswordStored) { + normalized.password = ''; } return normalized; - }, [sshDraft, sshKeyStored, sshPassphraseStored]); + }, [sshDraft, sshPasswordStored]); const handleScriptSave = useCallback(async () => { if (!scriptConfig) return; @@ -350,8 +331,7 @@ export default function Maintenance() { setSshSaving(true); await saveSshConfig(normalizedSshDraft); setSshTestResult(null); - setShowPrivateKey(false); - setShowPassphrase(false); + setShowPassword(false); showToast({ variant: "success", title: "SSH-Konfiguration gespeichert", @@ -389,10 +369,8 @@ export default function Maintenance() { setSshDeleting(true); await deleteSshConfig(); setSshDraft(createEmptySshDraft()); - setSshKeyStored(false); - setSshPassphraseStored(false); - setShowPrivateKey(false); - setShowPassphrase(false); + setSshPasswordStored(false); + setShowPassword(false); setSshTestResult(null); showToast({ variant: "info", @@ -766,60 +744,30 @@ export default function Maintenance() {
- + -
-