Skip to content

Commit

Permalink
feat: add support for prefixing all secrets on copy
Browse files Browse the repository at this point in the history
  • Loading branch information
shubham-stepsecurity committed Aug 29, 2024
1 parent 81f3282 commit dd59271
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 13 deletions.
11 changes: 9 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
name: "Build"
on:
on:
[push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: 16
- run: |
npm i
npm run all
- name: Upload coverage reports to Codecov
if: always()
uses: codecov/codecov-action@v3
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,7 @@ Thumbs.db

# Ignore built ts files
__tests__/runner/*
lib/**/*
lib/**/*

# Ignore IntelliJ files
.idea
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ If this value is set to the name of a valid environment in the target repositori

Target where secrets should be stored: `actions` (default), `codespaces` or `dependabot`.

### `new_secret_prefix`

If this value is set, the action will prefix the name of the secret with the provided value. This is useful when you want to use the same secret name in multiple repositories but want to avoid conflicts.

## Usage

```yaml
Expand Down
4 changes: 4 additions & 0 deletions __tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe("getConfig", () => {
const RUN_DELETE = false;
const ENVIRONMENT = "production";
const TARGET = "actions";
const NEW_SECRET_PREFIX = "PREFIX_";

// Must implement because operands for delete must be optional in typescript >= 4.0
interface Inputs {
Expand All @@ -51,6 +52,7 @@ describe("getConfig", () => {
INPUT_RUN_DELETE: string;
INPUT_ENVIRONMENT: string;
INPUT_TARGET: string;
INPUT_NEW_SECRET_PREFIX: string;
}
const inputs: Inputs = {
INPUT_GITHUB_API_URL: String(GITHUB_API_URL),
Expand All @@ -64,6 +66,7 @@ describe("getConfig", () => {
INPUT_RUN_DELETE: String(RUN_DELETE),
INPUT_ENVIRONMENT: String(ENVIRONMENT),
INPUT_TARGET: String(TARGET),
INPUT_NEW_SECRET_PREFIX: String(NEW_SECRET_PREFIX),
};

beforeEach(() => {
Expand Down Expand Up @@ -93,6 +96,7 @@ describe("getConfig", () => {
RUN_DELETE,
ENVIRONMENT,
TARGET,
NEW_SECRET_PREFIX,
});
});

Expand Down
23 changes: 21 additions & 2 deletions __tests__/github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import * as config from "../src/config";

import {
DefaultOctokit,
deleteSecretForRepo,
filterReposByPatterns,
listAllMatchingRepos,
getRepos,
listAllMatchingRepos,
publicKeyCache,
setSecretForRepo,
deleteSecretForRepo,
} from "../src/github";

// @ts-ignore-next-line
Expand All @@ -41,6 +41,7 @@ beforeAll(() => {
REPOSITORIES_LIST_REGEX: true,
DRY_RUN: false,
RETRIES: 3,
NEW_SECRET_PREFIX: "",
});

octokit = DefaultOctokit({
Expand Down Expand Up @@ -189,6 +190,7 @@ describe("setSecretForRepo", () => {
secrets.FOO,
repo,
"",
"",
true,
"actions"
);
Expand All @@ -202,6 +204,7 @@ describe("setSecretForRepo", () => {
secrets.FOO,
repo,
"",
"",
true,
"dependabot"
);
Expand All @@ -215,6 +218,7 @@ describe("setSecretForRepo", () => {
secrets.FOO,
repo,
"",
"",
true,
"codespaces"
);
Expand All @@ -228,6 +232,7 @@ describe("setSecretForRepo", () => {
secrets.FOO,
repo,
"",
"",
true,
"actions"
);
Expand All @@ -242,6 +247,7 @@ describe("setSecretForRepo", () => {
secrets.FOO,
repo,
"",
"",
false,
"actions"
);
Expand All @@ -255,6 +261,7 @@ describe("setSecretForRepo", () => {
secrets.FOO,
repo,
"",
"",
false,
"dependabot"
);
Expand All @@ -268,6 +275,7 @@ describe("setSecretForRepo", () => {
secrets.FOO,
repo,
"",
"",
false,
"codespaces"
);
Expand Down Expand Up @@ -320,6 +328,7 @@ describe("setSecretForRepo with environment", () => {
secrets.FOO,
repo,
repoEnvironment,
"",
true,
"actions"
);
Expand All @@ -333,6 +342,7 @@ describe("setSecretForRepo with environment", () => {
secrets.FOO,
repo,
repoEnvironment,
"",
true,
"actions"
);
Expand All @@ -347,6 +357,7 @@ describe("setSecretForRepo with environment", () => {
secrets.FOO,
repo,
repoEnvironment,
"",
true,
"dependabot"
);
Expand All @@ -361,6 +372,7 @@ describe("setSecretForRepo with environment", () => {
secrets.FOO,
repo,
repoEnvironment,
"",
false,
"actions"
);
Expand Down Expand Up @@ -401,6 +413,7 @@ describe("deleteSecretForRepo", () => {
secrets.FOO,
repo,
"",
"",
true,
"actions"
);
Expand All @@ -414,6 +427,7 @@ describe("deleteSecretForRepo", () => {
secrets.FOO,
repo,
"",
"",
false,
"actions"
);
Expand All @@ -427,6 +441,7 @@ describe("deleteSecretForRepo", () => {
secrets.FOO,
repo,
"",
"",
false,
"dependabot"
);
Expand All @@ -440,6 +455,7 @@ describe("deleteSecretForRepo", () => {
secrets.FOO,
repo,
"",
"",
false,
"codespaces"
);
Expand Down Expand Up @@ -473,6 +489,7 @@ describe("deleteSecretForRepo with environment", () => {
secrets.FOO,
repo,
repoEnvironment,
"",
true,
"actions"
);
Expand All @@ -486,6 +503,7 @@ describe("deleteSecretForRepo with environment", () => {
secrets.FOO,
repo,
repoEnvironment,
"",
true,
"dependabot"
);
Expand All @@ -499,6 +517,7 @@ describe("deleteSecretForRepo with environment", () => {
secrets.FOO,
repo,
repoEnvironment,
"",
false,
"actions"
);
Expand Down
7 changes: 6 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# action.yml
name: "Secrets Sync Action"
branding:
icon: 'copy'
icon: 'copy'
color: 'red'
description: "Copies secrets from the action's environment to many other repos."
inputs:
Expand Down Expand Up @@ -66,6 +66,11 @@ inputs:
Target where secrets should be stored: `actions` (default), `codespaces` or `dependabot`.
default: "actions"
required: false
new_secret_prefix:
default: ""
description: |
If this value is set, the action will prefix the secret name with this value.
required: false
runs:
using: 'node20'
main: 'dist/index.js'
14 changes: 9 additions & 5 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ function getConfig() {
RUN_DELETE: ["1", "true"].includes(core.getInput("DELETE", { required: false }).toLowerCase()),
ENVIRONMENT: core.getInput("ENVIRONMENT", { required: false }),
TARGET: core.getInput("TARGET", { required: false }),
NEW_SECRET_PREFIX: core.getInput("NEW_SECRET_PREFIX", { required: false }),
};
if (config.DRY_RUN) {
core.info("[DRY_RUN='true'] No changes will be written to secrets");
Expand Down Expand Up @@ -265,12 +266,13 @@ function getPublicKey(octokit, repo, environment, target) {
});
}
exports.getPublicKey = getPublicKey;
function setSecretForRepo(octokit, name, secret, repo, environment, dry_run, target) {
function setSecretForRepo(octokit, name, secret, repo, environment, new_secret_prefix, dry_run, target) {
return __awaiter(this, void 0, void 0, function* () {
const [repo_owner, repo_name] = repo.full_name.split("/");
const publicKey = yield getPublicKey(octokit, repo, environment, target);
const encrypted_value = (0, utils_1.encrypt)(secret, publicKey.key);
core.info(`Set \`${name} = ***\` on ${repo.full_name}`);
const final_name = new_secret_prefix ? new_secret_prefix + name : name;
core.info(`Set \`${final_name} = ***\` on ${repo.full_name}`);
if (!dry_run) {
switch (target) {
case "codespaces":
Expand Down Expand Up @@ -314,9 +316,10 @@ function setSecretForRepo(octokit, name, secret, repo, environment, dry_run, tar
});
}
exports.setSecretForRepo = setSecretForRepo;
function deleteSecretForRepo(octokit, name, secret, repo, environment, dry_run, target) {
function deleteSecretForRepo(octokit, name, secret, repo, environment, new_secret_prefix, dry_run, target) {
return __awaiter(this, void 0, void 0, function* () {
core.info(`Remove ${name} from ${repo.full_name}`);
const final_name = new_secret_prefix ? new_secret_prefix + name : name;
core.info(`Remove ${final_name} from ${repo.full_name}`);
try {
if (!dry_run) {
const action = "DELETE";
Expand Down Expand Up @@ -513,6 +516,7 @@ function run() {
FOUND_SECRETS: Object.keys(secrets),
ENVIRONMENT: config.ENVIRONMENT,
TARGET: config.TARGET,
NEW_SECRET_PREFIX: config.NEW_SECRET_PREFIX,
}, null, 2));
const limit = (0, p_limit_1.default)(config.CONCURRENCY);
const calls = [];
Expand All @@ -521,7 +525,7 @@ function run() {
const action = config.RUN_DELETE
? github_1.deleteSecretForRepo
: github_1.setSecretForRepo;
calls.push(limit(() => action(octokit, k, secrets[k], repo, config.ENVIRONMENT, config.DRY_RUN, config.TARGET)));
calls.push(limit(() => action(octokit, k, secrets[k], repo, config.ENVIRONMENT, config.NEW_SECRET_PREFIX, config.DRY_RUN, config.TARGET)));
}
}
yield Promise.all(calls);
Expand Down
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface Config {
RUN_DELETE: boolean;
ENVIRONMENT: string;
TARGET: string;
NEW_SECRET_PREFIX: string;
}

export function getConfig(): Config {
Expand All @@ -54,6 +55,7 @@ export function getConfig(): Config {
),
ENVIRONMENT: core.getInput("ENVIRONMENT", { required: false }),
TARGET: core.getInput("TARGET", { required: false }),
NEW_SECRET_PREFIX: core.getInput("NEW_SECRET_PREFIX", { required: false }),
};

if (config.DRY_RUN) {
Expand Down
9 changes: 7 additions & 2 deletions src/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,17 @@ export async function setSecretForRepo(
secret: string,
repo: Repository,
environment: string,
new_secret_prefix: string,
dry_run: boolean,
target: string
): Promise<void> {
const [repo_owner, repo_name] = repo.full_name.split("/");

const publicKey = await getPublicKey(octokit, repo, environment, target);
const encrypted_value = encrypt(secret, publicKey.key);
const final_name = new_secret_prefix ? new_secret_prefix + name : name;

core.info(`Set \`${name} = ***\` on ${repo.full_name}`);
core.info(`Set \`${final_name} = ***\` on ${repo.full_name}`);

if (!dry_run) {
switch (target) {
Expand Down Expand Up @@ -288,10 +290,13 @@ export async function deleteSecretForRepo(
secret: string,
repo: Repository,
environment: string,
new_secret_prefix: string,
dry_run: boolean,
target: string
): Promise<void> {
core.info(`Remove ${name} from ${repo.full_name}`);
const final_name = new_secret_prefix ? new_secret_prefix + name : name;

core.info(`Remove ${final_name} from ${repo.full_name}`);

try {
if (!dry_run) {
Expand Down
2 changes: 2 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export async function run(): Promise<void> {
FOUND_SECRETS: Object.keys(secrets),
ENVIRONMENT: config.ENVIRONMENT,
TARGET: config.TARGET,
NEW_SECRET_PREFIX: config.NEW_SECRET_PREFIX,
},
null,
2
Expand All @@ -110,6 +111,7 @@ export async function run(): Promise<void> {
secrets[k],
repo,
config.ENVIRONMENT,
config.NEW_SECRET_PREFIX,
config.DRY_RUN,
config.TARGET
)
Expand Down

0 comments on commit dd59271

Please sign in to comment.