fix: op:// secret references rejected as ssh keys on credentials (#1394)

This commit is contained in:
LukeGus committed 2026-10-03 15:41:44 -05:00
1 parent 86840f7642
commit b03000e396
8 files changed
+342 -21

No files matched your search

@@ -9,6 +9,9 @@ import { createCurrentHostResolutionRepository } from "../repositories/factory.j
import { connectHost } from "../../hosts/connect/connect-host.js";
import type { SshConnectHost } from "../../hosts/connect/types.js";
import { resolveHostById } from "../../hosts/host-resolver.js";
import { isSecretReference } from "../../hosts/external-secrets.js";
import { resolveKeyReferences } from "../../hosts/stored-ssh-key.js";
import { parseSSHKey } from "../../utils/ssh-key-utils.js";
function describeDeployError(err: unknown): string {
const message = getErrorMessage(err, "Connection failed");
@@ -363,7 +366,20 @@ export function registerCredentialDeployRoutes(
});
}
const publicKey = credData.publicKey;
let publicKey = credData.publicKey;
const storedKey = credData.privateKey || credData.key;
if (!publicKey && isSecretReference(storedKey)) {
// A key kept in a secret source has no stored public key; derive
// it from the resolved key.
const resolved = await resolveKeyReferences(
userId,
storedKey,
(credData as { keyPassword?: string | null }).keyPassword,
);
publicKey =
parseSSHKey(resolved.key, resolved.passphrase).publicKey ||
undefined;
}
if (!publicKey) {
return res.status(400).json({
success: false,
@@ -8,6 +8,8 @@ import {
parseSSHKey,
validateKeyPair,
} from "../../utils/ssh-key-utils.js";
import { resolveKeyReferences } from "../../hosts/stored-ssh-key.js";
import type { AuthenticatedRequest } from "../../../types/index.js";
const { utils: ssh2Utils } = ssh2Pkg;
@@ -99,13 +101,17 @@ export function registerCredentialKeyRoutes(
requireCredentialViewPermission,
requireDataAccess,
async (req: Request, res: Response) => {
const { privateKey, keyPassword } = req.body;
if (!privateKey || typeof privateKey !== "string") {
if (!req.body?.privateKey || typeof req.body.privateKey !== "string") {
return res.status(400).json({ error: "Private key is required" });
}
try {
const { key: privateKey, passphrase: keyPassword } =
await resolveKeyReferences(
(req as AuthenticatedRequest).userId,
req.body.privateKey,
req.body.keyPassword,
);
const keyInfo = parseSSHKey(privateKey, keyPassword);
const response = {
@@ -352,13 +358,17 @@ export function registerCredentialKeyRoutes(
requireCredentialViewPermission,
requireDataAccess,
async (req: Request, res: Response) => {
const { privateKey, keyPassword } = req.body;
if (!privateKey || typeof privateKey !== "string") {
if (!req.body?.privateKey || typeof req.body.privateKey !== "string") {
return res.status(400).json({ error: "Private key is required" });
}
try {
const { key: privateKey, passphrase: keyPassword } =
await resolveKeyReferences(
(req as AuthenticatedRequest).userId,
req.body.privateKey,
req.body.keyPassword,
);
let privateKeyObj;
const parseAttempts = [];
+25 -9
View File
@@ -7,7 +7,7 @@ import express, { type Request, type Response } from "express";
import { authLogger } from "../../utils/logger.js";
import { PermissionManager } from "../../utils/permission-manager.js";
import { AuthManager } from "../../utils/auth-manager.js";
import { parseSSHKey } from "../../utils/ssh-key-utils.js";
import { parseKeyForStorage } from "../../hosts/stored-ssh-key.js";
import { registerCredentialKeyRoutes } from "./credential-key-routes.js";
import { registerCredentialDeployRoutes } from "./credential-deploy-routes.js";
import { registerCredentialBulkRoutes } from "./credential-bulk-routes.js";
@@ -165,7 +165,7 @@ router.post(
let keyInfo = null;
if (authType === "key" && plainKey) {
keyInfo = parseSSHKey(plainKey, plainKeyPassword);
keyInfo = parseKeyForStorage(plainKey, plainKeyPassword);
if (!keyInfo.success) {
authLogger.warn("SSH key parsing failed", {
operation: "credential_create",
@@ -192,7 +192,11 @@ router.post(
password: plainPassword,
key: plainKey,
privateKey: keyInfo?.privateKey || plainKey,
publicKey: keyInfo?.publicKey || null,
publicKey:
keyInfo?.publicKey ||
(keyInfo?.reference && typeof req.body?.publicKey === "string"
? req.body.publicKey.trim() || null
: null),
keyPassword: plainKeyPassword,
keyType: keyType || null,
detectedKeyType: keyInfo?.keyType || null,
@@ -502,7 +506,7 @@ router.post(
let keyInfo = null;
if (authType === "key" && plainKey) {
keyInfo = parseSSHKey(plainKey, plainKeyPassword);
keyInfo = parseKeyForStorage(plainKey, plainKeyPassword);
if (!keyInfo.success) {
return res.status(400).json({
error: keyInfo.error
@@ -524,7 +528,11 @@ router.post(
password: authType === "password" ? plainPassword : null,
key: authType === "key" ? plainKey : null,
privateKey: authType === "key" ? keyInfo?.privateKey || plainKey : null,
publicKey: authType === "key" ? keyInfo?.publicKey || null : null,
publicKey:
authType === "key"
? keyInfo?.publicKey ||
(keyInfo?.reference ? source.publicKey || null : null)
: null,
keyPassword: authType === "key" ? plainKeyPassword : null,
keyType: source.keyType,
detectedKeyType: authType === "key" ? keyInfo?.keyType || null : null,
@@ -688,8 +696,14 @@ router.put(
if (updateData.key !== undefined) {
updateFields.key = updateData.key || null;
if (updateData.key && existingCredential.authType === "key") {
const keyInfo = parseSSHKey(updateData.key, updateData.keyPassword);
if (
updateData.key &&
(updateData.authType ?? existingCredential.authType) === "key"
) {
const keyInfo = parseKeyForStorage(
updateData.key,
updateData.keyPassword,
);
if (!keyInfo.success) {
authLogger.warn("SSH key parsing failed during update", {
operation: "credential_update",
@@ -704,8 +718,10 @@ router.put(
});
}
updateFields.privateKey = keyInfo.privateKey;
updateFields.publicKey = keyInfo.publicKey;
updateFields.detectedKeyType = keyInfo.keyType;
updateFields.publicKey = keyInfo.reference
? updateData.publicKey?.trim() || null
: keyInfo.publicKey;
updateFields.detectedKeyType = keyInfo.keyType || null;
}
}
if (updateData.keyPassword !== undefined) {
+3 -3
View File
@@ -11,7 +11,7 @@ import { pluginEvents, TOPICS } from "../../plugins/events.js";
import { AuthManager } from "../../utils/auth-manager.js";
import { PermissionManager } from "../../utils/permission-manager.js";
import { DataCrypto } from "../../utils/data-crypto.js";
import { parseSSHKey } from "../../utils/ssh-key-utils.js";
import { parseKeyForStorage } from "../../hosts/stored-ssh-key.js";
import {
pickResolvedPassword,
pickResolvedUsername,
@@ -358,7 +358,7 @@ router.post(
sshDataObj.keyType = null;
} else if (effectiveAuthType === "key") {
if (key && typeof key === "string") {
const keyValidation = parseSSHKey(
const keyValidation = parseKeyForStorage(
key,
typeof keyPassword === "string" ? keyPassword : undefined,
);
@@ -940,7 +940,7 @@ router.put(
sshDataObj.keyType = null;
} else if (effectiveAuthType === "key") {
if (key && typeof key === "string") {
const keyValidation = parseSSHKey(
const keyValidation = parseKeyForStorage(
key,
typeof keyPassword === "string" ? keyPassword : undefined,
);