add secrets #1

Merged
jamie merged 30 commits from feature/add-secrets into main 2025-02-27 14:16:41 +00:00
13 changed files with 757 additions and 54 deletions
Showing only changes of commit 134c1ee801 - Show all commits

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
/node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

View File

@ -24,57 +24,5 @@ inputs:
required: true
runs:
using: "composite"
steps:
- name: Configure Bitwarden Server
shell: sh
run: bw config server ${{ inputs.server }}
- name: Unlock Vault
shell: sh
run: |
# Ensure Bitwarden is logged in
if ! bw login --check; then
bw login --apikey
fi
# Unlock the vault and store the session key
BW_SESSION=$(bw unlock "${{ inputs.password }}" --raw)
# Verify if BW_SESSION is set correctly
if [ -n "$BW_SESSION" ]; then
echo "BW_SESSION=$BW_SESSION" >> "$GITHUB_ENV"
export BW_SESSION
echo "✅ Vault unlocked successfully!"
else
echo "❌ Failed to unlock Bitwarden vault"
exit 1
fi
env:
BW_CLIENTID: ${{ inputs.client-id }}
BW_CLIENTSECRET: ${{ inputs.client-secret }}
- name: Retrieve Requested Secrets
shell: sh
run: |
INPUT_SECRETS=$(echo "${{ inputs.secrets }}" | tr "\n" ",")
OLDIFS="$IFS"
IFS=","
set -- "$INPUT_SECRETS"
IFS="$OLDIFS"
for pair in "$@"; do
SECRET_ID=$(echo "$pair" | cut -d">" -f1 | xargs)
ENV_VAR=$(echo "$pair" | cut -d">" -f2 | xargs)
echo "Retrieving secret: $SECRET_ID"
SECRET_VALUE=$(bw get notes "$SECRET_ID" --session "$BW_SESSION")
if [ -n "$SECRET_VALUE" ]; then
echo "$ENV_VAR=$SECRET_VALUE" >> "$GITHUB_ENV"
echo "✅ Stored $SECRET_ID in $ENV_VAR"
else
echo "❌ Failed to retrieve secret: $SECRET_ID"
fi
done
using: "node20"
main: "dist/index.js"

4
dist/index.js vendored Normal file
View File

@ -0,0 +1,4 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const main_1 = require("./main");
(0, main_1.run)();

101
dist/main.js vendored Normal file
View File

@ -0,0 +1,101 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.run = run;
const core = __importStar(require("@actions/core"));
const sdk_napi_1 = require("@bitwarden/sdk-napi");
const parser_1 = require("./parser");
async function run() {
try {
const inputs = readInputs();
core.info("🔑 Logging into Bitwarden...");
const client = await getBitwardenClient(inputs);
const secretInputs = (0, parser_1.parseSecretInput)(inputs.secrets);
core.info("🔍 Retrieving secrets...");
await retrieveAndSetSecrets(client, secretInputs);
core.info("✅ Successfully retrieved and set secrets!");
}
catch (error) {
core.setFailed(`❌ Error: ${error instanceof Error ? error.message : error}`);
}
}
function readInputs() {
const clientId = core.getInput("client-id", { required: true });
const clientSecret = core.getInput("client-secret", { required: true });
const password = core.getInput("password", { required: true });
const server = core.getInput("server", { required: true }); // Je eigen Bitwarden server
const secretsRaw = core.getInput("secrets", { required: true });
const secrets = secretsRaw
.split("\n")
.map((s) => s.trim())
.filter((s) => s.includes(">"));
if (secrets.length === 0) {
throw new Error("No valid secrets provided.");
}
return { clientId, clientSecret, password, server, secrets };
}
async function retrieveAndSetSecrets(client, secretsInput) {
const secretIds = secretsInput.map((secret) => secret.id);
const secretResponse = await client.secrets().getByIds(secretIds);
if (secretResponse.success && secretResponse.data) {
const fetchedSecrets = secretResponse.data.data;
fetchedSecrets.forEach((secret) => {
const secretInput = secretsInput.find((input) => input.id === secret.id);
if (secretInput) {
core.setSecret(secret.value);
core.exportVariable(secretInput.outputEnvName, secret.value);
core.setOutput(secretInput.outputEnvName, secret.value);
}
});
}
else {
throw new Error(`The secrets provided could not be found. Please check the machine account has access to the secret UUIDs provided.\nError: ${secretResponse.errorMessage}`);
}
core.info("✅ Completed setting secrets as environment variables.");
}
async function getBitwardenClient(inputs) {
const settings = {
identityUrl: `${inputs.server}/identity`,
apiUrl: `${inputs.server}/api`,
userAgent: "actions/warden",
deviceType: sdk_napi_1.DeviceType.SDK,
};
const client = new sdk_napi_1.BitwardenClient(settings, 2 /* LogLevel.Info */);
const result = await client.loginWithAccessToken(inputs.clientSecret);
if (!result.success) {
throw Error(`Authentication with Bitwarden failed.\nError: ${result.errorMessage}`);
}
return client;
}

