Skip to content

Commit

Permalink
feat: add container signing
Browse files Browse the repository at this point in the history
  • Loading branch information
xynydev committed Apr 17, 2024
1 parent 8b88c75 commit 2bd084e
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 18 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"arctic": "^1.5.0",
"bits-ui": "^0.21.2",
"clsx": "^2.1.0",
"libsodium-wrappers": "^0.7.13",
"lucide-svelte": "^0.368.0",
"mode-watcher": "^0.3.0",
"svelte-sonner": "^0.3.22",
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

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

4 changes: 2 additions & 2 deletions src/routes/api/github/createRepo/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export async function POST({ request, cookies }): Promise<Response> {
}
);
if (!readmeUpdate.ok) {
log("Updating README failed: " + JSON.stringify(readme));
log("Updating README failed: " + JSON.stringify(readmeUpdate));
} else {
log("Updated README successfully!");
}
Expand Down Expand Up @@ -80,7 +80,7 @@ export async function POST({ request, cookies }): Promise<Response> {
}
);
if (!recipeUpdate.ok) {
log("Updating recipe failed: " + JSON.stringify(recipe));
log("Updating recipe failed: " + JSON.stringify(recipeUpdate));
} else {
log("Updated recipe successfully!");
}
Expand Down
74 changes: 74 additions & 0 deletions src/routes/api/github/setupCosign/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { ghApiGet, ghApiPost, ghApiPut } from "$lib/ts/github/api";
import { createLogStream } from "$lib/ts/misc/logStream";
import type { Endpoints } from "@octokit/types";
import _sodium from "libsodium-wrappers";

