secrets
: _(Required)_ List of secrets to access and inject into the environment. These are
65 | comma-separated or newline-separated `OUTPUTNAME:SECRET`. Output names or
66 | secret names that contain separators must be escaped with a backslash
67 | (e.g. `\,` or `\\n`) unless quoted. Any leading or trailing whitespace is
68 | trimmed unless values are quoted.
69 |
70 | ```yaml
71 | secrets: |-
72 | output1:my-project/my-secret1
73 | output2:my-project/my-secret2
74 | ```
75 |
76 | Secrets can be referenced using the following formats:
77 |
78 | ```text
79 | # Long form
80 | projects/min_mask_length
: _(Optional, default: `4`)_ Minimum line length for a secret to be masked. Extremely short secrets
93 | (e.g. `{` or `a`) can make GitHub Actions log output unreadable. This is
94 | especially important for multi-line secrets, since each line of the secret
95 | is masked independently.
96 |
97 | - export_to_environment
: _(Optional)_ Make the fetched secrets additionally available as environment variables.
98 |
99 | - encoding
: _(Optional, default: `utf8`)_ Encoding in which secrets will be exported into outputs (and environment
100 | variables if `export_to_environment` is true). For secrets that cannot be
101 | represented in text, such as encryption key bytes, choose an encoding that
102 | has a safe character such as `base64` or `hex`. For more information about
103 | available encoding types, please see the [Node.js Buffer and character
104 | encodings](https://nodejs.org/docs/latest/api/buffer.html#buffers-and-character-encodings).
105 |
106 | - universe
: _(Optional, default: `googleapis.com`)_ The Google Cloud universe to use for constructing API endpoints. The
107 | default universe is "googleapis.com", which corresponds to
108 | https://cloud.google.com. Trusted Partner Cloud and Google Distributed
109 | Hosted Cloud should set this to their universe address.
110 |
111 |
112 |
113 |
114 |
115 | ## Outputs
116 |
117 |
118 |
119 | - `secrets`: Each secret is prefixed with an output name. The secret's resolved access
120 | value will be available at that output in future build steps. For example:
121 |
122 | ```yaml
123 | jobs:
124 | job_id:
125 | steps:
126 | - id: 'secrets'
127 | uses: 'google-github-actions/get-secretmanager-secrets@v2'
128 | with:
129 | secrets: |-
130 | token:my-project/docker-registry-token
131 | ```
132 |
133 | will be available in future steps as the output:
134 |
135 | ```text
136 | steps.secrets.outputs.token
137 | ```
138 |
139 |
140 |
141 |
142 |
143 | ## Authorization
144 |
145 | There are a few ways to authenticate this action. The caller must have
146 | permissions to access the secrets being requested.
147 |
148 | ### Via google-github-actions/auth
149 |
150 | Use [google-github-actions/auth](https://github.com/google-github-actions/auth)
151 | to authenticate the action. You can use [Workload Identity Federation][wif] or
152 | traditional [Service Account Key JSON][sa] authentication.
153 |
154 | ```yaml
155 | jobs:
156 | job_id:
157 | permissions:
158 | contents: 'read'
159 | id-token: 'write'
160 |
161 | steps:
162 | - uses: 'actions/checkout@v4'
163 |
164 | - id: 'auth'
165 | uses: 'google-github-actions/auth@v2'
166 | with:
167 | workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
168 | service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
169 |
170 | - id: 'secrets'
171 | uses: 'google-github-actions/get-secretmanager-secrets@v2'
172 | ```
173 |
174 | ### Via Application Default Credentials
175 |
176 | If you are hosting your own runners, **and** those runners are on Google Cloud,
177 | you can leverage the Application Default Credentials of the instance. This will
178 | authenticate requests as the service account attached to the instance. **This
179 | only works using a custom runner hosted on GCP.**
180 |
181 | ```yaml
182 | jobs:
183 | job_id:
184 | steps:
185 | - id: 'secrets'
186 | uses: 'google-github-actions/get-secretmanager-secrets@v2'
187 | ```
188 |
189 | The action will automatically detect and use the Application Default
190 | Credentials.
191 |
192 |
193 | [sm]: https://cloud.google.com/secret-manager
194 | [wif]: https://cloud.google.com/iam/docs/workload-identity-federation
195 | [sa]: https://cloud.google.com/iam/docs/creating-managing-service-accounts
196 | [gh-runners]: https://help.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners
197 | [gh-secret]: https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets
198 | [setup-gcloud]: https://github.com/google-github-actions/setup-gcloud
199 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2020 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | name: 'Get Secret Manager secrets'
16 | author: 'Google LLC'
17 | description: |-
18 | Get secrets from Google Secret Manager and make their results available as
19 | output variables.
20 |
21 | inputs:
22 | secrets:
23 | description: |-
24 | List of secrets to access and inject into the environment. These are
25 | comma-separated or newline-separated `OUTPUTNAME:SECRET`. Output names or
26 | secret names that contain separators must be escaped with a backslash
27 | (e.g. `\,` or `\\n`) unless quoted. Any leading or trailing whitespace is
28 | trimmed unless values are quoted.
29 |
30 | ```yaml
31 | secrets: |-
32 | output1:my-project/my-secret1
33 | output2:my-project/my-secret2
34 | ```
35 |
36 | Secrets can be referenced using the following formats:
37 |
38 | ```text
39 | # Long form
40 | projects//locations/ /secrets/ /locations/ /secrets/ / / //versions//versions/
65 | case 6: {
66 | if (refParts[2] === 'secrets') {
67 | this.project = refParts[1];
68 | this.name = refParts[3];
69 | this.version = refParts[5];
70 | break;
71 | } else if (refParts[2] === 'locations') {
72 | this.project = refParts[1];
73 | this.location = refParts[3];
74 | this.name = refParts[5];
75 | this.version = 'latest';
76 | break;
77 | } else {
78 | throw new TypeError(`Invalid reference "${s}" - unknown format`);
79 | }
80 | }
81 | // projects/ OR //
104 | case 2: {
105 | this.project = refParts[0];
106 | this.name = refParts[1];
107 | this.version = 'latest';
108 | break;
109 | }
110 | default: {
111 | throw new TypeError(`Invalid reference "${s}" - unknown format`);
112 | }
113 | }
114 | }
115 |
116 | /**
117 | * Returns the full GCP self link. For regional secrets, this will include the
118 | * location path.
119 | *
120 | * @returns String self link.
121 | */
122 | public selfLink(): string {
123 | if (this.location) {
124 | return `projects/${this.project}/locations/${this.location}/secrets/${this.name}/versions/${this.version}`;
125 | }
126 | return `projects/${this.project}/secrets/${this.name}/versions/${this.version}`;
127 | }
128 | }
129 |
130 | /**
131 | * Accepts the actions list of secrets and parses them as References.
132 | *
133 | * @param input List of secrets, from the actions input, can be
134 | * comma-delimited or newline, whitespace around secret entires is removed.
135 | * @param location String value of secret location/region
136 | * @returns Array of References for each secret, in the same order they were
137 | * given.
138 | */
139 | export function parseSecretsRefs(input: string): Reference[] {
140 | const secrets: Reference[] = [];
141 | for (const line of input.split(/\r|\n/)) {
142 | const pieces = parseCSV(line);
143 | for (const piece of pieces) {
144 | secrets.push(new Reference(piece));
145 | }
146 | }
147 | return secrets;
148 | }
149 |
--------------------------------------------------------------------------------
/tests/reference.test.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { test } from 'node:test';
18 | import assert from 'node:assert';
19 |
20 | import { Reference, parseSecretsRefs } from '../src/reference';
21 |
22 | test('Reference', { concurrency: true }, async (suite) => {
23 | const cases = [
24 | {
25 | input: 'out:projects/my-project/secrets/my-secret/versions/123',
26 | expected: 'projects/my-project/secrets/my-secret/versions/123',
27 | },
28 | {
29 | input: 'out:projects/my-project/secrets/my-secret',
30 | expected: 'projects/my-project/secrets/my-secret/versions/latest',
31 | },
32 | {
33 | input: 'out:projects/my-project/locations/my-location/secrets/my-secret',
34 | expected: 'projects/my-project/locations/my-location/secrets/my-secret/versions/latest',
35 | },
36 | {
37 | input: 'out:my-project/my-secret/123',
38 | expected: 'projects/my-project/secrets/my-secret/versions/123',
39 | },
40 | {
41 | input: 'out:my-project/my-secret',
42 | expected: 'projects/my-project/secrets/my-secret/versions/latest',
43 | },
44 | {
45 | input: 'out: my-project/my-secret',
46 | expected: 'projects/my-project/secrets/my-secret/versions/latest',
47 | },
48 | {
49 | input: 'out : projects/ my-project/ secrets/ my-secret',
50 | expected: 'projects/my-project/secrets/my-secret/versions/latest',
51 | },
52 | {
53 | input: '',
54 | error: 'TypeError',
55 | },
56 | {
57 | input: 'projects/my-project/secrets/my-secret/versions/123',
58 | error: 'TypeError',
59 | },
60 | {
61 | input: 'out:projects/my-project/pandas/my-location/secrets/my-secret',
62 | error: 'TypeErorr',
63 | },
64 | ];
65 |
66 | for await (const tc of cases) {
67 | if (tc.expected) {
68 | await suite.test(`parses "${tc.input}"`, async () => {
69 | const actual = new Reference(tc.input);
70 | assert.deepStrictEqual(actual.selfLink(), tc.expected);
71 | });
72 | } else if (tc.error) {
73 | await suite.test(`errors on "${tc.input}"`, async () => {
74 | await assert.rejects(async () => {
75 | new Reference(tc.input);
76 | }, tc.error);
77 | });
78 | }
79 | }
80 | });
81 |
82 | test('#parseSecretsRefs', { concurrency: true }, async (suite) => {
83 | const cases = [
84 | {
85 | name: 'empty string',
86 | input: '',
87 | location: '',
88 | expected: [],
89 | },
90 | {
91 | name: 'multi value commas',
92 | input: 'output1:project/secret, output2:project/secret',
93 | location: '',
94 | expected: [new Reference('output1:project/secret'), new Reference('output2:project/secret')],
95 | },
96 | {
97 | name: 'multi value newlines',
98 | input: 'output1:project/secret\noutput2:project/secret',
99 | location: '',
100 | expected: [new Reference('output1:project/secret'), new Reference('output2:project/secret')],
101 | },
102 | {
103 | name: 'multi value carriage',
104 | input: 'output1:project/secret\routput2:project/secret',
105 | location: '',
106 | expected: [new Reference('output1:project/secret'), new Reference('output2:project/secret')],
107 | },
108 | {
109 | name: 'multi value carriage newline',
110 | input: 'output1:project/secret\r\noutput2:project/secret',
111 | location: '',
112 | expected: [new Reference('output1:project/secret'), new Reference('output2:project/secret')],
113 | },
114 | {
115 | name: 'multi value empty lines',
116 | input: 'output1:project/secret\n\n\noutput2:project/secret',
117 | location: '',
118 | expected: [new Reference('output1:project/secret'), new Reference('output2:project/secret')],
119 | },
120 | {
121 | name: 'multi value commas',
122 | input: 'output1:project/secret\noutput2:project/secret,output3:project/secret',
123 | location: '',
124 | expected: [
125 | new Reference('output1:project/secret'),
126 | new Reference('output2:project/secret'),
127 | new Reference('output3:project/secret'),
128 | ],
129 | },
130 | {
131 | name: 'invalid input',
132 | input: 'not/valid',
133 | location: '',
134 | error: 'Invalid reference',
135 | },
136 | ];
137 |
138 | for await (const tc of cases) {
139 | await suite.test(tc.name, async () => {
140 | if (tc.expected) {
141 | const actual = parseSecretsRefs(tc.input);
142 | assert.deepStrictEqual(actual, tc.expected);
143 | } else if (tc.error) {
144 | await assert.rejects(async () => {
145 | parseSecretsRefs(tc.input);
146 | }, tc.error);
147 | }
148 | });
149 | }
150 | });
151 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | {
17 | "compilerOptions": {
18 | "target": "es6",
19 | "module": "commonjs",
20 | "lib": [
21 | "es6"
22 | ],
23 | "outDir": "./dist",
24 | "rootDir": "./src",
25 | "strict": true,
26 | "noImplicitAny": true,
27 | "esModuleInterop": true
28 | },
29 | "exclude": ["node_modules", "**/*.test.ts"]
30 | }
31 |
--------------------------------------------------------------------------------