47
dist/parser.js vendored Normal file
View File

@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecretInput = void 0;
exports.parseSecretInput = parseSecretInput;
const validators_1 = require("./validators");
class SecretInput {
constructor(id, outputEnvName) {
this.id = id;
this.outputEnvName = outputEnvName;
}
}
exports.SecretInput = SecretInput;
class ParsingError extends Error {
constructor(message) {
super(message);
}
}
function parseSecretInput(secrets) {
const results = secrets.map((secret) => {
try {
if (secret.indexOf(">") === -1) {
throw new ParsingError(`Expected format: <secretGuid> > <environmentVariableName>`);
}
let [id, envName] = secret.split(">", 2);
id = id.trim();
envName = envName.trim();
if (!(0, validators_1.isValidGuid)(id)) {
throw new ParsingError(`Id is not a valid GUID`);
}
if (!(0, validators_1.isValidEnvName)(envName)) {
throw new ParsingError(`Environment variable name is not valid`);
}
return new SecretInput(id, envName);
}
catch (e) {
const message = `Error occurred when attempting to parse ${secret}`;
if (e instanceof ParsingError) {
throw TypeError(`${message}. ${e.message}`);
}
throw TypeError(message);
}
});
if (!(0, validators_1.isUniqueEnvNames)(results)) {
throw TypeError("Environmental variable names provided are not unique, names must be unique");
}
return results;
}

30
dist/validators.js vendored Normal file
View File

@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isValidUrl = isValidUrl;
exports.isValidEnvName = isValidEnvName;
exports.isValidGuid = isValidGuid;
exports.isUniqueEnvNames = isUniqueEnvNames;
const ENV_NAME_REGEX = /^[a-zA-Z_]+[a-zA-Z0-9_]*$/;
const GUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
function isValidUrl(url) {
try {
const tempUrl = new URL(url);
if (tempUrl.protocol === "https:") {
return true;
}
}
catch {
return false;
}
return false;
}
function isValidEnvName(name) {
return ENV_NAME_REGEX.test(name);
}
function isValidGuid(value) {
return GUID_REGEX.test(value);
}
function isUniqueEnvNames(secretInputs) {
const envNames = [...new Set(secretInputs.map((s) => s.outputEnvName))];
return envNames.length === secretInputs.length;
}

347
package-lock.json generated Normal file
View File