export async function POST({ request, cookies }): Promise<Response> {
return await createLogStream(async (log) => {
const token = cookies.get("github_oauth_token");
if (token === undefined) throw new Error("Not logged in");
const { login, name, cosignPrivateKey, cosignPublicKey } = await request.json();

// Private key
log("Setting up sodium for encrypting private key for transit...");
await _sodium.ready;
const sodium = _sodium;

log("Fetching repository public key to encrypt private key for transit...");
const pubKey = await ghApiGet(token, `/repos/${login}/${name}/actions/secrets/public-key`);
if (!pubKey.ok) {
log("Fetching public key failed: " + JSON.stringify(pubKey));
} else {
log("Fetched public key successfully!");
}
const pubKeyData =
pubKey.data as Endpoints["GET /repos/{owner}/{repo}/actions/secrets/public-key"]["response"]["data"];

log("Encrypting private key for transit...");
const binRepoPubKey = sodium.from_base64(pubKeyData.key, sodium.base64_variants.ORIGINAL);
const binPrivateKey = sodium.from_string(cosignPrivateKey);
const encBytes = sodium.crypto_box_seal(binPrivateKey, binRepoPubKey);
const encPrivateKey = sodium.to_base64(encBytes, sodium.base64_variants.ORIGINAL);
log("Private signing key encrypted successfully!");

log("Setting SIGNING_SECRET secret...");
const signingSecret = await ghApiPut(
token,
`/repos/${login}/${name}/actions/secrets/SIGNING_SECRET`,
{
encrypted_value: encPrivateKey,
key_id: pubKeyData.key_id
}
);
if (!signingSecret.ok) {
log("Setting signing secret failed: " + JSON.stringify(signingSecret));
} else {
log("Set signing secret successfully!");
}

// Public key
log("Fetching template cosign.pub...");
const oldPubKey = await ghApiGet(token, `/repos/${login}/${name}/contents/cosign.pub`);
if (!oldPubKey.ok) {
log("Fetching old public key failed: " + JSON.stringify(oldPubKey));
} else {
log("Fetched old public key successfully!");
}
const oldPubKeyData =
oldPubKey.data as Endpoints["GET /repos/{owner}/{repo}/contents/{path}"]["response"]["data"];

log("Updating cosign.pub...");
const pubKeyUpdate = await ghApiPut(token, `/repos/${login}/${name}/contents/cosign.pub`, {
message: "chore(automatic): new cosign keys",
content: btoa(cosignPublicKey),
sha: oldPubKeyData.sha
});
if (!pubKeyUpdate.ok) {
log("Updating public key failed: " + JSON.stringify(pubKeyUpdate));
} else {
log("Updated public key successfully!");
}

log("Cosign setup DONE!");
});
}
105 changes: 89 additions & 16 deletions src/routes/new/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
let log: Array<string> = [];
export let data;
let repoName = document.getElementById("reponame");
async function createRepo() {
setupStep = "inprogress";
const res = await fetch("/api/github/createRepo", {
method: "POST",
body: JSON.stringify({
name: document.getElementById("reponame").value,
name: repoName,
login: data.githubUser.login
}),
headers: {
Expand All @@ -32,20 +34,59 @@
});
}
function generateCosignKeys() {
async function setupCosign() {
setupStep = "inprogress";
log = [...log, "Generating cosign keys..."];
const keys = await generateCosignKeys();
log = [...log, "Generated cosign keys, sending to serverless backend..."];
const res = await fetch("/api/github/setupCosign", {
method: "POST",
body: JSON.stringify({
name: repoName,
login: data.githubUser.login,
cosignPrivateKey: keys.cosignPrivateKey,
cosignPublicKey: keys.cosignPublicKey
}),
headers: {
"content-type": "application/json"
}
});
readLogStream(res, (value) => {
log = [...log, value];
if (value.includes("DONE!")) {
setupStep = "done";
}
});
}
async function generateCosignKeys(): Promise<{
cosignPublicKey: string;
cosignPrivateKey: string;
}> {
// @ts-ignore
const go = new Go();
WebAssembly.instantiateStreaming(fetch("/cosign.wasm"), go.importObject).then(
async (obj) => {
const wasm = obj.instance;
go.run(wasm);
// @ts-ignore
console.log(cosignPublicKey);
// @ts-ignore
console.log(cosignPrivateKey);
}
);
const obj = await WebAssembly.instantiateStreaming(fetch("/cosign.wasm"), go.importObject);
const wasm = obj.instance;
go.run(wasm);
// The keys are set as global variables
const returnObject = {
// @ts-ignore
cosignPublicKey: window.cosignPublicKey,
// @ts-ignore
cosignPrivateKey: window.cosignPrivateKey
};
// Clear the keys from global variables for shallow security reasons
// @ts-ignore
window.cosignPublicKey = undefined;
// @ts-ignore
window.cosignPrivateKey = undefined;
return returnObject;
}
</script>

Expand Down Expand Up @@ -83,18 +124,50 @@
placeholder="weird-os"
class="font-mono"
required
on:change={(e) => (repoName = e.target?.value)}
></Input>
<Button on:click={createRepo} size="lg" variant="default" class="mt-6">
Create repository
</Button>
{:else if setupStep === "inprogress"}
<Progress value={log.length} max={10} class="mb-6" />
<Log {log} />
<Progress value={log.length} max={25} class="mb-6" />
{:else if setupStep === "cosign"}
Set up container signing
<Log {log} />
<h3 class="text-lg">How do you want to set up container signing?</h3>
<p class="text-sm">
Container signing is used to verify the authenticity of the custom image. It is
important not to expose the cosign keys to third parties. BlueBuild can set
these up automatically for you. The keys will be generated in your browser and
transmitted over HTTPS to GitHub. If you do not trust BlueBuild to do this, you
can skip it for now and do it manually instead.
</p>

<div class="mt-6 flex w-full flex-row flex-wrap justify-between">
<Button
on:click={() => {
setupStep = "done";
}}
size="lg"
variant="ghost"
>
Skip
</Button>
<Button on:click={setupCosign} size="lg" variant="default">
Set keys cosign automatically
</Button>
</div>
{:else}
Done!
<Button
href="https://github.com/{data.githubUser.login}/{repoName}"
size="lg"
variant="default"
class="mt-6"
>
Open your repository ({data.githubUser.login}/{repoName})
</Button>
{/if}
{#if setupStep !== "start"}
<Log {log} />
{/if}
</Card.Content>
</Card.Root>
Expand Down

0 comments on commit 2bd084e

Please sign in to comment.