Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/secrets.env.encrypted
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"k": "ct",
"c": "mBbKV<xz1b;lL$qXaZ1eEazgy12v{5BvnND87}b_mbG58wOs>0G4)-8UdG=fVK^Mgf0{+={6c)WxWPnZ(Cp1Iq=%3}nK4a)CGBx1bjZ=qv8EHV9gp30vQ`13OJxFK&vBuwpyEt$JE{*iks{9CMl@IGpcvQ8gA7RNsgU>j6~vXw+Ir0WKXxrDu|PrlDk@Z@tYC<k#21dU5I!B7JhCdj#Oah$5;B@;`J!gjCaT!(>11HTq*7Ufq{V_GxAc=rh+gkkDsiM%LjPyK-fvut?5Fwf*i8>YcD<c1kk(FP_6PM@GEq&lP0STP$!tjzkO6$$`4{%3v{sgVk+M~*j#Iic)1CkbkbIYoay}^IRAz!t<tG2uEdpeU*9F(1a9p3RcFU*5pxM?C&6}LH0u<=<>-HELZe^)cc$8aXF;l#4*srYmmdO$ydq-KxbwECzD%{l$#IQh90%*!8$=`lK`!aea=QOICK(5JGRkL1<_eHdY9z1_J8<51L=N29rCD=uup}QKsFs2>$S)|D9?w4r;l>ovY*rGAxmlR14{QFjmRFQP!4d#Q6k6p=#uO(tNpb(^yl7XC+h4xCXFq(gy*@DmrEK>}gF7fQx{^)m{aV4gA&KH|wO)rikb|jF_K@5|f+NpJ*%)`>xuKl=uSaMF4p!PA;EzE4?hdtPnd+5Y4Dk|UC7@wAs%G|M~f`LP3K*__-N~wag5|>u|aHGqB{mnyWcVLsKU&RUA*PLl#znp61^`!uKxq8~0fpsZi%tn;yFzj>-vHH*Zndd0d(|vjSN7XFO6f$aN3~2jD)GOi}6EHaE^o(dM0T6=5c!rBY7_9zJzpt74(wmd&;*o}70+0^HUc9)3GP7-s0il063@(-@;mcA;%pp%{`q+J}@ZQ>Y7i|Que0zv2o~nuk#%2pe^E0UN;J&qv)igfU!(!JHBVN`&s8VknlfK+YI`d6Ex(qIR)))}{j{8ZssNpWrUUpL%CGhk)6bjn`YMP@j&!4b)o5EHb&x(EwT5Re@jO4IK&ZSSQ50Gn`=uvGmBQ_N|c=l+SXHc{Mwe^hT_DK)zCcCW;$(YGqnzo9>AZz%W3Dp9wW9mjKd*NrGw;>G4_yU_>0^X1Ol6KHcL#1|MY;|SC5Hwu<&vtJ>^t3onC{{VkhX",
"ob": null,
"bf": null,
"hm": null,
"i": {
"t": "ci_secrets",
"c": "value"
},
"v": 2
}
35 changes: 23 additions & 12 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,41 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-test

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: scripts/package-lock.json

- name: Install decrypt dependencies
working-directory: scripts
run: npm ci

- name: Decrypt secrets
env:
CS_CLIENT_KEY: ${{ secrets.CS_VAULT_CLIENT_KEY }}
CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_VAULT_CLIENT_ACCESS_KEY }}
CS_WORKSPACE_CRN: ${{ secrets.CS_VAULT_WORKSPACE_CRN }}
CS_CLIENT_ID: ${{ secrets.CS_VAULT_CLIENT_ID }}
run: cd scripts && npm run decrypt

- run: |
mise run postgres:up --extra-args "--detach --wait"

- name: Run benchmark
working-directory: tests/benchmark
env:
CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_CLIENT_ACCESS_KEY }}
CS_DEFAULT_KEYSET_ID: ${{ secrets.CS_DEFAULT_KEYSET_ID }}
CS_CLIENT_ID: ${{ secrets.CS_CLIENT_ID }}
CS_CLIENT_KEY: ${{ secrets.CS_CLIENT_KEY }}
CS_WORKSPACE_CRN: ${{ secrets.CS_WORKSPACE_CRN }}
RUST_BACKTRACE: "1"
run: mise run benchmark:continuous

# Download previous benchmark result from cache (if exists)
- name: Download previous benchmark data
uses: actions/cache@v4
with:
path: ./cache
key: ${{ runner.os }}-benchmark

# Run `github-action-benchmark` action
- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1
Expand All @@ -56,10 +73,4 @@ jobs:
comment-on-alert: true
summary-always: true
auto-push: true
benchmark-data-dir-path: docs