@ -0,0 +1,347 @@
{
"name": "ts-bw-secrets",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ts-bw-secrets",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@actions/core": "^1.10.1",
"@bitwarden/sdk-napi": "^0.3.1"
},
"devDependencies": {
"@types/node": "^22.13.5",
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
}
},
"node_modules/@actions/core": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz",
"integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"node_modules/@actions/http-client": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^5.25.4"
}
},
"node_modules/@bitwarden/sdk-napi": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-napi/-/sdk-napi-0.3.1.tgz",
"integrity": "sha512-G5oM/st2+1eQOposoxdgLqK52j52fkcjv6W3OiojBx/yHy854FnY5KfKZRf73DRV/U1AYnHWckHE4IQ53/u3CQ==",
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@bitwarden/sdk-napi-darwin-arm64": "0.3.1",
"@bitwarden/sdk-napi-darwin-x64": "0.3.1",
"@bitwarden/sdk-napi-linux-x64-gnu": "0.3.1",
"@bitwarden/sdk-napi-win32-x64-msvc": "0.3.1"
}
},
"node_modules/@bitwarden/sdk-napi-darwin-arm64": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-napi-darwin-arm64/-/sdk-napi-darwin-arm64-0.3.1.tgz",
"integrity": "sha512-kfQ6uEJOEO9x8sPHk+qysJGYvBfc9XIHLSTNMp8axkRpuVOkc+uq7IC2fvViUDNJXGRjdepvuC5blp75oT6BMA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@bitwarden/sdk-napi-darwin-x64": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-napi-darwin-x64/-/sdk-napi-darwin-x64-0.3.1.tgz",
"integrity": "sha512-mV4DLakyQ4hhM3HI0jeZ55y62UnrDccj6qX7Z5ygItx/Q9w0mEn0TQLZiwcm/uqtOYWBsDr3Bg9QyyiOfCCT1g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@bitwarden/sdk-napi-linux-x64-gnu": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-napi-linux-x64-gnu/-/sdk-napi-linux-x64-gnu-0.3.1.tgz",
"integrity": "sha512-NkS09B0P55zWy6YAyFKJ1MytJWyUgAUFCNgFCf6wx7L2W9uplKfunWaY4NhElAhn4pXX1aElNN1+T2OECUTykg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@bitwarden/sdk-napi-win32-x64-msvc": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-napi-win32-x64-msvc/-/sdk-napi-win32-x64-msvc-0.3.1.tgz",
"integrity": "sha512-C70Y4skSbPcKxAVb3zUS++TmtHU8Wz5FCx+kdJvq1VWPAokuOG5OjlqiK06tY+zaiCIgNBAvjxfHCq1774N30w==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"engines": {
"node": ">=14"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true
},
"node_modules/@tsconfig/node10": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
"dev": true
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true
},
"node_modules/@types/node": {
"version": "22.13.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz",
"integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==",
"dev": true,
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"dev": true,
"dependencies": {
"acorn": "^8.11.0"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/typescript": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici": {
"version": "5.28.5",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz",
"integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==",
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
"engines": {
"node": ">=14.0"
}
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"engines": {
"node": ">=6"
}
}
}
}

22
package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "ts-bw-secrets",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dist": "pwsh ./pack.ps1 && ncc build src/index.ts --license licenses.txt --external @bitwarden/sdk-napi"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@actions/core": "^1.10.1",
"@bitwarden/sdk-napi": "^0.3.1"
},
"devDependencies": {
"@types/node": "^22.13.5",
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
}
}

3
src/index.ts Normal file
View File

@ -0,0 +1,3 @@
import { run } from "./main";
run();

103
src/main.ts Normal file
View File

