feat: add orphan EC2 cleanup on startup, fix SetupWizard routing, fix SSH key gen
- On startup, scan for EC2 instances with pp:managed=true and terminate any orphans - SetupWizard now renders as full-page (no Layout wrapper) - Fixed SSH RSA key generation (generateKeyPairSync with spki format) - Removed unused import in orphanCleanup.ts - Frontend rebuild with App.tsx routing fix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+44
-46
@@ -12,7 +12,7 @@ import {
|
||||
CreateTagsCommand,
|
||||
} from "@aws-sdk/client-ec2";
|
||||
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
|
||||
import { generateKeyPairSync } from "crypto";
|
||||
import { generateKeyPairSync, createPublicKey } from "crypto";
|
||||
import { createLogger } from "../lib/logger";
|
||||
import { decrypt } from "../lib/encryption";
|
||||
import type { User } from "@prisma/client";
|
||||
@@ -68,46 +68,36 @@ export async function validateAwsCredentials(user: User): Promise<{ success: boo
|
||||
}
|
||||
}
|
||||
|
||||
export function generateSshKeyPair(): { privateKey: string; publicKey: string } {
|
||||
const { privateKey, publicKey } = generateKeyPairSync("rsa", {
|
||||
export function generateSshKeyPair(): { privateKey: string; publicKeyOpenssh: string } {
|
||||
const { privateKey: privKeyPem, publicKey: pubKeyPem } = generateKeyPairSync("rsa", {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: { type: "pkcs1", format: "pem" },
|
||||
publicKeyEncoding: { type: "spki", format: "pem" },
|
||||
privateKeyEncoding: { type: "pkcs1", format: "pem" },
|
||||
});
|
||||
const pubKeyOpenSsh = rsaPemToOpenSsh(publicKey);
|
||||
return { privateKey, publicKey: pubKeyOpenSsh };
|
||||
}
|
||||
|
||||
function rsaPemToOpenSsh(pem: string): string {
|
||||
const { publicKeyEncoding } = generateKeyPairSync("rsa", {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: { type: "pkcs8", format: "pem" },
|
||||
privateKeyEncoding: { type: "pkcs8", format: "pem" },
|
||||
});
|
||||
void publicKeyEncoding;
|
||||
const der = Buffer.from(
|
||||
pem.replace(/-----BEGIN RSA PUBLIC KEY-----/, "")
|
||||
.replace(/-----END RSA PUBLIC KEY-----/, "")
|
||||
.replace(/\n/g, ""),
|
||||
"base64"
|
||||
);
|
||||
const type = Buffer.from("ssh-rsa");
|
||||
function encodeBuffer(buf: Buffer): Buffer {
|
||||
const len = Buffer.allocUnsafe(4);
|
||||
len.writeUInt32BE(buf.length, 0);
|
||||
return Buffer.concat([len, buf]);
|
||||
const pubKeyObj = createPublicKey(pubKeyPem);
|
||||
const pubKeyDer = pubKeyObj.export({ type: "spki", format: "der" });
|
||||
|
||||
function sshEncodeBuffer(buf: Buffer): Buffer {
|
||||
const lenBuf = Buffer.allocUnsafe(4);
|
||||
lenBuf.writeUInt32BE(buf.length, 0);
|
||||
return Buffer.concat([lenBuf, buf]);
|
||||
}
|
||||
const typeEncoded = encodeBuffer(type);
|
||||
const rsaKeyData = der;
|
||||
const base64Key = Buffer.concat([typeEncoded, rsaKeyData]).toString("base64");
|
||||
return `ssh-rsa ${base64Key} pp-generated`;
|
||||
|
||||
const keyTypeStr = Buffer.from("ssh-rsa");
|
||||
const keyTypeEncoded = sshEncodeBuffer(keyTypeStr);
|
||||
|
||||
const base64Encoded = pubKeyDer.toString("base64");
|
||||
const openSshKey = `ssh-rsa ${Buffer.concat([keyTypeEncoded]).toString("base64")} pp-generated`;
|
||||
|
||||
return { privateKey: privKeyPem, publicKeyOpenssh: `ssh-rsa ${base64Encoded} pp-generated` };
|
||||
}
|
||||
|
||||
export async function generateAndImportKeyPair(ec2: EC2Client, keyName: string): Promise<{ privateKey: string }> {
|
||||
const { privateKey, publicKey } = generateSshKeyPair();
|
||||
const { privateKey, publicKeyOpenssh } = generateSshKeyPair();
|
||||
await ec2.send(new ImportKeyPairCommand({
|
||||
KeyName: keyName,
|
||||
PublicKeyMaterial: Buffer.from(publicKey),
|
||||
PublicKeyMaterial: Buffer.from(publicKeyOpenssh),
|
||||
}));
|
||||
return { privateKey };
|
||||
}
|
||||
@@ -126,22 +116,26 @@ export async function createPreviewSecurityGroup(ec2: EC2Client, groupName: stri
|
||||
}));
|
||||
const groupId = res.GroupId!;
|
||||
|
||||
const ingress: any[] = [
|
||||
{
|
||||
IpProtocol: "tcp",
|
||||
FromPort: 22,
|
||||
ToPort: 22,
|
||||
IpRanges: [{ CidrIp: "0.0.0.0/0" }],
|
||||
},
|
||||
];
|
||||
if (port !== 22) {
|
||||
ingress.push({
|
||||
IpProtocol: "tcp",
|
||||
FromPort: port,
|
||||
ToPort: port,
|
||||
IpRanges: [{ CidrIp: "0.0.0.0/0" }],
|
||||
});
|
||||
}
|
||||
|
||||
await ec2.send(new AuthorizeSecurityGroupIngressCommand({
|
||||
GroupId: groupId,
|
||||
IpPermissions: [
|
||||
{
|
||||
IpProtocol: "tcp",
|
||||
FromPort: 22,
|
||||
ToPort: 22,
|
||||
IpRanges: [{ CidrIp: "0.0.0.0/0" }],
|
||||
},
|
||||
{
|
||||
IpProtocol: "tcp",
|
||||
FromPort: port,
|
||||
ToPort: port,
|
||||
IpRanges: [{ CidrIp: "0.0.0.0/0" }],
|
||||
},
|
||||
],
|
||||
IpPermissions: ingress,
|
||||
}));
|
||||
return groupId;
|
||||
}
|
||||
@@ -223,6 +217,7 @@ export async function deleteSecurityGroupAws(ec2: EC2Client, groupName: string):
|
||||
}));
|
||||
const groupId = describe.SecurityGroups?.[0]?.GroupId;
|
||||
if (groupId) {
|
||||
await sleep(5000);
|
||||
await ec2.send(new DeleteSecurityGroupCommand({ GroupId: groupId }));
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -232,7 +227,10 @@ export async function deleteSecurityGroupAws(ec2: EC2Client, groupName: string):
|
||||
|
||||
export async function describeAllManagedInstances(ec2: EC2Client): Promise<any[]> {
|
||||
const res = await ec2.send(new DescribeInstancesCommand({
|
||||
Filters: [{ Name: "tag:pp:managed", Values: ["true"] }, { Name: "instance-state-name", Values: ["running", "pending", "stopping", "stopped"] }],
|
||||
Filters: [
|
||||
{ Name: "tag:pp:managed", Values: ["true"] },
|
||||
{ Name: "instance-state-name", Values: ["running", "pending", "stopping"] },
|
||||
],
|
||||
}));
|
||||
return (res.Reservations ?? []).flatMap(r => r.Instances ?? []);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user