- uses: ./.github/actions/send-slack-notification
with:
channel: engineering
webhook_url: ${{ secrets.SLACK_NOTIFICATION_WEBHOOK_URL }}

benchmark-data-dir-path: docs
20 changes: 19 additions & 1 deletion .github/workflows/release-aws-marketplace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ jobs:

- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: scripts/package-lock.json

- name: Install decrypt dependencies
working-directory: scripts
run: npm ci

- name: Decrypt secrets
env:
CS_CLIENT_KEY: ${{ secrets.CS_VAULT_CLIENT_KEY }}
CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_VAULT_CLIENT_ACCESS_KEY }}
CS_WORKSPACE_CRN: ${{ secrets.CS_VAULT_WORKSPACE_CRN }}
CS_CLIENT_ID: ${{ secrets.CS_VAULT_CLIENT_ID }}
run: cd scripts && npm run decrypt

- uses: jdx/mise-action@v2
with:
version: 2025.1.6 # [default: latest] mise version to install
Expand Down Expand Up @@ -111,6 +129,6 @@ jobs:
--fail-with-body \
--url "https://api.developer.multitudes.co/deployments" \
--header "Content-Type: application/json" \
--header "Authorization: ${{ secrets.MULTITUDES_ACCESS_TOKEN }}" \
--header "Authorization: ${{ env.MULTITUDES_ACCESS_TOKEN }}" \
--data '{"commitSha": "${{ github.sha }}", "environmentName":"marketplace"}'

47 changes: 42 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@ jobs:
runs-on: ${{matrix.build.os}}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: scripts/package-lock.json

- name: Install decrypt dependencies
working-directory: scripts
run: npm ci

- name: Decrypt secrets
env:
CS_CLIENT_KEY: ${{ secrets.CS_VAULT_CLIENT_KEY }}
CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_VAULT_CLIENT_ACCESS_KEY }}
run: cd scripts && npm run decrypt

- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
if: github.event_name == 'pull_request' # only cache in pull requests
Expand Down Expand Up @@ -55,8 +72,8 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PERSONAL_ACCESS_TOKEN }}
username: ${{ env.DOCKER_HUB_USERNAME }}
password: ${{ env.DOCKER_HUB_PASSWORD }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
Expand Down Expand Up @@ -92,6 +109,26 @@ jobs:
needs:
- build
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: scripts/package-lock.json

- name: Install decrypt dependencies
working-directory: scripts
run: npm ci

- name: Decrypt secrets
env:
CS_CLIENT_KEY: ${{ secrets.CS_VAULT_CLIENT_KEY }}
CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_VAULT_CLIENT_ACCESS_KEY }}
CS_WORKSPACE_CRN: ${{ secrets.CS_VAULT_WORKSPACE_CRN }}
CS_CLIENT_ID: ${{ secrets.CS_VAULT_CLIENT_ID }}
run: cd scripts && npm run decrypt

- name: Download digests
uses: actions/download-artifact@v4
with:
Expand All @@ -102,8 +139,8 @@ jobs:
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_PERSONAL_ACCESS_TOKEN }}
username: ${{ env.DOCKER_HUB_USERNAME }}
password: ${{ env.DOCKER_HUB_PASSWORD }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
Expand Down Expand Up @@ -135,5 +172,5 @@ jobs:
--fail-with-body \
--url "https://api.developer.multitudes.co/deployments" \
--header "Content-Type: application/json" \
--header "Authorization: ${{ secrets.MULTITUDES_ACCESS_TOKEN }}" \
--header "Authorization: ${{ env.MULTITUDES_ACCESS_TOKEN }}" \
--data '{"commitSha": "${{ github.sha }}", "environmentName":"dockerhub"}'
31 changes: 14 additions & 17 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,25 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-test

- name: Decrypt secrets
uses: cipherstash/protectgh@main
with:
secrets-file: .github/secrets.env.encrypted
env:
CS_CLIENT_ID: ${{ secrets.CS_VAULT_CLIENT_ID }}
CS_CLIENT_KEY: ${{ secrets.CS_VAULT_CLIENT_KEY }}
CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_VAULT_CLIENT_ACCESS_KEY }}
CS_WORKSPACE_CRN: ${{ secrets.CS_VAULT_WORKSPACE_CRN }}

- run: |
mise run postgres:up --extra-args "--detach --wait"
- env:

- name: Run tests
env:
# REMEMBER TO ADD ENVIRONMENT VARIABLES TO tests/docker-compose.yml
# The tests/docker-compose.yml config passes the ENV vars into the container
CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_CLIENT_ACCESS_KEY }}
CS_DEFAULT_KEYSET_ID: ${{ secrets.CS_DEFAULT_KEYSET_ID }}
CS_TENANT_KEYSET_ID_1: ${{ secrets.CS_TENANT_KEYSET_ID_1 }}
CS_TENANT_KEYSET_ID_2: ${{ secrets.CS_TENANT_KEYSET_ID_2 }}
CS_TENANT_KEYSET_ID_3: ${{ secrets.CS_TENANT_KEYSET_ID_3 }}
CS_TENANT_KEYSET_NAME_1: ${{ secrets.CS_TENANT_KEYSET_NAME_1 }}
CS_TENANT_KEYSET_NAME_2: ${{ secrets.CS_TENANT_KEYSET_NAME_2 }}
CS_TENANT_KEYSET_NAME_3: ${{ secrets.CS_TENANT_KEYSET_NAME_3 }}
CS_CLIENT_ID: ${{ secrets.CS_CLIENT_ID }}
CS_CLIENT_KEY: ${{ secrets.CS_CLIENT_KEY }}
CS_WORKSPACE_CRN: ${{ secrets.CS_WORKSPACE_CRN }}
RUST_BACKTRACE: "1"
run: |
mise run --output prefix test

- uses: ./.github/actions/send-slack-notification
with:
channel: engineering
webhook_url: ${{ secrets.SLACK_NOTIFICATION_WEBHOOK_URL }}

6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ rust-toolchain.toml
# credentials for local dev
.env.proxy.docker

# CI secrets plaintext (never commit actual values)
.github/secrets.env.plaintext

# Node modules (scripts directory)
scripts/node_modules/

## benchmark result data
tests/benchmark/results/*.csv
tests/benchmark/benchmark-*.png
Expand Down
93 changes: 93 additions & 0 deletions scripts/decrypt-secrets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { protect, csTable, csColumn, Encrypted } from "@cipherstash/protect";
import * as fs from "fs";
import * as path from "path";
import * as dotenv from "dotenv";

const schema = csTable("ci_secrets", {
value: csColumn("value"),
});

function isFileMode(data: unknown): data is Encrypted {
return data !== null && typeof data === "object" && "k" in data && "c" in data;
}

async function main(): Promise<void> {
const repoRoot = path.resolve(import.meta.dirname, "..");
const encryptedPath = path.join(repoRoot, ".github", "secrets.env.encrypted");

if (!fs.existsSync(encryptedPath)) {
console.error(`Error: ${encryptedPath} not found`);
process.exit(1);
}

const client = await protect({ schemas: [schema] });
const encrypted: unknown = JSON.parse(
fs.readFileSync(encryptedPath, "utf-8")
);

let env: Record<string, string>;

if (isFileMode(encrypted)) {
// File mode: decrypt single blob, parse as .env
const result = await client.decrypt(encrypted);
if (result.failure) {
console.error(`Failed to decrypt: ${result.failure.message}`);
process.exit(1);
}
env = dotenv.parse(String(result.data));
console.error("Detected file mode encryption");
} else {
// Vars mode: decrypt each variable individually
env = {};
const encryptedVars = encrypted as Record<string, Encrypted>;
for (const [key, payload] of Object.entries(encryptedVars)) {
const result = await client.decrypt(payload);
if (result.failure) {
console.error(`Failed to decrypt ${key}: ${result.failure.message}`);
process.exit(1);
}
env[key] = String(result.data);
}
console.error(`Detected vars mode encryption (${Object.keys(env).length} variables)`);
}
const githubEnvPath = process.env.GITHUB_ENV;
const isCI = !!githubEnvPath;

// Bootstrap secrets (passed in as env vars, need to forward to $GITHUB_ENV)
const bootstrapSecrets = [
"CS_CLIENT_ID",
"CS_CLIENT_KEY",
"CS_CLIENT_ACCESS_KEY",
"CS_WORKSPACE_CRN",
];

// Combine bootstrap secrets with decrypted secrets
const allSecrets: Record<string, string> = { ...env };
for (const key of bootstrapSecrets) {
const value = process.env[key];
if (value) {
allSecrets[key] = value;
}
}

for (const [key, value] of Object.entries(allSecrets)) {
if (isCI) {
const delimiter = `EOF_${key}_${Date.now()}`;
fs.appendFileSync(githubEnvPath, `${key}<<${delimiter}\n${value}\n${delimiter}\n`);
} else {
// Only output non-bootstrap secrets locally (bootstrap are already in env)
if (!bootstrapSecrets.includes(key)) {
console.log(`${key}=${value}`);
}
}
}

if (isCI) {
console.error(`Wrote ${Object.keys(allSecrets).length} secrets to $GITHUB_ENV`);
}
}

main().catch((err) => {
console.error("Decryption failed:", err);
process.exit(1);
});
Loading