mirror of
https://github.com/1Password/load-secrets-action.git
synced 2026-06-21 14:23:48 +00:00
Add workload identy feature
This commit is contained in:
@@ -20,6 +20,12 @@ on:
|
||||
required: true
|
||||
OP_SERVICE_ACCOUNT_TOKEN:
|
||||
required: true
|
||||
OP_WORKLOAD_ID:
|
||||
required: true
|
||||
OP_ENVIRONMENT_ID:
|
||||
required: true
|
||||
OP_INTEGRATION_KEY:
|
||||
required: true
|
||||
VAULT:
|
||||
description: "1Password vault name or UUID"
|
||||
required: true
|
||||
@@ -248,3 +254,77 @@ jobs:
|
||||
- name: Assert removed secrets [exported env]
|
||||
if: ${{ matrix.export-env }}
|
||||
run: ./tests/assert-env-unset.sh
|
||||
|
||||
test-workload-identity:
|
||||
name: Workload Identity (ubuntu-latest, export-env=${{ matrix.export-env }})
|
||||
runs-on: ubuntu-latest
|
||||
# Workload Identity exchanges the GitHub OIDC token for 1Password access,
|
||||
# so the job needs permission to request an OIDC token.
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
export-env: [true, false]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.ref }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build actions
|
||||
run: npm run build:all
|
||||
|
||||
# No ./configure step and no op:// references: Workload Identity authenticates
|
||||
# via OIDC and loads all variables from the configured 1Password environment.
|
||||
- name: Load secrets
|
||||
id: load_secrets
|
||||
uses: ./
|
||||
with:
|
||||
export-env: ${{ matrix.export-env }}
|
||||
env:
|
||||
OP_WORKLOAD_ID: ${{ secrets.OP_WORKLOAD_ID }}
|
||||
OP_ENVIRONMENT_ID: ${{ secrets.OP_ENVIRONMENT_ID }}
|
||||
OP_INTEGRATION_KEY: ${{ secrets.OP_INTEGRATION_KEY }}
|
||||
|
||||
- name: Assert test secret values [step output]
|
||||
if: ${{ !matrix.export-env }}
|
||||
shell: bash
|
||||
env:
|
||||
ANOTHER_TEST: ${{ steps.load_secrets.outputs.ANOTHER_TEST }}
|
||||
SUPER_SECRET: ${{ steps.load_secrets.outputs.SUPER_SECRET }}
|
||||
TEST_SECRET: ${{ steps.load_secrets.outputs.TEST_SECRET }}
|
||||
run: ./tests/assert-workload-identity.sh
|
||||
|
||||
- name: Assert test secret values [exported env]
|
||||
if: ${{ matrix.export-env }}
|
||||
shell: bash
|
||||
run: ./tests/assert-workload-identity.sh
|
||||
|
||||
- name: Remove secrets [exported env]
|
||||
if: ${{ matrix.export-env }}
|
||||
uses: ./
|
||||
with:
|
||||
unset-previous: true
|
||||
|
||||
- name: Assert removed secrets [exported env]
|
||||
if: ${{ matrix.export-env }}
|
||||
shell: bash
|
||||
run: |
|
||||
for var in ANOTHER_TEST SUPER_SECRET TEST_SECRET; do
|
||||
if [ -n "$(printenv "$var")" ]; then
|
||||
echo "Expected secret $var to be unset"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -92,6 +92,9 @@ jobs:
|
||||
OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }}
|
||||
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||
OP_WORKLOAD_ID: ${{ secrets.OP_WORKLOAD_ID }}
|
||||
OP_ENVIRONMENT_ID: ${{ secrets.OP_ENVIRONMENT_ID }}
|
||||
OP_INTEGRATION_KEY: ${{ secrets.OP_INTEGRATION_KEY }}
|
||||
VAULT: ${{ secrets.VAULT }}
|
||||
|
||||
# Post comment on fork PRs after /ok-to-test
|
||||
|
||||
@@ -88,6 +88,33 @@ When loading SSH keys, you can specify the format using the `ssh-format` query p
|
||||
|
||||
For more details on secret reference syntax, see the [1Password CLI documentation](https://developer.1password.com/docs/cli/secret-reference-syntax/#ssh-format-parameter).
|
||||
|
||||
## 🧪 Workload Identity (private beta)
|
||||
|
||||
> [!NOTE]
|
||||
> Workload Identity is in **private beta**. It's available to invited participants only. [Contact 1Password](https://developer.1password.com/joinslack) if you're interested in joining the beta.
|
||||
|
||||
Instead of a Service Account token or Connect credentials, you can authenticate using Workload Identity, which exchanges your GitHub Actions OIDC token for short-lived 1Password access — no long-lived secret to store. To use it, set all three of the following environment variables (and do not set `OP_SERVICE_ACCOUNT_TOKEN` or the Connect variables):
|
||||
|
||||
```yml
|
||||
on: push
|
||||
jobs:
|
||||
hello-world:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
id-token: write # required for the action to request a GitHub OIDC token
|
||||
contents: read
|
||||
steps:
|
||||
- name: Load secret
|
||||
id: load_secrets
|
||||
uses: 1password/load-secrets-action@v5beta
|
||||
env:
|
||||
OP_WORKLOAD_ID: ${{ vars.OP_WORKLOAD_ID }}
|
||||
OP_ENVIRONMENT_ID: ${{ vars.OP_ENVIRONMENT_ID }}
|
||||
OP_INTEGRATION_KEY: ${{ secrets.OP_INTEGRATION_KEY }}
|
||||
```
|
||||
|
||||
When Workload Identity is configured, secrets are loaded directly from your environment's variables. You don't need to specify individual `op://` secret references. If only some of the three variables are set, or if they're combined with another authentication method, the action fails with a configuration error.
|
||||
|
||||
## 💙 Community & Support
|
||||
|
||||
- File an [issue](https://github.com/1Password/load-secrets-action/issues) for bugs and feature requests.
|
||||
|
||||
@@ -14,6 +14,7 @@ const jestConfig = {
|
||||
"^@actions/core$": "<rootDir>/__mocks__/actions-core.ts",
|
||||
"^@actions/tool-cache$": "<rootDir>/__mocks__/actions-tool-cache.ts",
|
||||
"^@actions/exec$": "<rootDir>/__mocks__/actions-exec.ts",
|
||||
"^@1password/sdk$": "<rootDir>/__mocks__/1password-sdk.ts",
|
||||
},
|
||||
transform: {
|
||||
".ts": [
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Vendored
+2931
-33
File diff suppressed because one or more lines are too long
Generated
+16
@@ -10,6 +10,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@1password/op-js": "^0.1.11",
|
||||
"@1password/sdk": "0.5.0-beta.1",
|
||||
"@actions/core": "^3.0.0",
|
||||
"@actions/exec": "^3.0.0",
|
||||
"@actions/tool-cache": "^4.0.0",
|
||||
@@ -72,6 +73,21 @@
|
||||
"prettier": "^2.0.0 || ^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@1password/sdk": {
|
||||
"version": "0.5.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/@1password/sdk/-/sdk-0.5.0-beta.1.tgz",
|
||||
"integrity": "sha512-GY1kcn86qkb39jt20AyOftEu5Tw/Kyq4f84GOHXKRjur4TvqvzdhapynBBosRcBL+kBrc+E8cx7Tp7GEfqAomw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@1password/sdk-core": "0.5.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@1password/sdk-core": {
|
||||
"version": "0.5.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/@1password/sdk-core/-/sdk-core-0.5.0-beta.1.tgz",
|
||||
"integrity": "sha512-61Q2n0kKYXBVAbW5ZVFqtbK1KX3lUfFi8wdsv+UjIVtbFd+X1GpFbLFs+nPtPgX+Z7oc2tTN/czK0S9Cz4oF/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz",
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/1Password/load-secrets-action#readme",
|
||||
"dependencies": {
|
||||
"@1password/sdk": "0.5.0-beta.1",
|
||||
"@1password/op-js": "^0.1.11",
|
||||
"@actions/core": "^3.0.0",
|
||||
"@actions/exec": "^3.0.0",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const createClient = jest.fn();
|
||||
@@ -11,4 +11,5 @@ module.exports = {
|
||||
debug: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
getIDToken: jest.fn(() => Promise.resolve("mock-oidc-token")),
|
||||
};
|
||||
|
||||
@@ -3,5 +3,8 @@ export const envConnectToken = "OP_CONNECT_TOKEN";
|
||||
export const envServiceAccountToken = "OP_SERVICE_ACCOUNT_TOKEN";
|
||||
export const envManagedVariables = "OP_MANAGED_VARIABLES";
|
||||
export const envFilePath = "OP_ENV_FILE";
|
||||
export const envWorkloadId = "OP_WORKLOAD_ID";
|
||||
export const envEnvironmentId = "OP_ENVIRONMENT_ID";
|
||||
export const envIntegrationKey = "OP_INTEGRATION_KEY";
|
||||
|
||||
export const authErr = `Authentication error with environment variables: you must set either 1) ${envServiceAccountToken}, or 2) both ${envConnectHost} and ${envConnectToken}.`;
|
||||
|
||||
+31
-14
@@ -2,7 +2,13 @@ import dotenv from "dotenv";
|
||||
import * as core from "@actions/core";
|
||||
import { validateCli } from "@1password/op-js";
|
||||
import { installCliOnGithubActionRunner } from "./op-cli-installer";
|
||||
import { loadSecrets, unsetPrevious, validateAuth } from "./utils";
|
||||
import {
|
||||
getWorkloadIdentityConfig,
|
||||
loadSecrets,
|
||||
unsetPrevious,
|
||||
validateAuth,
|
||||
} from "./utils";
|
||||
import { loadSecretsFromSDK } from "./sdk-client";
|
||||
import { envFilePath } from "./constants";
|
||||
|
||||
const loadSecretsAction = async () => {
|
||||
@@ -16,21 +22,32 @@ const loadSecretsAction = async () => {
|
||||
unsetPrevious();
|
||||
}
|
||||
|
||||
// Validate that a proper authentication configuration is set for the CLI
|
||||
validateAuth();
|
||||
const workloadConfig = getWorkloadIdentityConfig();
|
||||
|
||||
// Set environment variables from OP_ENV_FILE
|
||||
const file = process.env[envFilePath];
|
||||
if (file) {
|
||||
core.info(`Loading environment variables from file: ${file}`);
|
||||
dotenv.config({ path: file });
|
||||
if (workloadConfig) {
|
||||
await loadSecretsFromSDK(
|
||||
workloadConfig.workloadId,
|
||||
workloadConfig.environmentId,
|
||||
workloadConfig.integrationKey,
|
||||
shouldExportEnv,
|
||||
);
|
||||
} else {
|
||||
// Validate that a proper authentication configuration is set for the CLI
|
||||
validateAuth();
|
||||
|
||||
// Set environment variables from OP_ENV_FILE
|
||||
const file = process.env[envFilePath];
|
||||
if (file) {
|
||||
core.info(`Loading environment variables from file: ${file}`);
|
||||
dotenv.config({ path: file });
|
||||
}
|
||||
|
||||
// Download and install the CLI
|
||||
await installCLI();
|
||||
|
||||
// Load secrets
|
||||
await loadSecrets(shouldExportEnv);
|
||||
}
|
||||
|
||||
// Download and install the CLI
|
||||
await installCLI();
|
||||
|
||||
// Load secrets
|
||||
await loadSecrets(shouldExportEnv);
|
||||
} catch (error) {
|
||||
// It's possible for the Error constructor to be modified to be anything
|
||||
// in JavaScript, so the following code accounts for this possibility.
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
import * as core from "@actions/core";
|
||||
import { createClient } from "@1password/sdk";
|
||||
import { envManagedVariables } from "./constants";
|
||||
import { getOIDCToken, loadSecretsFromSDK } from "./sdk-client";
|
||||
|
||||
jest.mock("@1password/sdk");
|
||||
|
||||
const mockGetVariables = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(createClient as jest.Mock).mockResolvedValue({
|
||||
environments: {
|
||||
getVariables: mockGetVariables,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("getOIDCToken", () => {
|
||||
it("delegates to core.getIDToken", async () => {
|
||||
(core.getIDToken as jest.Mock).mockResolvedValue("oidc-token");
|
||||
|
||||
await expect(getOIDCToken("test-audience")).resolves.toBe("oidc-token");
|
||||
expect(core.getIDToken).toHaveBeenCalledWith("test-audience");
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadSecretsFromSDK", () => {
|
||||
const workloadId = "workload-uuid";
|
||||
const environmentId = "environment-uuid";
|
||||
const integrationKey = "integration-key";
|
||||
|
||||
const variables = [
|
||||
{ name: "DOCKERHUB_USERNAME", value: "myuser" },
|
||||
{ name: "DOCKERHUB_TOKEN", value: "mypassword" },
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetVariables.mockResolvedValue({ variables });
|
||||
});
|
||||
|
||||
it("sets secrets as step outputs by default", async () => {
|
||||
await loadSecretsFromSDK(
|
||||
workloadId,
|
||||
environmentId,
|
||||
integrationKey,
|
||||
false,
|
||||
);
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith(
|
||||
"DOCKERHUB_USERNAME",
|
||||
"myuser",
|
||||
);
|
||||
expect(core.setOutput).toHaveBeenCalledWith(
|
||||
"DOCKERHUB_TOKEN",
|
||||
"mypassword",
|
||||
);
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith(
|
||||
"DOCKERHUB_USERNAME",
|
||||
"myuser",
|
||||
);
|
||||
expect(core.setSecret).toHaveBeenCalledWith("myuser");
|
||||
expect(core.setSecret).toHaveBeenCalledWith("mypassword");
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith(
|
||||
envManagedVariables,
|
||||
expect.any(String),
|
||||
);
|
||||
});
|
||||
|
||||
it("exports secrets as environment variables when shouldExportEnv is true", async () => {
|
||||
await loadSecretsFromSDK(
|
||||
workloadId,
|
||||
environmentId,
|
||||
integrationKey,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(core.exportVariable).toHaveBeenCalledWith(
|
||||
"DOCKERHUB_USERNAME",
|
||||
"myuser",
|
||||
);
|
||||
expect(core.exportVariable).toHaveBeenCalledWith(
|
||||
"DOCKERHUB_TOKEN",
|
||||
"mypassword",
|
||||
);
|
||||
expect(core.setOutput).not.toHaveBeenCalled();
|
||||
expect(core.exportVariable).toHaveBeenCalledWith(
|
||||
envManagedVariables,
|
||||
"DOCKERHUB_USERNAME,DOCKERHUB_TOKEN",
|
||||
);
|
||||
});
|
||||
|
||||
describe("when secret value is empty string", () => {
|
||||
beforeEach(() => {
|
||||
mockGetVariables.mockResolvedValue({
|
||||
variables: [{ name: "EMPTY_SECRET", value: "" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("sets empty string as step output", async () => {
|
||||
await loadSecretsFromSDK(
|
||||
workloadId,
|
||||
environmentId,
|
||||
integrationKey,
|
||||
false,
|
||||
);
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith("EMPTY_SECRET", "");
|
||||
expect(core.setSecret).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sets empty string as environment variable", async () => {
|
||||
await loadSecretsFromSDK(
|
||||
workloadId,
|
||||
environmentId,
|
||||
integrationKey,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(core.exportVariable).toHaveBeenCalledWith("EMPTY_SECRET", "");
|
||||
expect(core.setSecret).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not export OP_MANAGED_VARIABLES when no variables are returned", async () => {
|
||||
mockGetVariables.mockResolvedValue({ variables: [] });
|
||||
|
||||
await loadSecretsFromSDK(
|
||||
workloadId,
|
||||
environmentId,
|
||||
integrationKey,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(core.exportVariable).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import * as core from "@actions/core";
|
||||
import { createClient } from "@1password/sdk";
|
||||
import { version } from "../package.json";
|
||||
import { envManagedVariables } from "./constants";
|
||||
|
||||
export const getOIDCToken = async (audience: string): Promise<string> =>
|
||||
core.getIDToken(audience);
|
||||
|
||||
export const loadSecretsFromSDK = async (
|
||||
workloadId: string,
|
||||
environmentId: string,
|
||||
integrationKey: string,
|
||||
shouldExportEnv: boolean,
|
||||
): Promise<void> => {
|
||||
|
||||
// Temporary fix: strip base64 padding from integrationKey — this will eventually be handled by the SDK core itself
|
||||
integrationKey = integrationKey.replace(/=+$/, "");
|
||||
|
||||
const client = await createClient({
|
||||
integrationName: "1Password GitHub Action",
|
||||
integrationVersion: version,
|
||||
oidcFetcher: getOIDCToken,
|
||||
workloadDetails: {
|
||||
customerManagedSecret: integrationKey,
|
||||
workloadUuid: workloadId,
|
||||
},
|
||||
});
|
||||
|
||||
core.info("Authenticated with Workload Identity.");
|
||||
|
||||
const { variables } = await client.environments.getVariables(environmentId);
|
||||
|
||||
const envNames: string[] = [];
|
||||
for (const { name, value } of variables) {
|
||||
core.info(`Populating variable: ${name}`);
|
||||
if (shouldExportEnv) {
|
||||
core.exportVariable(name, value);
|
||||
} else {
|
||||
core.setOutput(name, value);
|
||||
}
|
||||
if (value) {
|
||||
core.setSecret(value);
|
||||
}
|
||||
envNames.push(name);
|
||||
}
|
||||
|
||||
if (shouldExportEnv && envNames.length > 0) {
|
||||
core.exportVariable(envManagedVariables, envNames.join());
|
||||
}
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import * as exec from "@actions/exec";
|
||||
import { read, setClientInfo } from "@1password/op-js";
|
||||
import {
|
||||
extractSecret,
|
||||
getWorkloadIdentityConfig,
|
||||
loadSecrets,
|
||||
unsetPrevious,
|
||||
validateAuth,
|
||||
@@ -11,8 +12,11 @@ import {
|
||||
authErr,
|
||||
envConnectHost,
|
||||
envConnectToken,
|
||||
envEnvironmentId,
|
||||
envIntegrationKey,
|
||||
envManagedVariables,
|
||||
envServiceAccountToken,
|
||||
envWorkloadId,
|
||||
} from "./constants";
|
||||
|
||||
jest.mock("@1password/op-js");
|
||||
@@ -66,6 +70,68 @@ describe("validateAuth", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("getWorkloadIdentityConfig", () => {
|
||||
const testWorkloadId = "workload-id";
|
||||
const testEnvironmentId = "environment-id";
|
||||
const testIntegrationKey = "integration-key";
|
||||
|
||||
beforeEach(() => {
|
||||
process.env[envWorkloadId] = "";
|
||||
process.env[envEnvironmentId] = "";
|
||||
process.env[envIntegrationKey] = "";
|
||||
process.env[envConnectHost] = "";
|
||||
process.env[envConnectToken] = "";
|
||||
process.env[envServiceAccountToken] = "";
|
||||
});
|
||||
|
||||
it("should return null when no variables are set", () => {
|
||||
expect(getWorkloadIdentityConfig()).toBeNull();
|
||||
});
|
||||
|
||||
it("should return the config when all variables are set", () => {
|
||||
process.env[envWorkloadId] = testWorkloadId;
|
||||
process.env[envEnvironmentId] = testEnvironmentId;
|
||||
process.env[envIntegrationKey] = testIntegrationKey;
|
||||
|
||||
expect(getWorkloadIdentityConfig()).toEqual({
|
||||
workloadId: testWorkloadId,
|
||||
environmentId: testEnvironmentId,
|
||||
integrationKey: testIntegrationKey,
|
||||
});
|
||||
});
|
||||
|
||||
it("should throw an error when only some variables are set", () => {
|
||||
process.env[envWorkloadId] = testWorkloadId;
|
||||
|
||||
expect(getWorkloadIdentityConfig).toThrow(
|
||||
/Incomplete Workload Identity configuration/,
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw an error when combined with Connect credentials", () => {
|
||||
process.env[envWorkloadId] = testWorkloadId;
|
||||
process.env[envEnvironmentId] = testEnvironmentId;
|
||||
process.env[envIntegrationKey] = testIntegrationKey;
|
||||
process.env[envConnectHost] = "https://localhost:8000";
|
||||
process.env[envConnectToken] = "token";
|
||||
|
||||
expect(getWorkloadIdentityConfig).toThrow(
|
||||
/Conflicting authentication configuration/,
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw an error when combined with a service account token", () => {
|
||||
process.env[envWorkloadId] = testWorkloadId;
|
||||
process.env[envEnvironmentId] = testEnvironmentId;
|
||||
process.env[envIntegrationKey] = testIntegrationKey;
|
||||
process.env[envServiceAccountToken] = "ops_token";
|
||||
|
||||
expect(getWorkloadIdentityConfig).toThrow(
|
||||
/Conflicting authentication configuration/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractSecret", () => {
|
||||
const envTestSecretEnv = "TEST_SECRET";
|
||||
const testSecretRef = "op://vault/item/secret";
|
||||
|
||||
@@ -8,8 +8,53 @@ import {
|
||||
envConnectToken,
|
||||
envServiceAccountToken,
|
||||
envManagedVariables,
|
||||
envWorkloadId,
|
||||
envEnvironmentId,
|
||||
envIntegrationKey,
|
||||
} from "./constants";
|
||||
|
||||
export interface WorkloadIdentityConfig {
|
||||
workloadId: string;
|
||||
environmentId: string;
|
||||
integrationKey: string;
|
||||
}
|
||||
|
||||
// Returns the Workload Identity configuration when all variables are set,
|
||||
// or null when none are set (so the CLI auth path can be used instead).
|
||||
// Throws if the configuration is only partially set, or if it is combined
|
||||
// with the CLI auth methods (Connect / service account).
|
||||
export const getWorkloadIdentityConfig = (): WorkloadIdentityConfig | null => {
|
||||
const workloadId = process.env[envWorkloadId];
|
||||
const environmentId = process.env[envEnvironmentId];
|
||||
const integrationKey = process.env[envIntegrationKey];
|
||||
|
||||
// None set: fall back to the CLI auth path.
|
||||
if (!workloadId && !environmentId && !integrationKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Some but not all set: configuration is incomplete.
|
||||
if (!workloadId || !environmentId || !integrationKey) {
|
||||
throw new Error(
|
||||
`Incomplete Workload Identity configuration. To use Workload Identity, set all of ${envWorkloadId}, ${envEnvironmentId}, and ${envIntegrationKey}.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Workload Identity is fully configured, so it must not be combined with the
|
||||
// CLI auth methods (Connect / service account), which are mutually exclusive.
|
||||
if (
|
||||
process.env[envConnectHost] ||
|
||||
process.env[envConnectToken] ||
|
||||
process.env[envServiceAccountToken]
|
||||
) {
|
||||
throw new Error(
|
||||
`Conflicting authentication configuration: Workload Identity cannot be combined with Connect (${envConnectHost}/${envConnectToken}) or a service account (${envServiceAccountToken}). Set only one authentication method.`,
|
||||
);
|
||||
}
|
||||
|
||||
return { workloadId, environmentId, integrationKey };
|
||||
};
|
||||
|
||||
export const validateAuth = (): void => {
|
||||
const isConnect = process.env[envConnectHost] && process.env[envConnectToken];
|
||||
const isServiceAccount = process.env[envServiceAccountToken];
|
||||
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# shellcheck disable=SC2086
|
||||
set -e
|
||||
|
||||
# Asserts the secrets loaded via Workload Identity.
|
||||
|
||||
assert_env_equals() {
|
||||
if [ "$(printenv $1)" != "$2" ]; then
|
||||
echo -e "Expected $1 to be set to:\n$2\nBut got:\n$(printenv $1)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_env_equals "ANOTHER_TEST" "anothertest123"
|
||||
assert_env_equals "SUPER_SECRET" "supersecret"
|
||||
assert_env_equals "TEST_SECRET" "thisisatest"
|
||||
Reference in New Issue
Block a user