@ -0,0 +1,103 @@
import * as core from "@actions/core";
import {
BitwardenClient,
ClientSettings,
DeviceType,
LogLevel,
} from "@bitwarden/sdk-napi";
import { parseSecretInput, SecretInput } from "./parser";
interface Inputs {
clientId: string;
clientSecret: string;
password: string;
server: string;
secrets: string[];
}
export async function run(): Promise<void> {
try {
const inputs = readInputs();
core.info("🔑 Logging into Bitwarden...");
const client = await getBitwardenClient(inputs);
const secretInputs = parseSecretInput(inputs.secrets);
core.info("🔍 Retrieving secrets...");
await retrieveAndSetSecrets(client, secretInputs);
core.info("✅ Successfully retrieved and set secrets!");
} catch (error) {
core.setFailed(
`❌ Error: ${error instanceof Error ? error.message : error}`
);
}
}
function readInputs(): Inputs {
const clientId = core.getInput("client-id", { required: true });
const clientSecret = core.getInput("client-secret", { required: true });
const password = core.getInput("password", { required: true });
const server = core.getInput("server", { required: true }); // Je eigen Bitwarden server
const secretsRaw = core.getInput("secrets", { required: true });
const secrets = secretsRaw
.split("\n")
.map((s) => s.trim())
.filter((s) => s.includes(">"));
if (secrets.length === 0) {
throw new Error("No valid secrets provided.");
}
return { clientId, clientSecret, password, server, secrets };
}
async function retrieveAndSetSecrets(
client: BitwardenClient,
secretsInput: SecretInput[]
) {
const secretIds = secretsInput.map((secret) => secret.id);
const secretResponse = await client.secrets().getByIds(secretIds);
if (secretResponse.success && secretResponse.data) {
const fetchedSecrets = secretResponse.data.data;
fetchedSecrets.forEach((secret) => {
const secretInput = secretsInput.find((input) => input.id === secret.id);
if (secretInput) {
core.setSecret(secret.value);
core.exportVariable(secretInput.outputEnvName, secret.value);
core.setOutput(secretInput.outputEnvName, secret.value);
}
});
} else {
throw new Error(
`The secrets provided could not be found. Please check the machine account has access to the secret UUIDs provided.\nError: ${secretResponse.errorMessage}`
);
}
core.info("✅ Completed setting secrets as environment variables.");
}
async function getBitwardenClient(inputs: Inputs): Promise<BitwardenClient> {
const settings: ClientSettings = {
identityUrl: `${inputs.server}/identity`,
apiUrl: `${inputs.server}/api`,
userAgent: "actions/warden",
deviceType: DeviceType.SDK,
};
const client = new BitwardenClient(settings, LogLevel.Info);
const result = await client.loginWithAccessToken(inputs.clientSecret);
if (!result.success) {
throw Error(
`Authentication with Bitwarden failed.\nError: ${result.errorMessage}`
);
}
return client;
}

48
src/parser.ts Normal file
View File

@ -0,0 +1,48 @@
import { isUniqueEnvNames, isValidEnvName, isValidGuid } from "./validators";
export class SecretInput {
constructor(public id: string, public outputEnvName: string) {}
}
class ParsingError extends Error {
constructor(message: string) {
super(message);
}
}
export function parseSecretInput(secrets: string[]): SecretInput[] {
const results = secrets.map((secret) => {
try {
if (secret.indexOf(">") === -1) {
throw new ParsingError(
`Expected format: <secretGuid> > <environmentVariableName>`
);
}
let [id, envName] = secret.split(">", 2);
id = id.trim();
envName = envName.trim();
if (!isValidGuid(id)) {
throw new ParsingError(`Id is not a valid GUID`);
}
if (!isValidEnvName(envName)) {
throw new ParsingError(`Environment variable name is not valid`);
}
return new SecretInput(id, envName);
} catch (e: unknown) {
const message = `Error occurred when attempting to parse ${secret}`;
if (e instanceof ParsingError) {
throw TypeError(`${message}. ${e.message}`);
}
throw TypeError(message);
}
});
if (!isUniqueEnvNames(results)) {
throw TypeError(
"Environmental variable names provided are not unique, names must be unique"
);
}
return results;
}

31
src/validators.ts Normal file
View File

@ -0,0 +1,31 @@
import { SecretInput } from "./parser";
const ENV_NAME_REGEX = /^[a-zA-Z_]+[a-zA-Z0-9_]*$/;
const GUID_REGEX =
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
export function isValidUrl(url: string): boolean {
try {
const tempUrl = new URL(url);
if (tempUrl.protocol === "https:") {
return true;
}
} catch {
return false;
}
return false;
}
export function isValidEnvName(name: string): boolean {
return ENV_NAME_REGEX.test(name);
}
export function isValidGuid(value: string): boolean {
return GUID_REGEX.test(value);
}
export function isUniqueEnvNames(secretInputs: SecretInput[]): boolean {
const envNames = [...new Set(secretInputs.map((s) => s.outputEnvName))];
return envNames.length === secretInputs.length;
}

10
tsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"outDir": "dist",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*.ts"]
}