@gtrevorrow/oci-token-exchange 1.0.0-20250417-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +35 -0
- package/README.md +329 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +45 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -0
- package/dist/main.d.ts +6 -0
- package/dist/main.js +366 -0
- package/dist/platforms/cli.d.ts +12 -0
- package/dist/platforms/cli.js +51 -0
- package/dist/platforms/github.d.ts +10 -0
- package/dist/platforms/github.js +70 -0
- package/dist/platforms/types.d.ts +18 -0
- package/dist/platforms/types.js +2 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.js +19 -0
- package/package.json +110 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Copyright (c) 2024 Oracle and/or its affiliates.
|
|
2
|
+
|
|
3
|
+
The Universal Permissive License (UPL), Version 1.0
|
|
4
|
+
|
|
5
|
+
Subject to the condition set forth below, permission is hereby granted to any
|
|
6
|
+
person obtaining a copy of this software, associated documentation and/or data
|
|
7
|
+
(collectively the "Software"), free of charge and under any and all copyright
|
|
8
|
+
rights in the Software, and any and all patent rights owned or freely
|
|
9
|
+
licensable by each licensor hereunder covering either (i) the unmodified
|
|
10
|
+
Software as contributed to or provided by such licensor, or (ii) the Larger
|
|
11
|
+
Works (as defined below), to deal in both
|
|
12
|
+
|
|
13
|
+
(a) the Software, and
|
|
14
|
+
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
|
|
15
|
+
one is included with the Software (each a "Larger Work" to which the Software
|
|
16
|
+
is contributed by such licensors),
|
|
17
|
+
|
|
18
|
+
without restriction, including without limitation the rights to copy, create
|
|
19
|
+
derivative works of, display, perform, and distribute the Software and make,
|
|
20
|
+
use, sell, offer for sale, import, export, have made, and have sold the
|
|
21
|
+
Software and the Larger Work(s), and to sublicense the foregoing rights on
|
|
22
|
+
either these or other terms.
|
|
23
|
+
|
|
24
|
+
This license is subject to the following condition:
|
|
25
|
+
The above copyright notice and either this complete permission notice or at
|
|
26
|
+
a minimum a reference to the UPL must be included in all copies or
|
|
27
|
+
substantial portions of the Software.
|
|
28
|
+
|
|
29
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
30
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
31
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
32
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
33
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
34
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
35
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# OCI Token Exchange
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Installation](#installation)
|
|
6
|
+
- [As GitHub Action](#as-github-action)
|
|
7
|
+
- [As CLI Tool](#as-cli-tool)
|
|
8
|
+
- [Usage](#usage)
|
|
9
|
+
- [GitHub Actions](#github-actions)
|
|
10
|
+
- [GitLab CI](#gitlab-ci)
|
|
11
|
+
- [Option 1: Building from Source](#option-1-building-from-source)
|
|
12
|
+
- [Option 2: Using npm Package](#option-2-using-npm-package)
|
|
13
|
+
- [Bitbucket Pipelines](#bitbucket-pipelines)
|
|
14
|
+
- [Option 1: Building from Source](#option-1-building-from-source-1)
|
|
15
|
+
- [Option 2: Using npm Package](#option-2-using-npm-package-1)
|
|
16
|
+
- [Standalone CLI Usage](#standalone-cli-usage)
|
|
17
|
+
- [Debugging](#debugging)
|
|
18
|
+
- [Environment Variables / Github Secrets](#environment-variables--github-secrets)
|
|
19
|
+
- [Environment Variable Handling](#environment-variable-handling)
|
|
20
|
+
- [How it Works](#how-it-works)
|
|
21
|
+
- [Semantic Versioning](#semantic-versioning)
|
|
22
|
+
- [License](#license)
|
|
23
|
+
- [Contributing](#contributing)
|
|
24
|
+
|
|
25
|
+
# OCI Token Exchange
|
|
26
|
+
|
|
27
|
+
A tool to exchange OIDC tokens for [OCI session tokens](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/clitoken.htm), supporting multiple CI platforms:
|
|
28
|
+
- GitHub Actions
|
|
29
|
+
- GitLab CI
|
|
30
|
+
- Bitbucket Pipelines
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### As GitHub Action
|
|
35
|
+
```yaml
|
|
36
|
+
- uses: gtrevorrow/oci-token-exchange-action@v1
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### As CLI Tool
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g @gtrevorrow/oci-token-exchange
|
|
42
|
+
|
|
43
|
+
# Install the latest version globally
|
|
44
|
+
npm install -g @gtrevorrow/oci-token-exchange
|
|
45
|
+
|
|
46
|
+
# Install a specific version globally (e.g., 1.2.3)
|
|
47
|
+
npm install -g @gtrevorrow/oci-token-exchange@1.2.3
|
|
48
|
+
|
|
49
|
+
# Install a version with a specific tag (e.g., beta)
|
|
50
|
+
npm install -g @gtrevorrow/oci-token-exchange@beta
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
### GitHub Actions
|
|
56
|
+
```yaml
|
|
57
|
+
- uses: gtrevorrow/oci-token-exchange-action@v1
|
|
58
|
+
with:
|
|
59
|
+
oidc_client_identifier: ${{ secrets.OIDC_CLIENT_ID }}
|
|
60
|
+
domain_base_url: ${{ secrets.DOMAIN_URL }}
|
|
61
|
+
oci_tenancy: ${{ secrets.OCI_TENANCY }}
|
|
62
|
+
oci_region: ${{ secrets.OCI_REGION }}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### GitLab CI
|
|
66
|
+
|
|
67
|
+
#### Option 1: Building from Source
|
|
68
|
+
|
|
69
|
+
This example clones the repository, builds the CLI, and then runs it.
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
# Use these YAML anchors to setup common tasks
|
|
73
|
+
.oci_setup: &oci_setup |
|
|
74
|
+
curl -LO https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh
|
|
75
|
+
bash install.sh --accept-all-defaults
|
|
76
|
+
source ~/.bashrc
|
|
77
|
+
|
|
78
|
+
.clone_build_cli: &clone_build_cli |
|
|
79
|
+
git clone https://github.com/gtrevorrow/oci-token-exchange-action.git
|
|
80
|
+
cd oci-token-exchange-action
|
|
81
|
+
npm ci
|
|
82
|
+
npm run build:cli
|
|
83
|
+
|
|
84
|
+
deploy:
|
|
85
|
+
script:
|
|
86
|
+
# Install OCI CLI
|
|
87
|
+
- *oci_setup
|
|
88
|
+
|
|
89
|
+
# Clone and build the token exchange CLI
|
|
90
|
+
- *clone_build_cli
|
|
91
|
+
|
|
92
|
+
# Export token from GitLab CI
|
|
93
|
+
- export CI_JOB_JWT_V2="$(cat $CI_JOB_JWT_FILE)"
|
|
94
|
+
|
|
95
|
+
# Run the built CLI
|
|
96
|
+
- |
|
|
97
|
+
cd dist &&
|
|
98
|
+
PLATFORM=gitlab \
|
|
99
|
+
OIDC_CLIENT_ID=${OIDC_CLIENT_ID} \
|
|
100
|
+
DOMAIN_URL=${DOMAIN_URL} \
|
|
101
|
+
OCI_TENANCY=${OCI_TENANCY} \
|
|
102
|
+
OCI_REGION=${OCI_REGION} \
|
|
103
|
+
RETRY_COUNT=3 \
|
|
104
|
+
node cli.js
|
|
105
|
+
|
|
106
|
+
# Verify OCI CLI configuration works
|
|
107
|
+
- cd ../..
|
|
108
|
+
- oci os ns get
|
|
109
|
+
|
|
110
|
+
# Run only on main branch
|
|
111
|
+
rules:
|
|
112
|
+
- if: $CI_COMMIT_BRANCH == "main"
|
|
113
|
+
|
|
114
|
+
# Configure OIDC token for GitLab
|
|
115
|
+
id_tokens:
|
|
116
|
+
ID_TOKEN:
|
|
117
|
+
aud: https://cloud.oracle.com/gitlab
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### Option 2: Using npm Package
|
|
121
|
+
|
|
122
|
+
This example installs the CLI tool directly from npm.
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
# Use these YAML anchors to setup common tasks
|
|
126
|
+
.oci_setup: &oci_setup |
|
|
127
|
+
curl -LO https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh
|
|
128
|
+
bash install.sh --accept-all-defaults
|
|
129
|
+
source ~/.bashrc
|
|
130
|
+
|
|
131
|
+
deploy_npm:
|
|
132
|
+
script:
|
|
133
|
+
# Install OCI CLI
|
|
134
|
+
- *oci_setup
|
|
135
|
+
|
|
136
|
+
# Install the token exchange CLI from npm
|
|
137
|
+
- npm install -g @gtrevorrow/oci-token-exchange
|
|
138
|
+
|
|
139
|
+
# Export token from GitLab CI
|
|
140
|
+
- export CI_JOB_JWT_V2="$(cat $CI_JOB_JWT_FILE)"
|
|
141
|
+
|
|
142
|
+
# Run the installed CLI
|
|
143
|
+
- |
|
|
144
|
+
PLATFORM=gitlab \
|
|
145
|
+
OIDC_CLIENT_ID=${OIDC_CLIENT_ID} \
|
|
146
|
+
DOMAIN_URL=${DOMAIN_URL} \
|
|
147
|
+
OCI_TENANCY=${OCI_TENANCY} \
|
|
148
|
+
OCI_REGION=${OCI_REGION} \
|
|
149
|
+
RETRY_COUNT=3 \
|
|
150
|
+
oci-token-exchange
|
|
151
|
+
|
|
152
|
+
# Verify OCI CLI configuration works
|
|
153
|
+
- oci os ns get
|
|
154
|
+
|
|
155
|
+
# Run only on main branch
|
|
156
|
+
rules:
|
|
157
|
+
- if: $CI_COMMIT_BRANCH == "main"
|
|
158
|
+
|
|
159
|
+
# Configure OIDC token for GitLab
|
|
160
|
+
id_tokens:
|
|
161
|
+
ID_TOKEN:
|
|
162
|
+
aud: https://cloud.oracle.com/gitlab
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Bitbucket Pipelines
|
|
166
|
+
|
|
167
|
+
#### Option 1: Building from Source
|
|
168
|
+
|
|
169
|
+
This example clones the repository, builds the CLI, and then runs it.
|
|
170
|
+
|
|
171
|
+
```yaml
|
|
172
|
+
image: node:20
|
|
173
|
+
|
|
174
|
+
pipelines:
|
|
175
|
+
default:
|
|
176
|
+
- step:
|
|
177
|
+
name: Setup OCI CLI with OIDC Token Exchange (Build from Source)
|
|
178
|
+
oidc: true # Enable OIDC for Bitbucket
|
|
179
|
+
script:
|
|
180
|
+
# Setup OCI CLI
|
|
181
|
+
- curl -LO https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh
|
|
182
|
+
- bash install.sh --accept-all-defaults
|
|
183
|
+
- export PATH=$PATH:/root/bin
|
|
184
|
+
|
|
185
|
+
# Clone and build the token exchange CLI from GitHub
|
|
186
|
+
- git clone https://github.com/gtrevorrow/oci-token-exchange-action.git
|
|
187
|
+
- cd oci-token-exchange-action
|
|
188
|
+
- npm ci
|
|
189
|
+
- npm run build:cli
|
|
190
|
+
|
|
191
|
+
# Run the built CLI for token exchange
|
|
192
|
+
- >
|
|
193
|
+
cd dist &&
|
|
194
|
+
export PLATFORM=bitbucket &&
|
|
195
|
+
export OIDC_CLIENT_ID=${OIDC_CLIENT_ID} &&
|
|
196
|
+
export DOMAIN_URL=${DOMAIN_URL} &&
|
|
197
|
+
export OCI_TENANCY=${OCI_TENANCY} &&
|
|
198
|
+
export OCI_REGION=${OCI_REGION} &&
|
|
199
|
+
export RETRY_COUNT=3
|
|
200
|
+
- node cli.js || exit 1
|
|
201
|
+
|
|
202
|
+
# Verify OCI CLI works with generated token
|
|
203
|
+
- cd ../..
|
|
204
|
+
- oci os ns get
|
|
205
|
+
|
|
206
|
+
# Preserve credentials for subsequent steps
|
|
207
|
+
artifacts:
|
|
208
|
+
- ".oci/**"
|
|
209
|
+
- "private_key.pem"
|
|
210
|
+
- "public_key.pem"
|
|
211
|
+
- "session"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
#### Option 2: Using npm Package
|
|
215
|
+
|
|
216
|
+
This example installs the CLI tool directly from npm.
|
|
217
|
+
|
|
218
|
+
```yaml
|
|
219
|
+
image: node:20
|
|
220
|
+
|
|
221
|
+
pipelines:
|
|
222
|
+
default:
|
|
223
|
+
- step:
|
|
224
|
+
name: Setup OCI CLI with OIDC Token Exchange (npm Package)
|
|
225
|
+
oidc: true # Enable OIDC for Bitbucket
|
|
226
|
+
script:
|
|
227
|
+
# Setup OCI CLI
|
|
228
|
+
- curl -LO https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh
|
|
229
|
+
- bash install.sh --accept-all-defaults
|
|
230
|
+
- export PATH=$PATH:/root/bin
|
|
231
|
+
|
|
232
|
+
# Install the token exchange CLI from npm
|
|
233
|
+
- npm install -g @gtrevorrow/oci-token-exchange
|
|
234
|
+
|
|
235
|
+
# Run the installed CLI for token exchange
|
|
236
|
+
- >
|
|
237
|
+
export PLATFORM=bitbucket &&
|
|
238
|
+
export OIDC_CLIENT_ID=${OIDC_CLIENT_ID} &&
|
|
239
|
+
export DOMAIN_URL=${DOMAIN_URL} &&
|
|
240
|
+
export OCI_TENANCY=${OCI_TENANCY} &&
|
|
241
|
+
export OCI_REGION=${OCI_REGION} &&
|
|
242
|
+
export RETRY_COUNT=3
|
|
243
|
+
- oci-token-exchange || exit 1
|
|
244
|
+
|
|
245
|
+
# Verify OCI CLI works with generated token
|
|
246
|
+
- oci os ns get
|
|
247
|
+
|
|
248
|
+
# Preserve credentials for subsequent steps
|
|
249
|
+
artifacts:
|
|
250
|
+
- ".oci/**"
|
|
251
|
+
- "private_key.pem"
|
|
252
|
+
- "public_key.pem"
|
|
253
|
+
- "session"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Standalone CLI Usage
|
|
257
|
+
```bash
|
|
258
|
+
# Install globally
|
|
259
|
+
npm install -g @gtrevorrow/oci-token-exchange
|
|
260
|
+
|
|
261
|
+
# Run with required environment variables
|
|
262
|
+
export LOCAL_OIDC_TOKEN="your.jwt.token"
|
|
263
|
+
PLATFORM=local \
|
|
264
|
+
OIDC_CLIENT_ID=your-client-id \
|
|
265
|
+
DOMAIN_URL=https://your-domain.identity.oraclecloud.com \
|
|
266
|
+
OCI_TENANCY=your-tenancy-ocid \
|
|
267
|
+
OCI_REGION=your-region \
|
|
268
|
+
oci-token-exchange
|
|
269
|
+
|
|
270
|
+
# Use the configured OCI CLI
|
|
271
|
+
oci os ns get
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Debugging
|
|
275
|
+
|
|
276
|
+
To enable detailed logging, set the `DEBUG` environment variable to `true`:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
export DEBUG=true
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
This will log additional information, such as token exchange requests and responses, to help with troubleshooting.
|
|
283
|
+
|
|
284
|
+
## Environment Variables / Github Secrets
|
|
285
|
+
|
|
286
|
+
The action supports flexible environment variable naming to make it easier to use across different platforms:
|
|
287
|
+
|
|
288
|
+
| Variable | Alternate Names | Description | Required |
|
|
289
|
+
|----------|----------------|-------------|----------|
|
|
290
|
+
| `OIDC_CLIENT_IDENTIFIER` | `INPUT_OIDC_CLIENT_IDENTIFIER` | OIDC client identifier | Yes |
|
|
291
|
+
| `DOMAIN_BASE_URL` | `INPUT_DOMAIN_BASE_URL` | Base URL of OCI Identity Domain | Yes |
|
|
292
|
+
| `OCI_TENANCY` | `INPUT_OCI_TENANCY` | OCI tenancy OCID | Yes |
|
|
293
|
+
| `OCI_REGION` | `INPUT_OCI_REGION` | OCI region identifier | Yes |
|
|
294
|
+
| `PLATFORM` | `INPUT_PLATFORM` | CI platform (`github`, `gitlab`, `bitbucket`, or `local`) | No (default: `github`) |
|
|
295
|
+
| `RETRY_COUNT` | `INPUT_RETRY_COUNT` | Number of retry attempts | No (default: `0`) |
|
|
296
|
+
| `LOCAL_OIDC_TOKEN` | - | OIDC token when using PLATFORM=local | Yes, when platform=local |
|
|
297
|
+
| `CI_JOB_JWT_V2` | - | GitLab CI JWT token | Yes, when platform=gitlab |
|
|
298
|
+
| `BITBUCKET_STEP_OIDC_TOKEN` | - | Bitbucket OIDC token | Yes, when platform=bitbucket |
|
|
299
|
+
| `DEBUG` | - | Enable debug output | No (default: `false`) |
|
|
300
|
+
|
|
301
|
+
### Environment Variable Handling
|
|
302
|
+
|
|
303
|
+
Variables can be provided in two ways:
|
|
304
|
+
|
|
305
|
+
1. **GitHub Actions Format**: Variables in the format `INPUT_VARIABLE_NAME` (used by GitHub Actions)
|
|
306
|
+
2. **Standard Format**: Plain environment variables matching the exact names listed above
|
|
307
|
+
|
|
308
|
+
The GitHub Action automatically maps variables from GitHub's format to the standard format, but if you're using the CLI directly, use the variable names exactly as shown above.
|
|
309
|
+
|
|
310
|
+
## How it Works
|
|
311
|
+
|
|
312
|
+
1. Generates an RSA key pair
|
|
313
|
+
2. Requests a GitHub OIDC JWT token
|
|
314
|
+
3. Exchanges the JWT for an OCI UPST token
|
|
315
|
+
4. Configures the OCI CLI with the obtained credentials
|
|
316
|
+
|
|
317
|
+
## Semantic Versioning
|
|
318
|
+
|
|
319
|
+
This project uses [semantic-release](https://github.com/semantic-release/semantic-release) for automated versioning and publishing.
|
|
320
|
+
**For details on the build and release process, see [CONTRIBUTING.md](./CONTRIBUTING.md).**
|
|
321
|
+
|
|
322
|
+
## License
|
|
323
|
+
|
|
324
|
+
This action is licensed under the [Universal Permissive License v1.0 (UPL-1.0)](LICENSE.txt).
|
|
325
|
+
|
|
326
|
+
## Contributing
|
|
327
|
+
|
|
328
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
329
|
+
````
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const main_1 = require("./main");
|
|
5
|
+
// Map environment variables to GitHub Actions input format
|
|
6
|
+
// This mapping provides flexibility for users to follow different naming conventions
|
|
7
|
+
const envVarMappings = {
|
|
8
|
+
// Standard CLI env vars
|
|
9
|
+
PLATFORM: 'platform',
|
|
10
|
+
OIDC_CLIENT_IDENTIFIER: 'oidc_client_identifier',
|
|
11
|
+
DOMAIN_BASE_URL: 'domain_base_url',
|
|
12
|
+
OCI_TENANCY: 'oci_tenancy',
|
|
13
|
+
OCI_REGION: 'oci_region',
|
|
14
|
+
RETRY_COUNT: 'retry_count',
|
|
15
|
+
// Support for directly providing GitHub Actions style input vars
|
|
16
|
+
// This prevents needless remapping if already in correct format
|
|
17
|
+
INPUT_PLATFORM: 'platform',
|
|
18
|
+
INPUT_OIDC_CLIENT_IDENTIFIER: 'oidc_client_identifier',
|
|
19
|
+
INPUT_DOMAIN_BASE_URL: 'domain_base_url',
|
|
20
|
+
INPUT_OCI_TENANCY: 'oci_tenancy',
|
|
21
|
+
INPUT_OCI_REGION: 'oci_region',
|
|
22
|
+
INPUT_RETRY_COUNT: 'retry_count'
|
|
23
|
+
};
|
|
24
|
+
// Set environment variables in GitHub Actions format only if not already set
|
|
25
|
+
Object.entries(envVarMappings).forEach(([envVar, inputName]) => {
|
|
26
|
+
const value = process.env[envVar];
|
|
27
|
+
if (value && !process.env[`INPUT_${inputName.toUpperCase()}`]) {
|
|
28
|
+
process.env[`INPUT_${inputName.toUpperCase()}`] = value;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
// Enable debug mode via environment variable
|
|
32
|
+
if (process.env.DEBUG === 'true') {
|
|
33
|
+
console.log('Debug mode enabled');
|
|
34
|
+
console.log('Environment variables mapped:');
|
|
35
|
+
Object.entries(envVarMappings).forEach(([envVar, inputName]) => {
|
|
36
|
+
if (process.env[envVar]) {
|
|
37
|
+
console.log(`${envVar} → INPUT_${inputName.toUpperCase()}`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// Run the main function
|
|
42
|
+
(0, main_1.main)().catch(error => {
|
|
43
|
+
console.error('Error:', error instanceof Error ? error.message : 'Unknown error');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './main';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./main"), exports);
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Platform } from './platforms/types';
|
|
2
|
+
import { TokenExchangeConfig, OciConfig, UpstTokenResponse, TokenExchangeError } from './types';
|
|
3
|
+
export declare function tokenExchangeJwtToUpst(platform: Platform, { tokenExchangeURL, clientCred, ociPublicKey, subjectToken, retryCount, currentAttempt }: TokenExchangeConfig): Promise<UpstTokenResponse>;
|
|
4
|
+
export declare function configureOciCli(platform: Platform, config: OciConfig): Promise<void>;
|
|
5
|
+
export declare function main(): Promise<void>;
|
|
6
|
+
export { TokenExchangeError, TokenExchangeConfig, OciConfig };
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.TokenExchangeError = void 0;
|
|
40
|
+
exports.tokenExchangeJwtToUpst = tokenExchangeJwtToUpst;
|
|
41
|
+
exports.configureOciCli = configureOciCli;
|
|
42
|
+
exports.main = main;
|
|
43
|
+
/**
|
|
44
|
+
* Copyright (c) 2021, 2024 Oracle and/or its affiliates.
|
|
45
|
+
* Licensed under the Universal Permissive License v1.0 as shown at https://oss.oracle.com/licenses/upl.
|
|
46
|
+
*/
|
|
47
|
+
const fs = __importStar(require("fs/promises"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
50
|
+
const axios_1 = __importDefault(require("axios"));
|
|
51
|
+
const github_1 = require("./platforms/github");
|
|
52
|
+
const cli_1 = require("./platforms/cli");
|
|
53
|
+
const types_1 = require("./types");
|
|
54
|
+
Object.defineProperty(exports, "TokenExchangeError", { enumerable: true, get: function () { return types_1.TokenExchangeError; } });
|
|
55
|
+
const PLATFORM_CONFIGS = {
|
|
56
|
+
github: {
|
|
57
|
+
audience: 'https://cloud.oracle.com'
|
|
58
|
+
},
|
|
59
|
+
gitlab: {
|
|
60
|
+
tokenEnvVar: 'CI_JOB_JWT_V2',
|
|
61
|
+
audience: 'https://cloud.oracle.com/gitlab'
|
|
62
|
+
},
|
|
63
|
+
bitbucket: {
|
|
64
|
+
tokenEnvVar: 'BITBUCKET_STEP_OIDC_TOKEN',
|
|
65
|
+
audience: 'https://cloud.oracle.com/bitbucket'
|
|
66
|
+
},
|
|
67
|
+
local: {
|
|
68
|
+
tokenEnvVar: 'LOCAL_OIDC_TOKEN',
|
|
69
|
+
audience: 'https://cloud.oracle.com'
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
// Create platform instance based on environment
|
|
73
|
+
function createPlatform(platformType) {
|
|
74
|
+
const config = PLATFORM_CONFIGS[platformType];
|
|
75
|
+
if (!config) {
|
|
76
|
+
throw new Error(`Unsupported platform: ${platformType}`);
|
|
77
|
+
}
|
|
78
|
+
return platformType === 'github' ? new github_1.GitHubPlatform() : new cli_1.CLIPlatform(config);
|
|
79
|
+
}
|
|
80
|
+
// Generate RSA key pair
|
|
81
|
+
const { publicKey, privateKey } = crypto_1.default.generateKeyPairSync('rsa', {
|
|
82
|
+
modulusLength: 2048
|
|
83
|
+
});
|
|
84
|
+
async function delay(count) {
|
|
85
|
+
return new Promise(resolve => setTimeout(resolve, 1000 * count));
|
|
86
|
+
}
|
|
87
|
+
// Encode public key in a format the OCI token exchange endpoint expects
|
|
88
|
+
function encodePublicKeyToBase64() {
|
|
89
|
+
return publicKey.export({ type: 'spki', format: 'der' }).toString('base64');
|
|
90
|
+
}
|
|
91
|
+
// Calculate the fingerprint of the OCI API public key
|
|
92
|
+
function calcFingerprint(publicKey) {
|
|
93
|
+
const publicKeyData = publicKey.export({ type: 'spki', format: 'der' });
|
|
94
|
+
const hash = crypto_1.default.createHash('MD5');
|
|
95
|
+
hash.update(publicKeyData);
|
|
96
|
+
return hash.digest('hex').replace(/(.{2})/g, '$1:').slice(0, -1);
|
|
97
|
+
}
|
|
98
|
+
// Function to validate URLs
|
|
99
|
+
function isValidUrl(url) {
|
|
100
|
+
try {
|
|
101
|
+
new URL(url);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
catch (_) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Function to exchange JWT for OCI UPST token
|
|
109
|
+
async function tokenExchangeJwtToUpst(platform, { tokenExchangeURL, clientCred, ociPublicKey, subjectToken, retryCount, currentAttempt = 0 }) {
|
|
110
|
+
const headers = {
|
|
111
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
112
|
+
'Authorization': `Basic ${clientCred}`
|
|
113
|
+
};
|
|
114
|
+
if (subjectToken && platform.isDebug()) {
|
|
115
|
+
// Do some basic validation on the JWT token that we are going to exchange
|
|
116
|
+
try {
|
|
117
|
+
const parts = subjectToken.split('.');
|
|
118
|
+
// Check if the token is already a valid JWT (has at least 2 periods)
|
|
119
|
+
if (parts.length == 3) {
|
|
120
|
+
// If not well-formed, it might be a raw JWT that needs to be formatted
|
|
121
|
+
platform.logger.debug(' OIDC token does not appear to be a properly formatted JWT, attempting to parse');
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// Try to parse the token segments to validate it's a proper(ish) JWT
|
|
125
|
+
// Note: This is a very basic check and does not guarantee the token's validity
|
|
126
|
+
const header = JSON.parse(Buffer.from(parts[0], 'base64').toString());
|
|
127
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
|
|
128
|
+
platform.logger.debug(`JWT appears structured as expected with a header , payload and signature . Issuer: ${payload.iss || 'unknown'}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
platform.logger.warning(`Error pre-processing OIDC token: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
133
|
+
// Continue with the original token, as we may be mistaken about its format
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const data = {
|
|
137
|
+
'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
|
|
138
|
+
'requested_token_type': 'urn:oci:token-type:oci-upst',
|
|
139
|
+
'public_key': ociPublicKey,
|
|
140
|
+
'subject_token': subjectToken,
|
|
141
|
+
'subject_token_type': 'jwt'
|
|
142
|
+
};
|
|
143
|
+
// Note that this will log potentially sensitive information but will leave it up to the user to decide if they want to enable debug logging with this risk
|
|
144
|
+
platform.logger.debug('Token Exchange Request Data: ' + JSON.stringify(data));
|
|
145
|
+
try {
|
|
146
|
+
const response = await axios_1.default.post(tokenExchangeURL, data, { headers });
|
|
147
|
+
platform.logger.debug('Token Exchange Response: ' + JSON.stringify(response.data));
|
|
148
|
+
return response.data; // auto wrapped in a Promise
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
const attemptCounter = currentAttempt ? currentAttempt : 0;
|
|
152
|
+
if (retryCount > 0 && retryCount >= attemptCounter) {
|
|
153
|
+
platform.logger.warning(`Token exchange failed, retrying ... (${retryCount - attemptCounter - 1} retries left)`);
|
|
154
|
+
await delay(attemptCounter + 1);
|
|
155
|
+
return tokenExchangeJwtToUpst(platform, {
|
|
156
|
+
tokenExchangeURL,
|
|
157
|
+
clientCred,
|
|
158
|
+
ociPublicKey,
|
|
159
|
+
subjectToken: subjectToken,
|
|
160
|
+
retryCount,
|
|
161
|
+
currentAttempt: attemptCounter + 1
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
platform.logger.error('Failed to exchange JWT for UPST after multiple attempts');
|
|
166
|
+
if (error instanceof Error) {
|
|
167
|
+
throw new types_1.TokenExchangeError(`Token exchange failed: ${error.message}`, error);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
throw new types_1.TokenExchangeError('Token exchange failed with an unknown error');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Update configureOciCli to accept platform as first parameter
|
|
176
|
+
async function configureOciCli(platform, config) {
|
|
177
|
+
try {
|
|
178
|
+
const home = process.env.HOME || '';
|
|
179
|
+
if (!home) {
|
|
180
|
+
throw new types_1.TokenExchangeError('HOME environment variable is not defined');
|
|
181
|
+
}
|
|
182
|
+
// Sanitize file paths to prevent path injection
|
|
183
|
+
const ociConfigDir = path.resolve(path.join(home, '.oci'));
|
|
184
|
+
const ociConfigFile = path.resolve(path.join(ociConfigDir, 'config'));
|
|
185
|
+
const ociPrivateKeyFile = path.resolve(path.join(home, 'private_key.pem'));
|
|
186
|
+
const ociPublicKeyFile = path.resolve(path.join(home, 'public_key.pem'));
|
|
187
|
+
const upstTokenFile = path.resolve(path.join(home, 'session'));
|
|
188
|
+
platform.logger.debug(`OCI Config Dir: ${ociConfigDir}`);
|
|
189
|
+
const ociConfig = `[DEFAULT]
|
|
190
|
+
user='not used'
|
|
191
|
+
fingerprint=${config.ociFingerprint}
|
|
192
|
+
key_file=${ociPrivateKeyFile}
|
|
193
|
+
tenancy=${config.ociTenancy}
|
|
194
|
+
region=${config.ociRegion}
|
|
195
|
+
security_token_file=${upstTokenFile}
|
|
196
|
+
`;
|
|
197
|
+
try {
|
|
198
|
+
await fs.mkdir(ociConfigDir, { recursive: true });
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
throw new Error('Unable to create OCI Config folder');
|
|
202
|
+
}
|
|
203
|
+
platform.logger.debug(`Created OCI Config : ${ociConfig}`);
|
|
204
|
+
try {
|
|
205
|
+
// Use await/try-catch for fs.access instead of chaining then/catch
|
|
206
|
+
try {
|
|
207
|
+
await fs.access(ociConfigFile);
|
|
208
|
+
platform.logger.warning(`Overwriting existing config file at ${ociConfigFile}`);
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
// File does not exist, proceed silently
|
|
212
|
+
}
|
|
213
|
+
// Export and validate keys first
|
|
214
|
+
const privateKeyPem = config.privateKey.export({ type: 'pkcs1', format: 'pem' });
|
|
215
|
+
const publicKeyPem = config.publicKey.export({ type: 'spki', format: 'pem' });
|
|
216
|
+
if (!privateKeyPem || typeof privateKeyPem !== 'string') {
|
|
217
|
+
throw new Error('Private key export failed or invalid type');
|
|
218
|
+
}
|
|
219
|
+
if (!publicKeyPem || typeof publicKeyPem !== 'string') {
|
|
220
|
+
throw new Error('Public key export failed or invalid type');
|
|
221
|
+
}
|
|
222
|
+
if (!config.upstToken || typeof config.upstToken !== 'string') {
|
|
223
|
+
throw new Error('Session token is undefined or invalid type');
|
|
224
|
+
}
|
|
225
|
+
if (!ociConfig || typeof ociConfig !== 'string') {
|
|
226
|
+
throw new Error('OCI config is undefined or invalid type');
|
|
227
|
+
}
|
|
228
|
+
platform.logger.debug('Validated all file contents before writing');
|
|
229
|
+
await Promise.all([
|
|
230
|
+
fs.writeFile(ociConfigFile, ociConfig)
|
|
231
|
+
.then(() => platform.logger.debug(`Successfully wrote OCI config to ${ociConfigFile}`)),
|
|
232
|
+
fs.writeFile(ociPrivateKeyFile, privateKeyPem)
|
|
233
|
+
.then(() => fs.chmod(ociPrivateKeyFile, '600'))
|
|
234
|
+
.then(() => platform.logger.debug(`Successfully wrote private key to ${ociPrivateKeyFile} with permissions 600`)),
|
|
235
|
+
fs.writeFile(ociPublicKeyFile, publicKeyPem)
|
|
236
|
+
.then(() => platform.logger.debug(`Successfully wrote public key to ${ociPublicKeyFile}`)),
|
|
237
|
+
fs.writeFile(upstTokenFile, config.upstToken)
|
|
238
|
+
.then(() => fs.chmod(upstTokenFile, '600'))
|
|
239
|
+
.then(() => platform.logger.debug(`Successfully wrote session token to ${upstTokenFile}`))
|
|
240
|
+
]);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
throw new types_1.TokenExchangeError('Failed to write OCI configuration files', error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
platform.setFailed(`Failed to configure OCI CLI: ${error}`);
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Update debugPrintJWTToken to properly handle different token formats
|
|
252
|
+
function debugPrintJWTToken(platform, token) {
|
|
253
|
+
if (platform.isDebug()) {
|
|
254
|
+
platform.logger.debug(`JWT Token received (length: ${token.length} characters)`);
|
|
255
|
+
try {
|
|
256
|
+
const tokenParts = token.split('.');
|
|
257
|
+
if (tokenParts.length !== 3) {
|
|
258
|
+
platform.logger.debug(`Warning: JWT token does not have the expected format (header.payload.signature)`);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
// Only decode and print the header and selected parts of payload, not the full token
|
|
262
|
+
const headerStr = Buffer.from(tokenParts[0], 'base64').toString('utf8');
|
|
263
|
+
let header;
|
|
264
|
+
try {
|
|
265
|
+
header = JSON.parse(headerStr);
|
|
266
|
+
platform.logger.debug(`JWT Header: ${JSON.stringify(header)}`);
|
|
267
|
+
}
|
|
268
|
+
catch (e) {
|
|
269
|
+
platform.logger.debug(`Failed to parse JWT header: ${headerStr}`);
|
|
270
|
+
}
|
|
271
|
+
// Parse payload but only log safe information
|
|
272
|
+
try {
|
|
273
|
+
const payloadStr = Buffer.from(tokenParts[1], 'base64').toString('utf8');
|
|
274
|
+
const payload = JSON.parse(payloadStr);
|
|
275
|
+
const safePayload = {
|
|
276
|
+
iss: payload.iss,
|
|
277
|
+
aud: payload.aud,
|
|
278
|
+
exp: payload.exp,
|
|
279
|
+
iat: payload.iat,
|
|
280
|
+
sub: payload.sub ? `${payload.sub.substring(0, 10)}...` : undefined,
|
|
281
|
+
// Include timestamp information for troubleshooting token expiry issues
|
|
282
|
+
expires_at: payload.exp ? new Date(payload.exp * 1000).toISOString() : undefined,
|
|
283
|
+
issued_at: payload.iat ? new Date(payload.iat * 1000).toISOString() : undefined
|
|
284
|
+
};
|
|
285
|
+
platform.logger.debug(`JWT Payload (safe parts): ${JSON.stringify(safePayload)}`);
|
|
286
|
+
}
|
|
287
|
+
catch (e) {
|
|
288
|
+
platform.logger.debug(`Failed to parse JWT payload: ${e instanceof Error ? e.message : 'Unknown error'}`);
|
|
289
|
+
}
|
|
290
|
+
platform.logger.debug(`JWT Signature present: ${tokenParts[2].length > 0 ? 'Yes' : 'No'}`);
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
platform.logger.debug(`Error parsing JWT token: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Main function now creates a local platform instance and passes it to subfunctions
|
|
298
|
+
async function main() {
|
|
299
|
+
const platformType = process.env.PLATFORM || 'github';
|
|
300
|
+
if (!PLATFORM_CONFIGS[platformType]) {
|
|
301
|
+
throw new Error(`Unsupported platform: ${platformType}`);
|
|
302
|
+
}
|
|
303
|
+
const platform = createPlatform(platformType);
|
|
304
|
+
try {
|
|
305
|
+
// Use typed object for config
|
|
306
|
+
const config = ['oidc_client_identifier', 'domain_base_url', 'oci_tenancy', 'oci_region']
|
|
307
|
+
.reduce((acc, input) => ({
|
|
308
|
+
...acc,
|
|
309
|
+
[input]: platform.getInput(input, true)
|
|
310
|
+
}), {});
|
|
311
|
+
const retryCount = parseInt(platform.getInput('retry_count', false) || '0');
|
|
312
|
+
if (isNaN(retryCount) || retryCount < 0) {
|
|
313
|
+
throw new Error('retry_count must be a non-negative number');
|
|
314
|
+
}
|
|
315
|
+
// Validate the tokenExchangeURL
|
|
316
|
+
if (!isValidUrl(`${config.domain_base_url}/oauth2/v1/token`)) {
|
|
317
|
+
throw new Error('Invalid domain_base_url provided');
|
|
318
|
+
}
|
|
319
|
+
const idToken = await platform.getOIDCToken(PLATFORM_CONFIGS[platformType].audience);
|
|
320
|
+
platform.logger.debug(`Token obtained from ${platformType}`);
|
|
321
|
+
debugPrintJWTToken(platform, idToken);
|
|
322
|
+
// Calculate the fingerprint of the public key
|
|
323
|
+
const ociFingerprint = calcFingerprint(publicKey);
|
|
324
|
+
// Get the B64 encoded public key DER
|
|
325
|
+
const publicKeyB64 = encodePublicKeyToBase64();
|
|
326
|
+
platform.logger.debug(`Public Key B64: ${publicKeyB64}`);
|
|
327
|
+
//Exchange platform OIDC token for OCI UPST
|
|
328
|
+
const upstToken = await tokenExchangeJwtToUpst(platform, {
|
|
329
|
+
tokenExchangeURL: `${config.domain_base_url}/oauth2/v1/token`,
|
|
330
|
+
clientCred: Buffer.from(config.oidc_client_identifier).toString('base64'),
|
|
331
|
+
ociPublicKey: publicKeyB64,
|
|
332
|
+
subjectToken: idToken,
|
|
333
|
+
retryCount
|
|
334
|
+
});
|
|
335
|
+
platform.logger.info(`OCI issued a Session Token `);
|
|
336
|
+
//Setup the OCI cli/sdk on the CI platform runner with the UPST token
|
|
337
|
+
const ociConfig = {
|
|
338
|
+
privateKey,
|
|
339
|
+
publicKey,
|
|
340
|
+
upstToken: upstToken.token,
|
|
341
|
+
ociFingerprint,
|
|
342
|
+
ociTenancy: config.oci_tenancy,
|
|
343
|
+
ociRegion: config.oci_region
|
|
344
|
+
};
|
|
345
|
+
await configureOciCli(platform, ociConfig);
|
|
346
|
+
platform.logger.info(`OCI CLI has been configured to use the session token`);
|
|
347
|
+
// Add success output
|
|
348
|
+
platform.setOutput('configured', 'true');
|
|
349
|
+
// Error Handling
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
if (error instanceof types_1.TokenExchangeError) {
|
|
353
|
+
platform.setFailed(`Token exchange failed: ${error.message}`);
|
|
354
|
+
if (error.cause) {
|
|
355
|
+
platform.logger.debug(`Cause: ${error.cause}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
platform.setFailed(`Action failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
360
|
+
}
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (require.main === module) {
|
|
365
|
+
main();
|
|
366
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Platform, PlatformLogger, PlatformConfig } from './types';
|
|
2
|
+
export declare class CLIPlatform implements Platform {
|
|
3
|
+
private config;
|
|
4
|
+
private readonly _logger;
|
|
5
|
+
constructor(config: PlatformConfig);
|
|
6
|
+
getInput(name: string, required?: boolean): string;
|
|
7
|
+
setOutput(name: string, value: string): void;
|
|
8
|
+
setFailed(message: string): void;
|
|
9
|
+
isDebug(): boolean;
|
|
10
|
+
getOIDCToken(audience: string): Promise<string>;
|
|
11
|
+
get logger(): PlatformLogger;
|
|
12
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CLIPlatform = void 0;
|
|
4
|
+
class CLIPlatform {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
this._logger = {
|
|
8
|
+
debug: (message) => { if (this.isDebug())
|
|
9
|
+
console.debug(message); },
|
|
10
|
+
info: (message) => console.log(message),
|
|
11
|
+
warning: (message) => console.warn(message),
|
|
12
|
+
error: (message) => console.error(message)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
getInput(name, required = false) {
|
|
16
|
+
// Then check for direct environment variable (using common naming conventions)
|
|
17
|
+
const value = process.env[name.toUpperCase()] ||
|
|
18
|
+
process.env[`INPUT_${name.toUpperCase()}`] ||
|
|
19
|
+
process.env[`OCI_${name.toUpperCase()}`] ||
|
|
20
|
+
process.env[`OIDC_${name.toUpperCase()}`] || '';
|
|
21
|
+
if (required && !value) {
|
|
22
|
+
throw new Error(`Input required and not supplied: ${name}`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
setOutput(name, value) {
|
|
27
|
+
console.log(`::set-output name=${name}::${value}`);
|
|
28
|
+
}
|
|
29
|
+
setFailed(message) {
|
|
30
|
+
console.error(message);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
isDebug() {
|
|
34
|
+
return process.env.DEBUG === 'true';
|
|
35
|
+
}
|
|
36
|
+
async getOIDCToken(audience) {
|
|
37
|
+
if (this.config.tokenEnvVar) {
|
|
38
|
+
const token = process.env[this.config.tokenEnvVar];
|
|
39
|
+
if (!token) {
|
|
40
|
+
throw new Error(`${this.config.tokenEnvVar} environment variable not found`);
|
|
41
|
+
}
|
|
42
|
+
// Do not log the token here
|
|
43
|
+
return token;
|
|
44
|
+
}
|
|
45
|
+
throw new Error('No OIDC token configuration available');
|
|
46
|
+
}
|
|
47
|
+
get logger() {
|
|
48
|
+
return this._logger;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.CLIPlatform = CLIPlatform;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Platform, PlatformLogger } from './types';
|
|
2
|
+
export declare class GitHubPlatform implements Platform {
|
|
3
|
+
private readonly _logger;
|
|
4
|
+
getInput(name: string, required?: boolean): string;
|
|
5
|
+
setOutput(name: string, value: string): void;
|
|
6
|
+
setFailed(message: string): void;
|
|
7
|
+
isDebug(): boolean;
|
|
8
|
+
getOIDCToken(audience: string): Promise<string>;
|
|
9
|
+
get logger(): PlatformLogger;
|
|
10
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.GitHubPlatform = void 0;
|
|
37
|
+
const core = __importStar(require("@actions/core"));
|
|
38
|
+
class GitHubPlatform {
|
|
39
|
+
constructor() {
|
|
40
|
+
this._logger = {
|
|
41
|
+
debug: (message) => core.debug(message),
|
|
42
|
+
info: (message) => core.info(message),
|
|
43
|
+
warning: (message) => core.warning(message),
|
|
44
|
+
error: (message) => core.error(message)
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
getInput(name, required = false) {
|
|
48
|
+
return core.getInput(name, { required });
|
|
49
|
+
}
|
|
50
|
+
setOutput(name, value) {
|
|
51
|
+
core.setOutput(name, value);
|
|
52
|
+
}
|
|
53
|
+
setFailed(message) {
|
|
54
|
+
core.setFailed(message);
|
|
55
|
+
}
|
|
56
|
+
isDebug() {
|
|
57
|
+
return core.isDebug();
|
|
58
|
+
}
|
|
59
|
+
async getOIDCToken(audience) {
|
|
60
|
+
const token = await core.getIDToken(audience);
|
|
61
|
+
if (!token) {
|
|
62
|
+
throw new Error('Failed to get OIDC token from GitHub Actions');
|
|
63
|
+
}
|
|
64
|
+
return token;
|
|
65
|
+
}
|
|
66
|
+
get logger() {
|
|
67
|
+
return this._logger;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.GitHubPlatform = GitHubPlatform;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface PlatformLogger {
|
|
2
|
+
debug(message: string): void;
|
|
3
|
+
info(message: string): void;
|
|
4
|
+
warning(message: string): void;
|
|
5
|
+
error(message: string): void;
|
|
6
|
+
}
|
|
7
|
+
export interface Platform {
|
|
8
|
+
getInput(name: string, required?: boolean): string;
|
|
9
|
+
setOutput(name: string, value: string): void;
|
|
10
|
+
setFailed(message: string): void;
|
|
11
|
+
isDebug(): boolean;
|
|
12
|
+
logger: PlatformLogger;
|
|
13
|
+
getOIDCToken(audience: string): Promise<string>;
|
|
14
|
+
}
|
|
15
|
+
export interface PlatformConfig {
|
|
16
|
+
tokenEnvVar?: string;
|
|
17
|
+
audience: string;
|
|
18
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for token exchange operation
|
|
4
|
+
*/
|
|
5
|
+
export interface TokenExchangeConfig {
|
|
6
|
+
/**
|
|
7
|
+
* URL of the token exchange endpoint
|
|
8
|
+
*/
|
|
9
|
+
tokenExchangeURL: string;
|
|
10
|
+
/**
|
|
11
|
+
* Base64-encoded client credentials
|
|
12
|
+
*/
|
|
13
|
+
clientCred: string;
|
|
14
|
+
/**
|
|
15
|
+
* Base64-encoded DER format of the OCI public key
|
|
16
|
+
*/
|
|
17
|
+
ociPublicKey: string;
|
|
18
|
+
/**
|
|
19
|
+
* JWT token to be exchanged
|
|
20
|
+
*/
|
|
21
|
+
subjectToken: string;
|
|
22
|
+
/**
|
|
23
|
+
* Number of retry attempts for token exchange
|
|
24
|
+
*/
|
|
25
|
+
retryCount: number;
|
|
26
|
+
/**
|
|
27
|
+
* Current attempt number (used internally for retries)
|
|
28
|
+
*/
|
|
29
|
+
currentAttempt?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Configuration for the OCI CLI setup
|
|
33
|
+
*/
|
|
34
|
+
export interface OciConfig {
|
|
35
|
+
/**
|
|
36
|
+
* Private key used for OCI authentication
|
|
37
|
+
*/
|
|
38
|
+
privateKey: crypto.KeyObject;
|
|
39
|
+
/**
|
|
40
|
+
* Public key used for OCI authentication
|
|
41
|
+
*/
|
|
42
|
+
publicKey: crypto.KeyObject;
|
|
43
|
+
/**
|
|
44
|
+
* User principal security token obtained from token exchange
|
|
45
|
+
*/
|
|
46
|
+
upstToken: string;
|
|
47
|
+
/**
|
|
48
|
+
* Fingerprint of the public key
|
|
49
|
+
*/
|
|
50
|
+
ociFingerprint: string;
|
|
51
|
+
/**
|
|
52
|
+
* OCI tenancy OCID
|
|
53
|
+
*/
|
|
54
|
+
ociTenancy: string;
|
|
55
|
+
/**
|
|
56
|
+
* OCI region identifier
|
|
57
|
+
*/
|
|
58
|
+
ociRegion: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Input configuration for the CLI/Action
|
|
62
|
+
*/
|
|
63
|
+
export interface ConfigInputs {
|
|
64
|
+
oidc_client_identifier: string;
|
|
65
|
+
domain_base_url: string;
|
|
66
|
+
oci_tenancy: string;
|
|
67
|
+
oci_region: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Response from token exchange operation
|
|
71
|
+
*/
|
|
72
|
+
export interface UpstTokenResponse {
|
|
73
|
+
/**
|
|
74
|
+
* The exchanged UPST token
|
|
75
|
+
*/
|
|
76
|
+
token: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Custom error class for token exchange errors
|
|
80
|
+
*/
|
|
81
|
+
export declare class TokenExchangeError extends Error {
|
|
82
|
+
readonly cause?: unknown | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Creates a new TokenExchangeError
|
|
85
|
+
* @param message Error message
|
|
86
|
+
* @param cause Underlying cause of the error
|
|
87
|
+
*/
|
|
88
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
89
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TokenExchangeError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Custom error class for token exchange errors
|
|
6
|
+
*/
|
|
7
|
+
class TokenExchangeError extends Error {
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new TokenExchangeError
|
|
10
|
+
* @param message Error message
|
|
11
|
+
* @param cause Underlying cause of the error
|
|
12
|
+
*/
|
|
13
|
+
constructor(message, cause) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.cause = cause;
|
|
16
|
+
this.name = 'TokenExchangeError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.TokenExchangeError = TokenExchangeError;
|
package/package.json
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gtrevorrow/oci-token-exchange",
|
|
3
|
+
"description": "OCI Usder Principal Token Exchange for GitHub Actions, GitLab CI, and Bitbucket Pipelines",
|
|
4
|
+
"version": "1.0.0-20250417-beta",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Oracle Cloud Infrastructure",
|
|
7
|
+
"email": "gordon.trevorrow@oracle.com"
|
|
8
|
+
},
|
|
9
|
+
"private": false,
|
|
10
|
+
"homepage": "https://github.com/gtrevorrow/oci-token-exchange-action/blob/README.md",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/gtrevorrow/oci-token-exchange-action.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/gtrevorrow/oci-token-exchange-action/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"github",
|
|
20
|
+
"actions",
|
|
21
|
+
"oracle-cloud",
|
|
22
|
+
"oracle-cloud-infrastructure"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": "./dist/main.js"
|
|
26
|
+
},
|
|
27
|
+
"bin": {
|
|
28
|
+
"oci-token-exchange": "./dist/cli.js"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc",
|
|
35
|
+
"bundle": "npm run format:write && npm run package",
|
|
36
|
+
"format:write": "npx prettier --write .",
|
|
37
|
+
"format:check": "npx prettier --check .",
|
|
38
|
+
"lint": "npx eslint . -c ./.github/linters/.eslintrc.yml",
|
|
39
|
+
"package": "npx ncc build src/index.ts -o dist --source-map --license LICENSE.txt",
|
|
40
|
+
"all": "npm run format:write && npm run lint && npm run package",
|
|
41
|
+
"test": "jest",
|
|
42
|
+
"build:cli": "tsc && chmod +x dist/cli.js",
|
|
43
|
+
"package:cli": "npm run build:cli && npm pack",
|
|
44
|
+
"prepare": "husky install",
|
|
45
|
+
"release": "semantic-release",
|
|
46
|
+
"commit": "cz"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist",
|
|
50
|
+
"LICENSE.txt",
|
|
51
|
+
"README.md"
|
|
52
|
+
],
|
|
53
|
+
"license": "UPL-1.0",
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@actions/core": "^1.11.1",
|
|
56
|
+
"@actions/exec": "^1.1.1",
|
|
57
|
+
"@actions/github": "^6.0.0",
|
|
58
|
+
"@actions/io": "^1.1.3",
|
|
59
|
+
"@actions/tool-cache": "^2.0.2",
|
|
60
|
+
"axios": "^1.8.4"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@commitlint/cli": "^19.8.0",
|
|
64
|
+
"@commitlint/config-conventional": "^19.8.0",
|
|
65
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
66
|
+
"@semantic-release/git": "^10.0.1",
|
|
67
|
+
"@types/jest": "^29.5.13",
|
|
68
|
+
"@types/node": "^20.17.11",
|
|
69
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
70
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
71
|
+
"@vercel/ncc": "^0.38.1",
|
|
72
|
+
"commitizen": "^4.3.0",
|
|
73
|
+
"cz-conventional-changelog": "^3.3.0",
|
|
74
|
+
"eslint": "^8.57.1",
|
|
75
|
+
"eslint-config-prettier": "^9.1.0",
|
|
76
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
77
|
+
"husky": "^8.0.0",
|
|
78
|
+
"jest": "^29.7.0",
|
|
79
|
+
"list": "^2.0.19",
|
|
80
|
+
"npm": "^11.0.0",
|
|
81
|
+
"prettier": "^3.3.3",
|
|
82
|
+
"prettier-eslint": "^16.3.0",
|
|
83
|
+
"semantic-release": "^22.0.0",
|
|
84
|
+
"test-jest": "^1.0.1",
|
|
85
|
+
"ts-jest": "^29.2.5",
|
|
86
|
+
"typescript": "^5.8.2",
|
|
87
|
+
"typescript-eslint": "^8.1.0"
|
|
88
|
+
},
|
|
89
|
+
"publishConfig": {
|
|
90
|
+
"access": "public"
|
|
91
|
+
},
|
|
92
|
+
"release": {
|
|
93
|
+
"branches": [
|
|
94
|
+
"main"
|
|
95
|
+
],
|
|
96
|
+
"plugins": [
|
|
97
|
+
"@semantic-release/commit-analyzer",
|
|
98
|
+
"@semantic-release/release-notes-generator",
|
|
99
|
+
"@semantic-release/changelog",
|
|
100
|
+
"@semantic-release/npm",
|
|
101
|
+
"@semantic-release/github",
|
|
102
|
+
"@semantic-release/git"
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
"config": {
|
|
106
|
+
"commitizen": {
|
|
107
|
+
"path": "cz-conventional-changelog"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|