Skip to content

Commit

Permalink
add clusters
Browse files Browse the repository at this point in the history
  • Loading branch information
Flaque committed Nov 20, 2024
1 parent 921f627 commit 6176bf8
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 98 deletions.
7 changes: 7 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

80 changes: 40 additions & 40 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
{
"scripts": {
"dev": "IS_DEVELOPMENT_CLI_ENV=true deno run src/index.ts",
"release": "deno run --allow-all src/scripts/release.ts",
"prod": "deno run --allow-all src/index.ts",
"schema": "npx openapi-typescript https://api.sfcompute.com/docs/json -o src/schema.ts"
},
"dependencies": {
"@inquirer/prompts": "^5.1.2",
"@types/ms": "^0.7.34",
"axios": "^1.7.2",
"boxen": "^8.0.1",
"chalk": "^5.3.0",
"chrono-node": "^2.7.6",
"cli-table3": "^0.6.5",
"commander": "^12.1.0",
"dayjs": "^1.11.13",
"dotenv": "^16.4.5",
"ink": "^5.0.1",
"ink-confirm-input": "^2.0.0",
"ink-spinner": "^5.0.0",
"ink-text-input": "^6.0.0",
"inquirer": "^10.1.2",
"ms": "^2.1.3",
"node-fetch": "^3.3.2",
"openapi-fetch": "^0.11.1",
"ora": "^8.1.0",
"parse-duration": "^1.1.0",
"react": "^18.3.1",
"semver": "^7.6.3",
"tiny-invariant": "^1.3.3",
"yaml": "^2.6.1"
},
"devDependencies": {
"@types/semver": "^7.5.8"
},
"peerDependencies": {
"typescript": "^5.6.2"
},
"version": "0.0.70"
}
"scripts": {
"devv": "IS_DEVELOPMENT_CLI_ENV=true deno run --allow-all src/index.ts",
"release": "deno run --allow-all src/scripts/release.ts",
"prod": "deno run --allow-all src/index.ts",
"schema": "npx openapi-typescript https://api.sfcompute.com/docs/json -o src/schema.ts"
},
"dependencies": {
"@inquirer/prompts": "^5.1.2",
"@types/ms": "^0.7.34",
"axios": "^1.7.2",
"boxen": "^8.0.1",
"chalk": "^5.3.0",
"chrono-node": "^2.7.6",
"cli-table3": "^0.6.5",
"commander": "^12.1.0",
"dayjs": "^1.11.13",
"dotenv": "^16.4.5",
"ink": "^5.0.1",
"ink-confirm-input": "^2.0.0",
"ink-spinner": "^5.0.0",
"ink-text-input": "^6.0.0",
"inquirer": "^10.1.2",
"ms": "^2.1.3",
"node-fetch": "^3.3.2",
"openapi-fetch": "^0.11.1",
"ora": "^8.1.0",
"parse-duration": "^1.1.0",
"react": "^18.3.1",
"semver": "^7.6.3",
"tiny-invariant": "^1.3.3",
"yaml": "^2.6.1"
},
"devDependencies": {
"@types/semver": "^7.5.8"
},
"peerDependencies": {
"typescript": "^5.6.2"
},
"version": "0.0.70"
}
6 changes: 4 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { registerSSH } from "./lib/ssh.ts";
import { registerTokens } from "./lib/tokens.ts";
import { registerDown, registerUp } from "./lib/updown.tsx";
import { registerUpgrade } from "./lib/upgrade.ts";
import { registerClusters } from "./lib/clusters/clusters.tsx";
import { checkVersion } from "./checkVersion.ts";

const program = new Command();
Expand All @@ -30,14 +31,15 @@ registerLogin(program);
registerBuy(program);
registerOrders(program);
registerContracts(program);
registerInstances(program);
registerSSH(program);
// registerInstances(program);
// registerSSH(program);
registerSell(program);
registerBalance(program);
registerTokens(program);
registerUpgrade(program);
registerUp(program);
registerDown(program);
registerClusters(program);

// (development commands)
registerDev(program);
Expand Down
73 changes: 17 additions & 56 deletions src/lib/clusters/clusters.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { Command } from "commander";
import { apiClient } from "../../apiClient.ts";
import { logAndQuit } from "../../helpers/errors.ts";
import { generateCSR, generateKeyPair } from "./csr.ts";

import { getKeys } from "./keys.tsx";
export function registerClusters(program: Command) {
const clusters = program
.command("clusters")
Expand All @@ -22,9 +21,12 @@ export function registerClusters(program: Command) {
});
});

// sf clusters users add --cluster <cluster_id> [--user <username>]
clusters
.command("users add")
const users = clusters
.command("users")
.description("Manage cluster users");

