add secrets #1
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
/node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
56
action.yml
56
action.yml
@ -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
4
dist/index.js
vendored
Normal 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
101
dist/main.js
vendored
Normal 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
47
dist/parser.js
vendored
Normal 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
30
dist/validators.js
vendored
Normal 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
347
package-lock.json
generated
Normal 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
22
package.json
Normal 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
3
src/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { run } from "./main";
|
||||
|
||||
run();
|
103
src/main.ts
Normal file
103
src/main.ts
Normal 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
48
src/parser.ts
Normal 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
31
src/validators.ts
Normal 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
10
tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user