users
.command("add")
.description("Add a user to a cluster")
.requiredOption("--cluster <cluster_id>", "ID of the cluster")
.requiredOption("--user <username>", "Username to add")
Expand All @@ -36,9 +38,8 @@ export function registerClusters(program: Command) {
});
});

// sf clusters users rm <id>
clusters
.command("users rm <id>")
users
.command("rm <id>")
.description("Remove a user from a cluster")
.option("--json", "Output in JSON format")
.action(async (id, options) => {
Expand All @@ -47,9 +48,9 @@ export function registerClusters(program: Command) {
});
});

// sf clusters users ls|list
clusters
.command("users list")
users
.command("list")
.alias("ls")
.description("List users in a cluster")
.option("--json", "Output in JSON format")
.action(async (options) => {
Expand Down Expand Up @@ -80,41 +81,6 @@ async function listClustersAction({ returnJson }: { returnJson?: boolean }) {
}
}

async function saveKeyAndCrtToFile({
privateKey,
crt,
username,
}: {
privateKey: string;
crt: string;
username: string;
}) {
// Save keys to ~/.sfcompute/keys directory
const homeDir = Deno.env.get("HOME");
if (!homeDir) {
return logAndQuit("Could not determine home directory, please set HOME environment variable");
}

const keyDir = `${homeDir}/.sfcompute/keys`;
const keyPrefix = username;

try {
// Create keys directory if it doesn't exist
await Deno.mkdir(keyDir, { recursive: true });

// Save private key with restricted permissions
const keyPath = `${keyDir}/${keyPrefix}.key`;
await Deno.writeTextFile(keyPath, privateKey);
await Deno.chmod(keyPath, 0o600);

// Save public key
const certPath = `${keyDir}/${keyPrefix}.crt`;
await Deno.writeTextFile(certPath, crt);
} catch (err) {
return logAndQuit(`Failed to save keys: ${err}`);
}
}

async function addClusterUserAction({
clusterId,
username,
Expand All @@ -130,19 +96,12 @@ async function addClusterUserAction({
);
}

const { privateKey, publicKey } = await generateKeyPair();
const csr = generateCSR(privateKey, username, username);

await saveKeyAndCrtToFile({
privateKey,
crt: publicKey,
username,
});
const { publicKey } = await getKeys();

const { data, error, response } = await api.POST("/v0/credentials", {
body: {
object: "k8s_credential",
csr,
pubkey: publicKey,
username,
cluster_id: clusterId,
}
Expand Down Expand Up @@ -203,5 +162,7 @@ async function listClusterUsersAction({ returnJson }: { returnJson?: boolean })
);
}

console.log(data);
for (const item of data.data) {
console.log(item);
}
}
90 changes: 90 additions & 0 deletions src/lib/clusters/keys.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import * as crypto from "node:crypto";
import * as path from "node:path";
import * as os from "node:os";

export async function getKeys(): Promise<{ publicKey: string; privateKey: string }> {
const keys = await loadKeys();
if (keys && typeof keys.privateKey === 'string' && typeof keys.publicKey === 'string') {
return {
publicKey: keys.publicKey,
privateKey: keys.privateKey
};
}
const newKeys = generateKeyPair();
console.error("generating new keys")
await saveKeys(newKeys);
return newKeys;
}

function generateKeyPair() {
// Generate RSA key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});

return {
publicKey,
privateKey,
};
}

export function decryptSecret(secret: string, privateKey: string) {
const decrypted = crypto.privateDecrypt({
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PADDING,
}, secret);
return decrypted;
}

async function saveKeys(keys: { publicKey: string; privateKey: string }) {
const { publicKey, privateKey } = keys;
const publicKeyPath = path.join(os.homedir(), ".sf", "public.pem");
const privateKeyPath = path.join(os.homedir(), ".sf", "private.pem");

try {
// Create .sf directory if it doesn't exist
await Deno.mkdir(path.dirname(publicKeyPath), { recursive: true });

// Write keys to files
await Deno.writeTextFile(publicKeyPath, publicKey);
await Deno.writeTextFile(privateKeyPath, privateKey);

// Set private key permissions to be readable only by owner
await Deno.chmod(privateKeyPath, 0o600);
} catch (err) {
throw new Error(`Failed to store keys: ${err}`);
}
}

async function loadKeys() {
const publicKeyPath = path.join(os.homedir(), ".sf", "public.pem");
const privateKeyPath = path.join(os.homedir(), ".sf", "private.pem");

let publicKey = null;
let privateKey = null;

try {
publicKey = await Deno.readTextFile(publicKeyPath);
} catch (err) {
// Leave publicKey as null if read fails
}

try {
privateKey = await Deno.readTextFile(privateKeyPath);
} catch (err) {
// Leave privateKey as null if read fails
}

return {
publicKey,
privateKey,
};
}

0 comments on commit 6176bf8

Please sign in to comment.