Compare commits
91 Commits
v1.0.2
...
eddy/cli-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61def4eb0f | ||
|
|
8fd274c5eb | ||
|
|
f4303b27ca | ||
|
|
f4a99d4598 | ||
|
|
9c1afd6054 | ||
|
|
a02ee663cc | ||
|
|
663ac229cb | ||
|
|
9bb44334eb | ||
|
|
1ec261f63f | ||
|
|
2e386ac304 | ||
|
|
0b706bbe43 | ||
|
|
e23df52c69 | ||
|
|
ade3078eb5 | ||
|
|
4ee6567f7b | ||
|
|
27b96b5fd8 | ||
|
|
14687e434a | ||
|
|
1a17146422 | ||
|
|
94dcd16f05 | ||
|
|
4cd70e1a09 | ||
|
|
b30803866e | ||
|
|
feb6ed7c04 | ||
|
|
d669a8ba06 | ||
|
|
936f62b7d9 | ||
|
|
95681075d8 | ||
|
|
0a7975f916 | ||
|
|
ffba2a6966 | ||
|
|
2ee4979efa | ||
|
|
7903600d82 | ||
|
|
fbf9be8f55 | ||
|
|
5a04ae581c | ||
|
|
747c0b5974 | ||
|
|
c0fbfd88d3 | ||
|
|
3f3d1e45cb | ||
|
|
b73c8a7ca6 | ||
|
|
da6de9b6b3 | ||
|
|
7610c5737f | ||
|
|
30b5930e91 | ||
|
|
c2c5cda6a2 | ||
|
|
351ed34750 | ||
|
|
be04d82018 | ||
|
|
8c15b6c54d | ||
|
|
cb83ae04bf | ||
|
|
e3b137e007 | ||
|
|
30def81a24 | ||
|
|
ed1f9a48af | ||
|
|
b1b82d7384 | ||
|
|
a37c95c8d0 | ||
|
|
aaee1916c6 | ||
|
|
c53c263a7e | ||
|
|
7d858c7ad5 | ||
|
|
ce8b31d0b9 | ||
|
|
2ac8886444 | ||
|
|
641b0d93ec | ||
|
|
c27a045581 | ||
|
|
953b51736f | ||
|
|
2f338e16af | ||
|
|
e9bd76c87a | ||
|
|
d5280efa32 | ||
|
|
8d99fc2a1e | ||
|
|
5c5bbcbaf0 | ||
|
|
e1b37a5b1e | ||
|
|
2a214a29d3 | ||
|
|
2a4f64c09d | ||
|
|
df0b228eb9 | ||
|
|
8052f86fc6 | ||
|
|
34bed60a89 | ||
|
|
ada03a0325 | ||
|
|
c7774c7068 | ||
|
|
1263fc888c | ||
|
|
858b6a838e | ||
|
|
b38b493d73 | ||
|
|
bb28cdf0c6 | ||
|
|
302881bfef | ||
|
|
9fdfd04a6f | ||
|
|
b38d01591f | ||
|
|
d78889ad20 | ||
|
|
6c02b8909f | ||
|
|
cb24269fc9 | ||
|
|
300c776a97 | ||
|
|
2faffa0507 | ||
|
|
da5dd0865d | ||
|
|
4af3346b6a | ||
|
|
fae9e58c4f | ||
|
|
000522e32f | ||
|
|
4baca64066 | ||
|
|
ac12d2e3c4 | ||
|
|
e8da10d005 | ||
|
|
5add51bcb8 | ||
|
|
08df44393f | ||
|
|
478705935c | ||
|
|
e64093d691 |
9
.github/workflows/lint.yml
vendored
9
.github/workflows/lint.yml
vendored
@@ -5,6 +5,9 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: ShellCheck
|
||||
uses: ludeeus/action-shellcheck@1.1.0
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run ShellCheck
|
||||
uses: ludeeus/action-shellcheck@2.0.0
|
||||
with:
|
||||
ignore_paths: >-
|
||||
.husky
|
||||
|
||||
129
.github/workflows/test.yml
vendored
129
.github/workflows/test.yml
vendored
@@ -2,50 +2,135 @@ on: push
|
||||
name: Run acceptance tests
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
test-with-output-secrets:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, macos-latest ]
|
||||
auth: [ connect, service-account ]
|
||||
exclude:
|
||||
- os: macos-latest
|
||||
auth: connect
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Launch 1Password Connect instance
|
||||
if: ${{ matrix.auth == 'connect' }}
|
||||
env:
|
||||
OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }}
|
||||
run: |
|
||||
echo "$OP_CONNECT_CREDENTIALS" > 1password-credentials.json
|
||||
docker-compose -f tests/fixtures/docker-compose.yml up -d && sleep 10
|
||||
- name: Configure Service account
|
||||
if: ${{ matrix.auth == 'service-account' }}
|
||||
uses: ./configure
|
||||
with:
|
||||
service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||
- name: Configure 1Password Connect
|
||||
if: ${{ matrix.auth == 'connect' }}
|
||||
uses: ./configure # 1password/load-secrets-action/configure@<version>
|
||||
with:
|
||||
connect-host: localhost:8080
|
||||
connect-token: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||
- name: Load secrets
|
||||
id: load_secrets
|
||||
uses: ./ # 1password/load-secrets-action@<version>
|
||||
with:
|
||||
export-env: false
|
||||
env:
|
||||
SECRET: op://acceptance-tests/test-secret/password
|
||||
SECRET_IN_SECTION: op://acceptance-tests/test-secret/test-section/password
|
||||
MULTILINE_SECRET: op://acceptance-tests/multiline-secret/notesPlain
|
||||
- name: Assert test secret values
|
||||
env:
|
||||
SECRET: ${{ steps.load_secrets.outputs.SECRET }}
|
||||
SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.SECRET_IN_SECTION }}
|
||||
MULTILINE_SECRET: ${{ steps.load_secrets.outputs.MULTILINE_SECRET }}
|
||||
run: ./tests/assert-env-set.sh
|
||||
test-with-export-env:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, macos-latest ]
|
||||
auth: [ connect, service-account ]
|
||||
exclude:
|
||||
- os: macos-latest
|
||||
auth: connect
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Launch 1Password Connect instance
|
||||
if: ${{ matrix.auth == 'connect' }}
|
||||
env:
|
||||
OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }}
|
||||
run: |
|
||||
echo "$OP_CONNECT_CREDENTIALS" > 1password-credentials.json
|
||||
docker-compose -f tests/fixtures/docker-compose.yml up -d && sleep 10
|
||||
- name: Configure Service account
|
||||
if: ${{ matrix.auth == 'service-account' }}
|
||||
uses: ./configure
|
||||
with:
|
||||
service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||
- name: Configure 1Password Connect
|
||||
if: ${{ matrix.auth == 'connect' }}
|
||||
uses: ./configure # 1password/load-secrets-action/configure@<version>
|
||||
with:
|
||||
connect-host: http://localhost:8080
|
||||
connect-token: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||
- name: Load secrets
|
||||
id: load_secrets
|
||||
uses: ./ # 1password/load-secrets-action@<version>
|
||||
env:
|
||||
SECRET: op://v5pz6venw4roosmkzdq2nhpv6u/hrgkzhrlvscomepxlgafb2m3ca/password
|
||||
SECRET_IN_SECTION: op://v5pz6venw4roosmkzdq2nhpv6u/hrgkzhrlvscomepxlgafb2m3ca/test-section/password
|
||||
UNMASKED_VALUE: op://v5pz6venw4roosmkzdq2nhpv6u/hrgkzhrlvscomepxlgafb2m3ca/test-section/username
|
||||
- name: Load multiline secret
|
||||
uses: ./ # 1password/load-secrets-action@<version>
|
||||
env:
|
||||
MULTILINE_SECRET: op://v5pz6venw4roosmkzdq2nhpv6u/ghtz3jvcc6dqmzc53d3r3eskge/notesPlain
|
||||
- name: Print environment variables with masked secrets
|
||||
run: printenv
|
||||
SECRET: op://acceptance-tests/test-secret/password
|
||||
SECRET_IN_SECTION: op://acceptance-tests/test-secret/test-section/password
|
||||
MULTILINE_SECRET: op://acceptance-tests/multiline-secret/notesPlain
|
||||
- name: Assert test secret values
|
||||
run: ./tests/assert-env-set.sh
|
||||
- name: Remove secrets
|
||||
uses: ./ # 1password/load-secrets-action@<version>
|
||||
with:
|
||||
unset-previous: true
|
||||
- name: Print environment variables with secrets removed
|
||||
run: printenv
|
||||
- name: Assert removed secrets
|
||||
run: ./tests/assert-env-unset.sh
|
||||
- name: Load secrets by vault and item titles
|
||||
uses: ./ # 1password/load-secrets-action@<version>
|
||||
test-references-with-ids:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, macos-latest ]
|
||||
auth: [ connect, service-account ]
|
||||
exclude:
|
||||
- os: macos-latest
|
||||
auth: connect
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Launch 1Password Connect instance
|
||||
if: ${{ matrix.auth == 'connect' }}
|
||||
env:
|
||||
SECRET: op://acceptance-tests/test-secret/password
|
||||
SECRET_IN_SECTION: op://acceptance-tests/test-secret/test-section/password
|
||||
MULTILINE_SECRET: op://acceptance-tests/multiline-secret/notesPlain
|
||||
- name: Print environment variables with masked secrets
|
||||
run: printenv
|
||||
- name: Assert test secret values again
|
||||
OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }}
|
||||
run: |
|
||||
echo "$OP_CONNECT_CREDENTIALS" > 1password-credentials.json
|
||||
docker-compose -f tests/fixtures/docker-compose.yml up -d && sleep 10
|
||||
- name: Configure Service account
|
||||
if: ${{ matrix.auth == 'service-account' }}
|
||||
uses: ./configure
|
||||
with:
|
||||
service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||
- name: Configure 1Password Connect
|
||||
if: ${{ matrix.auth == 'connect' }}
|
||||
uses: ./configure # 1password/load-secrets-action/configure@<version>
|
||||
with:
|
||||
connect-host: http://localhost:8080
|
||||
connect-token: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||
- name: Load secrets
|
||||
id: load_secrets
|
||||
uses: ./ # 1password/load-secrets-action@<version>
|
||||
with:
|
||||
export-env: false
|
||||
env:
|
||||
SECRET: op://v5pz6venw4roosmkzdq2nhpv6u/hrgkzhrlvscomepxlgafb2m3ca/password
|
||||
SECRET_IN_SECTION: op://v5pz6venw4roosmkzdq2nhpv6u/hrgkzhrlvscomepxlgafb2m3ca/Section_tco6nsqycj6jcbyx63h5isxcny/doxu3mhkozcznnk5vjrkpdqayy
|
||||
MULTILINE_SECRET: op://v5pz6venw4roosmkzdq2nhpv6u/ghtz3jvcc6dqmzc53d3r3eskge/notesPlain
|
||||
- name: Assert test secret values
|
||||
env:
|
||||
SECRET: ${{ steps.load_secrets.outputs.SECRET }}
|
||||
SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.SECRET_IN_SECTION }}
|
||||
MULTILINE_SECRET: ${{ steps.load_secrets.outputs.MULTILINE_SECRET }}
|
||||
run: ./tests/assert-env-set.sh
|
||||
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
coverage/
|
||||
node_modules/
|
||||
378
README.md
378
README.md
@@ -1,30 +1,89 @@
|
||||
# Load Secrets from 1Password - GitHub Action
|
||||
|
||||
The action to load secrets from [1Password Connect](https://1password.com/secrets/) into GitHub Actions.
|
||||
This action loads secrets from 1Password into GitHub Actions using [1Password Connect](https://developer.1password.com/docs/connect) or a [Service Account <sup>[BETA]</sup>](https://developer.1password.com/docs/service-accounts).
|
||||
|
||||
Specify right from your workflow YAML which secrets from 1Password should be loaded into your job, and the action will make them available as environment variables for the next steps.
|
||||
Specify in your workflow YAML file which secrets from 1Password should be loaded into your job, and the action will make them available as environment variables for the next steps.
|
||||
|
||||
Read more on the [1Password Developer Portal](https://developer.1password.com/docs/ci-cd/github-actions).
|
||||
|
||||
## Requirements
|
||||
|
||||
Before you get started, you'll need to:
|
||||
|
||||
- [Deploy 1Password Connect](/docs/connect/get-started#step-2-deploy-1password-connect-server) in your infrastructure.
|
||||
- Set the `OP_CONNECT_HOST` and `OP_CONNECT_TOKEN` environment variables to your Connect instance's credentials, so it'll be used to load secrets.
|
||||
|
||||
_Supported runners_: You can run the action on Mac and Linux runners. Windows is currently not supported.
|
||||
|
||||
## Usage
|
||||
|
||||
You can configure the action to use your 1Password Connect instance.
|
||||
|
||||
If you provide `OP_CONNECT_HOST` and `OP_CONNECT_TOKEN` variables, the Connect instance will be used to load secrets. Make sure [1Password Connect](https://support.1password.com/secrets-automation/#step-2-deploy-a-1password-connect-server) is deployed in your infrastructure.
|
||||
|
||||
If you provide `OP_SERVICE_ACCOUNT_TOKEN` variable, the service account will be used to load secrets.
|
||||
|
||||
**_Note_**: If all environment variables have been set, the Connect credentials will take precedence over the provided service account token. You must unset the Connect environment variables to ensure the action uses the service account token.
|
||||
|
||||
There are two ways that secrets can be loaded:
|
||||
|
||||
- [use the secrets from the action's ouput](#use-secrets-from-the-actions-output)
|
||||
- [export secrets as environment variables](#export-secrets-as-environment-variables)
|
||||
|
||||
### Use secrets from the action's output
|
||||
|
||||
This method allows for you to use the loaded secrets as an output from the step: `steps.step-id.outputs.secret-name`. You will need to set an id for the step that uses this action to be able to access its outputs. For more details, , see [`outputs.<output_id>`](https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#outputsoutput_id).
|
||||
|
||||
```yml
|
||||
on: push
|
||||
jobs:
|
||||
hello-world:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Load secret
|
||||
id: op-load-secret
|
||||
uses: 1password/load-secrets-action@v1
|
||||
with:
|
||||
export-env: false
|
||||
env:
|
||||
OP_CONNECT_HOST: <Your Connect instance URL>
|
||||
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||
SECRET: op://app-cicd/hello-world/secret
|
||||
|
||||
- name: Print masked secret
|
||||
run: echo "Secret: $SECRET"
|
||||
run: echo "Secret: ${{ steps.op-load-secret.outputs.SECRET }}"
|
||||
# Prints: Secret: ***
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary><b>Usage example with Service Accounts <sup>BETA</sup></b></summary>
|
||||
|
||||
```yml
|
||||
on: push
|
||||
jobs:
|
||||
hello-world:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Load secret
|
||||
id: op-load-secret
|
||||
uses: 1password/load-secrets-action@v1
|
||||
with:
|
||||
export-env: false
|
||||
env:
|
||||
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||
SECRET: op://app-cicd/hello-world/secret
|
||||
|
||||
- name: Print masked secret
|
||||
run: echo "Secret: ${{ steps.op-load-secret.outputs.SECRET }}"
|
||||
# Prints: Secret: ***
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Longer usage example</b></summary>
|
||||
|
||||
@@ -36,25 +95,172 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Configure 1Password Connect
|
||||
uses: 1password/load-secrets-action/configure@v1
|
||||
with:
|
||||
# Persist the 1Password Connect URL for next steps. You can also persist
|
||||
# the Connect token using input `connect-token`, but keep in mind that
|
||||
# every single step in the job would then be able to access the token.
|
||||
# Persist the 1Password Connect URL for next steps. You can also persist
|
||||
# the Connect token using input `connect-token`, but keep in mind that
|
||||
# this will grant all steps of the job access to the token.
|
||||
connect-host: https://1password.acme.com
|
||||
|
||||
- name: Load Docker credentials
|
||||
id: load-docker-credentials
|
||||
uses: 1password/load-secrets-action@v1
|
||||
with:
|
||||
export-env: false
|
||||
env:
|
||||
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||
DOCKERHUB_USERNAME: op://app-cicd/docker/username
|
||||
DOCKERHUB_TOKEN: op://app-cicd/docker/token
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ steps.load-docker-credentials.outputs.DOCKERHUB_USERNAME }}
|
||||
password: ${{ steps.load-docker-credentials.outputs.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
push: true
|
||||
tags: acme/app:latest
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Longer usage example with Service Accounts <sup>BETA</sup></b></summary>
|
||||
|
||||
```yml
|
||||
on: push
|
||||
name: Deploy app
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Configure 1Password Connect
|
||||
uses: 1password/load-secrets-action/configure@v1
|
||||
with:
|
||||
# Persist the 1Password Service Account token. This will grant
|
||||
# all steps of the job access to the token.
|
||||
service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||
|
||||
- name: Load Docker credentials
|
||||
id: load-docker-credentials
|
||||
uses: 1password/load-secrets-action@v1
|
||||
with:
|
||||
export-env: false
|
||||
env:
|
||||
DOCKERHUB_USERNAME: op://app-cicd/docker/username
|
||||
DOCKERHUB_TOKEN: op://app-cicd/docker/token
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ steps.load-docker-credentials.outputs.DOCKERHUB_USERNAME }}
|
||||
password: ${{ steps.load-docker-credentials.outputs.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
push: true
|
||||
tags: acme/app:latest
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Export secrets as environment variables
|
||||
|
||||
This method, allows the action to access the loaded secrets as environment variables. These environment variables are accessible at a job level.
|
||||
|
||||
```yml
|
||||
on: push
|
||||
jobs:
|
||||
hello-world:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Load secret
|
||||
uses: 1password/load-secrets-action@v1
|
||||
with:
|
||||
# Export loaded secrets as environment variables
|
||||
export-env: true
|
||||
env:
|
||||
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||
SECRET: op://app-cicd/hello-world/secret
|
||||
|
||||
- name: Print masked secret
|
||||
run: echo "Secret: $SECRET"
|
||||
# Prints: Secret: ***
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary><b>Usage example with Service Accounts <sup>BETA</sup></b></summary>
|
||||
|
||||
```yml
|
||||
on: push
|
||||
jobs:
|
||||
hello-world:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Load secret
|
||||
uses: 1password/load-secrets-action@v1
|
||||
with:
|
||||
# Export loaded secrets as environment variables
|
||||
export-env: true
|
||||
env:
|
||||
OP_CONNECT_HOST: <Your Connect instance URL>
|
||||
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||
SECRET: op://app-cicd/hello-world/secret
|
||||
|
||||
- name: Print masked secret
|
||||
run: echo "Secret: $SECRET"
|
||||
# Prints: Secret: ***
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Longer usage example</b></summary>
|
||||
|
||||
```yml
|
||||
on: push
|
||||
name: Deploy app
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Configure 1Password Connect
|
||||
uses: 1password/load-secrets-action/configure@v1
|
||||
with:
|
||||
# Persist the 1Password Connect URL for next steps. You can also persist
|
||||
# the Connect token using input `connect-token`, but keep in mind that
|
||||
# this will grant all steps of the job access to the token.
|
||||
connect-host: https://1password.acme.com
|
||||
|
||||
- name: Load Docker credentials
|
||||
uses: 1password/load-secrets-action@v1
|
||||
with:
|
||||
# Export loaded secrets as environment variables
|
||||
export-env: true
|
||||
env:
|
||||
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||
DOCKERHUB_USERNAME: op://app-cicd/docker/username
|
||||
DOCKERHUB_TOKEN: op://app-cicd/docker/token
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ env.DOCKERHUB_USERNAME }}
|
||||
password: ${{ env.DOCKERHUB_TOKEN }}
|
||||
@@ -63,7 +269,7 @@ jobs:
|
||||
run: printenv
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
push: true
|
||||
tags: acme/app:latest
|
||||
@@ -71,7 +277,9 @@ jobs:
|
||||
- name: Load AWS credentials
|
||||
uses: 1password/load-secrets-action@v1
|
||||
with:
|
||||
# Remove local copies of the Docker credentials, which are not needed anymore
|
||||
# Export loaded secrets as environment variables
|
||||
export-env: true
|
||||
# Remove local copies of the Docker credentials, which aren't needed anymore
|
||||
unset-previous: true
|
||||
env:
|
||||
OP_CONNECT_TOKEN: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||
@@ -79,16 +287,81 @@ jobs:
|
||||
AWS_SECRET_ACCESS_KEY: op://app-cicd/aws/secret-access-key
|
||||
|
||||
- name: Deploy app
|
||||
# This script expects AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to be set, which was
|
||||
# done automatically by the step above
|
||||
# This script expects AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to be set.
|
||||
# This happened using secret references in the preceding lines.
|
||||
run: ./deploy.sh
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Longer usage example with Service Accounts <sup>BETA</sup></b></summary>
|
||||
|
||||
```yml
|
||||
on: push
|
||||
name: Deploy app
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Configure 1Password Connect
|
||||
uses: 1password/load-secrets-action/configure@v1
|
||||
with:
|
||||
# Persist the 1Password Service Account token. This will grant
|
||||
# all steps of the job access to the token.
|
||||
service-account-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||
|
||||
- name: Load Docker credentials
|
||||
uses: 1password/load-secrets-action@v1
|
||||
with:
|
||||
# Export loaded secrets as environment variables
|
||||
export-env: true
|
||||
env:
|
||||
DOCKERHUB_USERNAME: op://app-cicd/docker/username
|
||||
DOCKERHUB_TOKEN: op://app-cicd/docker/token
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ env.DOCKERHUB_USERNAME }}
|
||||
password: ${{ env.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Print environment variables with masked secrets
|
||||
run: printenv
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
push: true
|
||||
tags: acme/app:latest
|
||||
|
||||
- name: Load AWS credentials
|
||||
uses: 1password/load-secrets-action@v1
|
||||
with:
|
||||
# Export loaded secrets as environment variables
|
||||
export-env: true
|
||||
# Remove local copies of the Docker credentials, which aren't needed anymore
|
||||
unset-previous: true
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: op://app-cicd/aws/access-key-id
|
||||
AWS_SECRET_ACCESS_KEY: op://app-cicd/aws/secret-access-key
|
||||
|
||||
- name: Deploy app
|
||||
# This script expects AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to be set.
|
||||
# This happened using secret references in the preceding lines.
|
||||
run: ./deploy.sh
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Action Inputs
|
||||
|
||||
| Name | Default | Description |
|
||||
|---|---|---|
|
||||
| Name | Default | Description |
|
||||
| ---------------- | ------- | ---------------------------------------------------------------------------------- |
|
||||
| `export-env` | `true` | Export the loaded secrets as environment variables |
|
||||
| `unset-previous` | `false` | Whether to unset environment variables populated by 1Password in earlier job steps |
|
||||
|
||||
## Secrets Reference Syntax
|
||||
@@ -100,23 +373,23 @@ These reference URIs have the following syntax:
|
||||
> `op://<vault>/<item>[/<section>]/<field>`
|
||||
|
||||
So for example, the reference URI `op://app-cicd/aws/secret-access-key` would be interpreted as:
|
||||
* **Vault:** `app-cicd`
|
||||
* **Item:** `aws`
|
||||
* **Section:** default section
|
||||
* **Field:** `secret-access-key`
|
||||
|
||||
- **Vault:** `app-cicd`
|
||||
- **Item:** `aws`
|
||||
- **Section:** default section
|
||||
- **Field:** `secret-access-key`
|
||||
|
||||
## Masking
|
||||
|
||||
Similar to regular GitHub repository secrets, secret fields from 1Password will automatically be masked from the GitHub Actions logs too.
|
||||
A 1Password field is considered 'secret' when it's marked as concealed (which shows as `•••••••` in the 1Password GUI) or when it's a secure note.
|
||||
Similar to regular GitHub repository secrets, fields from 1Password will automatically be masked from the GitHub Actions logs too.
|
||||
So if one of these values accidentally gets printed, it'll get replaced with `***`.
|
||||
|
||||
This means that a username or port field for example will not get masked.
|
||||
## 1Password Configuration
|
||||
|
||||
## 1Password Connect Configuration
|
||||
To use the action with Connect, you need to have a [1Password Connect](https://support.1password.com/secrets-automation/#step-1-set-up-a-secrets-automation-workflow) instance deployed somewhere.
|
||||
To configure the action with your Connect host and token, set the `OP_CONNECT_HOST` and `OP_CONNECT_TOKEN` environment variables.
|
||||
|
||||
To use the action, you need to have a [1Password Connect](https://support.1password.com/secrets-automation/#step-1-set-up-a-secrets-automation-workflow) instance deployed somewhere.
|
||||
To configure the action with your Connect URL and a Connect token, you can set the `OP_CONNECT_HOST` and `OP_CONNECT_TOKEN` variables.
|
||||
To configure the action with your service account token <sup>BETA</sup>, set the `OP_SERVICE_ACCOUNT_TOKEN` environment variable.
|
||||
|
||||
If you're using the `load-secrets` action more than once in a single job, you can use the `configure` action to avoid duplicate configuration:
|
||||
|
||||
@@ -126,14 +399,13 @@ jobs:
|
||||
hello-world:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Configure 1Password Connect
|
||||
uses: 1password/load-secrets-action/configure@v1
|
||||
with:
|
||||
connect-host: <Your Connect instance URL>
|
||||
connect-token: ${{ secrets.OP_CONNECT_TOKEN }}
|
||||
|
||||
- name: Load secret
|
||||
uses: 1password/load-secrets-action@v1
|
||||
env:
|
||||
@@ -142,11 +414,55 @@ jobs:
|
||||
|
||||
### `configure` Action Inputs
|
||||
|
||||
| Name | Default | Environment variable | Description |
|
||||
|---|---|---|---|
|
||||
| `connect-host` | | `OP_CONNECT_HOST` | Your 1Password Connect instance URL |
|
||||
| `connect-token` | | `OP_CONNECT_TOKEN` | Token to authenticate to your 1Password Connect instance |
|
||||
| Name | Environment variable | Description |
|
||||
| ----------------------- | -------------------------- | -------------------------------------------------------- |
|
||||
| `connect-host` | `OP_CONNECT_HOST` | Your 1Password Connect instance URL |
|
||||
| `connect-token` | `OP_CONNECT_TOKEN` | Token to authenticate to your 1Password Connect instance |
|
||||
| `service-account-token` | `OP_SERVICE_ACCOUNT_TOKEN` | Your 1Password service account token |
|
||||
|
||||
## Supported Runners
|
||||
|
||||
You can run the action on Linux and macOS runners. Windows is currently not supported.
|
||||
|
||||
## Warnings
|
||||
|
||||
If you're using the CLI in your GitHub pipelines and you want to create items with it, the following command will fail:
|
||||
|
||||
```
|
||||
op item create --category=login --title='My Example Item' --vault='Test' \
|
||||
--url https://www.acme.com/login \
|
||||
--generate-password=20,letters,digits \
|
||||
username=jane@acme.com \
|
||||
'Test Field 1=my test secret' \
|
||||
'Test Section 1.Test Field2[text]=Jane Doe' \
|
||||
'Test Section 1.Test Field3[date]=1995-02-23' \
|
||||
'Test Section 2.Test Field4[text]='$myNotes
|
||||
```
|
||||
|
||||
This is caused by the fact that the environment in these pipelines is in piped mode, which triggers the CLI's pipe detection to expect a piped input.
|
||||
To be able to create items in such environments, do the following steps:
|
||||
|
||||
1. Get the template of the item category you want:
|
||||
|
||||
```sh
|
||||
op item template get --out-file=new-item.json <category>
|
||||
```
|
||||
|
||||
2. Edit [the template](https://developer.1password.com/docs/cli/item-template-json) to add your information.
|
||||
3. Pipe the item content to the command:
|
||||
|
||||
```sh
|
||||
cat new-item.json | op item create --vault='Test'
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
1Password requests you practice responsible disclosure if you discover a vulnerability.
|
||||
|
||||
Please file requests through [BugCrowd](https://bugcrowd.com/agilebits).
|
||||
|
||||
For information about our security practices, visit the [1Password Security homepage](https://1password.com/security).
|
||||
|
||||
## Getting help
|
||||
|
||||
If you find yourself stuck, visit our [**Support Page**](https://support.1password.com/) for help.
|
||||
|
||||
14
action.yml
14
action.yml
@@ -7,12 +7,10 @@ branding:
|
||||
inputs:
|
||||
unset-previous:
|
||||
description: Whether to unset environment variables populated by 1Password in earlier job steps
|
||||
default: false
|
||||
default: "false"
|
||||
export-env:
|
||||
description: Export the secrets as environment variables
|
||||
default: "true"
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- shell: bash
|
||||
env:
|
||||
INPUT_UNSET_PREVIOUS: ${{ inputs.unset-previous }}
|
||||
run: |
|
||||
${{ github.action_path }}/entrypoint.sh
|
||||
using: "node16"
|
||||
main: "dist/index.js"
|
||||
|
||||
4
config/.husky/pre-commit
Executable file
4
config/.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged --config ./config/lint-staged.config.js
|
||||
4
config/.husky/pre-push
Executable file
4
config/.husky/pre-push
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run validate
|
||||
3
config/.prettierignore
Normal file
3
config/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
coverage/
|
||||
dist/
|
||||
node_modules/
|
||||
19
config/jest.config.js
Normal file
19
config/jest.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const jestConfig = {
|
||||
/**
|
||||
* Jest docs: "We recommend placing the extensions most commonly used in your project
|
||||
* on the left, so if you are using TypeScript, you may want to consider
|
||||
* moving 'ts' to the beginning of the array."
|
||||
*
|
||||
* https://jestjs.io/docs/configuration#modulefileextensions-arraystring
|
||||
*/
|
||||
moduleFileExtensions: ["ts", "js", "json"],
|
||||
rootDir: "../src/",
|
||||
testEnvironment: "node",
|
||||
testRegex: "(/__tests__/.*|(\\.|/)test)\\.ts",
|
||||
transform: {
|
||||
".ts": ["ts-jest"],
|
||||
},
|
||||
verbose: true,
|
||||
};
|
||||
|
||||
export default jestConfig;
|
||||
9
config/lint-staged.config.js
Normal file
9
config/lint-staged.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const lintStagedConfig = {
|
||||
// run formatting and linting on all supported file types
|
||||
"*.{js,json,md,ts,yaml,yml}": "npm run format:write",
|
||||
"*.{js,ts}": ["npm run lint:fix"],
|
||||
// run testing on all supported file types within the src/ directory
|
||||
"src/**/*.{js,ts}": ["npm run test -- --findRelatedTests"],
|
||||
};
|
||||
|
||||
export default lintStagedConfig;
|
||||
@@ -1,11 +1,13 @@
|
||||
name: Configure 1Password Connect
|
||||
description: Persist 1Password Connect host and token for use in next steps.
|
||||
name: Configure 1Password Connect and service account
|
||||
description: Persist 1Password Connect host, token and service account for use in next steps.
|
||||
author: 1Password
|
||||
inputs:
|
||||
connect-host:
|
||||
description: Your 1Password Connect instance URL
|
||||
connect-token:
|
||||
description: Token to authenticate to your 1Password Connect instance
|
||||
service-account-token:
|
||||
description: Your 1Password service account token
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
@@ -13,5 +15,6 @@ runs:
|
||||
env:
|
||||
INPUT_CONNECT_HOST: ${{ inputs.connect-host }}
|
||||
INPUT_CONNECT_TOKEN: ${{ inputs.connect-token }}
|
||||
INPUT_SERVICE_ACCOUNT_TOKEN: ${{ inputs.service-account-token }}
|
||||
run: |
|
||||
${{ github.action_path }}/entrypoint.sh
|
||||
|
||||
@@ -14,3 +14,8 @@ OP_CONNECT_TOKEN="${INPUT_CONNECT_TOKEN:-$OP_CONNECT_TOKEN}"
|
||||
if [ -n "$OP_CONNECT_TOKEN" ]; then
|
||||
echo "OP_CONNECT_TOKEN=$OP_CONNECT_TOKEN" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
OP_SERVICE_ACCOUNT_TOKEN="${INPUT_SERVICE_ACCOUNT_TOKEN:-$OP_SERVICE_ACCOUNT_TOKEN}"
|
||||
if [ -n "$OP_SERVICE_ACCOUNT_TOKEN" ]; then
|
||||
echo "OP_SERVICE_ACCOUNT_TOKEN=$OP_SERVICE_ACCOUNT_TOKEN" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
4140
dist/index.js
vendored
Normal file
4140
dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3
dist/package.json
vendored
Normal file
3
dist/package.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
233
entrypoint.sh
233
entrypoint.sh
@@ -2,153 +2,144 @@
|
||||
# shellcheck disable=SC2046,SC2001,SC2086
|
||||
set -e
|
||||
|
||||
if [ -z "$OP_CONNECT_TOKEN" ] || [ -z "$OP_CONNECT_HOST" ]; then
|
||||
echo "\$OP_CONNECT_TOKEN and \$OP_CONNECT_HOST must be set"
|
||||
exit 1
|
||||
fi
|
||||
# Pass User-Agent Inforomation to the 1Password CLI
|
||||
export OP_INTEGRATION_NAME="1Password GitHub Action"
|
||||
export OP_INTEGRATION_ID="GHA"
|
||||
export OP_INTEGRATION_BUILDNUMBER="1010001"
|
||||
|
||||
readonly CONNECT="CONNECT"
|
||||
readonly SERVICE_ACCOUNT="SERVICE_ACCOUNT"
|
||||
|
||||
auth_type=$CONNECT
|
||||
managed_variables_var="OP_MANAGED_VARIABLES"
|
||||
IFS=',' read -r -a managed_variables <<< "$(printenv $managed_variables_var)"
|
||||
IFS=','
|
||||
|
||||
if [[ "$OP_CONNECT_HOST" != "http://"* ]] && [[ "$OP_CONNECT_HOST" != "https://"* ]]; then
|
||||
export OP_CONNECT_HOST="http://"$OP_CONNECT_HOST
|
||||
fi
|
||||
|
||||
# Unset all secrets managed by 1Password if `unset-previous` is set.
|
||||
if [ "$INPUT_UNSET_PREVIOUS" == "true" ]; then
|
||||
echo "Unsetting previous values..."
|
||||
unset_prev_secrets() {
|
||||
if [ "$INPUT_UNSET_PREVIOUS" == "true" ]; then
|
||||
echo "Unsetting previous values..."
|
||||
|
||||
# Find environment variables that are managed by 1Password.
|
||||
for env_var in "${managed_variables[@]}"; do
|
||||
echo "Unsetting $env_var"
|
||||
unset $env_var
|
||||
# Find environment variables that are managed by 1Password.
|
||||
for env_var in "${managed_variables[@]}"; do
|
||||
echo "Unsetting $env_var"
|
||||
unset $env_var
|
||||
|
||||
echo "$env_var=" >> $GITHUB_ENV
|
||||
echo "$env_var=" >> $GITHUB_ENV
|
||||
|
||||
# Keep the masks, just in case.
|
||||
done
|
||||
# Keep the masks, just in case.
|
||||
done
|
||||
|
||||
managed_variables=()
|
||||
fi
|
||||
|
||||
curl_headers=(-H "Content-Type: application/json" -H "Authorization: Bearer $OP_CONNECT_TOKEN")
|
||||
|
||||
# Iterate over environment varables to find 1Password references, load the secret values,
|
||||
# and make them available as environment variables in the next steps.
|
||||
IFS=$'\n'
|
||||
for possible_ref in $(printenv | grep "=op://" | grep -v "^#"); do
|
||||
env_var=$(echo "$possible_ref" | cut -d '=' -f1)
|
||||
ref=$(printenv $env_var)
|
||||
|
||||
if [[ ! $ref == "op://"* ]]; then
|
||||
echo "Not really a reference: $ref"
|
||||
continue
|
||||
managed_variables=()
|
||||
fi
|
||||
}
|
||||
|
||||
path=$(echo $ref | sed -e "s/^op:\/\///")
|
||||
if [ $(echo "$path" | tr -cd '/' | wc -c) -lt 2 ]; then
|
||||
echo "Expected path to be in format op://<vault>/<item>[/<section>]/<field>: $ref"
|
||||
continue
|
||||
# Install op-cli
|
||||
install_op_cli() {
|
||||
OP_INSTALL_DIR="$(mktemp -d)"
|
||||
if [[ ! -d "$OP_INSTALL_DIR" ]]; then
|
||||
echo "Install dir $OP_INSTALL_DIR not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Populating variable: $env_var"
|
||||
|
||||
vault=""
|
||||
item=""
|
||||
section=""
|
||||
field=""
|
||||
i=0
|
||||
IFS="/"
|
||||
for component in $path; do
|
||||
((i+=1))
|
||||
case "$i" in
|
||||
1) vault=$component ;;
|
||||
2) item=$component ;;
|
||||
3) section=$component ;;
|
||||
4) field=$component ;;
|
||||
esac
|
||||
done
|
||||
unset IFS
|
||||
|
||||
# If field is not set, it may have wrongfully been interpreted as the section.
|
||||
if [ -z "$field" ]; then
|
||||
field="$section"
|
||||
section=""
|
||||
export OP_INSTALL_DIR
|
||||
echo "::debug::OP_INSTALL_DIR: ${OP_INSTALL_DIR}"
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
curl -sSfLo op.zip "https://cache.agilebits.com/dist/1P/op2/pkg/v2.10.0-beta.02/op_linux_amd64_v2.10.0-beta.02.zip"
|
||||
unzip -od "$OP_INSTALL_DIR" op.zip && rm op.zip
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
curl -sSfLo op.pkg "https://cache.agilebits.com/dist/1P/op2/pkg/v2.10.0-beta.02/op_apple_universal_v2.10.0-beta.02.pkg"
|
||||
pkgutil --expand op.pkg temp-pkg
|
||||
tar -xvf temp-pkg/op.pkg/Payload -C "$OP_INSTALL_DIR"
|
||||
rm -rf temp-pkg && rm op.pkg
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ $(echo -n $(echo $vault | grep "^[a-z0-9]*$") | wc -c) -ne 26 ]]; then
|
||||
echo "Getting vault ID from vault name: $vault"
|
||||
vault=$(curl -sSf "${curl_headers[@]}" "$OP_CONNECT_HOST/v1/vaults?filter=name%20eq%20%22$vault%22" | jq -r '.[0] | .id')
|
||||
if [ -z "$vault" ]; then
|
||||
echo "Could not find vault ID for vault: $vault"
|
||||
exit 1
|
||||
fi
|
||||
# Uninstall op-cli
|
||||
uninstall_op_cli() {
|
||||
if [[ -d "$OP_INSTALL_DIR" ]]; then
|
||||
rm -fr "$OP_INSTALL_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ $(echo -n $(echo $item | grep "^[a-z0-9]*$") | wc -c) -ne 26 ]]; then
|
||||
echo "Getting item ID from vault $vault..."
|
||||
item=$(curl -sSf "${curl_headers[@]}" "$OP_CONNECT_HOST/v1/vaults/$vault/items?filter=title%20eq%20%22$item%22" | jq -r '.[0] | .id')
|
||||
if [ -z "$item" ]; then
|
||||
echo "Could not find item ID for item: $item"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
populating_secret() {
|
||||
ref=$(printenv $1)
|
||||
|
||||
echo "Loading item $item from vault $vault..."
|
||||
item_json=$(curl -sSf "${curl_headers[@]}" "$OP_CONNECT_HOST/v1/vaults/$vault/items/$item")
|
||||
|
||||
jq_field_selector=".id == \"$field\" or .label == \"$field\""
|
||||
jq_section_selector=".section == null"
|
||||
|
||||
# If the reference contains a section, edit the jq selector to take that into account.
|
||||
if [ -n "$section" ]; then
|
||||
echo "Looking for section: $section"
|
||||
section_id=$(echo "$item_json" | jq -r ".sections[] | select(.id == \"$section\" or .label == \"$section\") | .id")
|
||||
jq_section_selector=".section.id == \"$section_id\""
|
||||
fi
|
||||
|
||||
jq_secret_selector="$jq_section_selector and ($jq_field_selector)"
|
||||
|
||||
echo "Looking for field: $field"
|
||||
secret_field_json=$(echo "$item_json" | jq -r "first(.fields[] | select($jq_secret_selector))")
|
||||
|
||||
field_type=$(echo "$secret_field_json" | jq -r '.type')
|
||||
field_purpose=$(echo "$secret_field_json" | jq -r '.purpose')
|
||||
secret_value=$(echo "$secret_field_json" | jq -r '.value')
|
||||
echo "Populating variable: $1"
|
||||
secret_value=$("${OP_INSTALL_DIR}/op" read "$ref")
|
||||
|
||||
if [ -z "$secret_value" ]; then
|
||||
echo "Could not find or access secret $ref"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If the field is marked as concealed or is a note, register a mask
|
||||
# for the secret to prevent accidental log exposure.
|
||||
if [ "$field_type" == "CONCEALED" ] || [ "$field_purpose" == "NOTES" ]; then
|
||||
# To support multiline secrets, escape percent signs and add a mask per line.
|
||||
escaped_mask_value=$(echo "$secret_value" | sed -e 's/%/%25/g')
|
||||
IFS=$'\n'
|
||||
for line in $escaped_mask_value; do
|
||||
if [ "${#line}" -lt 3 ]; then
|
||||
# To avoid false positives and unreadable logs, omit mask for lines that are too short.
|
||||
continue
|
||||
fi
|
||||
echo "::add-mask::$line"
|
||||
done
|
||||
unset IFS
|
||||
# Register a mask for the secret to prevent accidental log exposure.
|
||||
# To support multiline secrets, escape percent signs and add a mask per line.
|
||||
escaped_mask_value=$(echo "$secret_value" | sed -e 's/%/%25/g')
|
||||
IFS=$'\n'
|
||||
for line in $escaped_mask_value; do
|
||||
if [ "${#line}" -lt 3 ]; then
|
||||
# To avoid false positives and unreadable logs, omit mask for lines that are too short.
|
||||
continue
|
||||
fi
|
||||
echo "::add-mask::$line"
|
||||
done
|
||||
unset IFS
|
||||
|
||||
if [ "$INPUT_EXPORT_ENV" == "true" ]; then
|
||||
# To support multiline secrets, we'll use the heredoc syntax to populate the environment variables.
|
||||
# As the heredoc identifier, we'll use a randomly generated 64-character string,
|
||||
# so that collisions are practically impossible.
|
||||
random_heredoc_identifier=$(openssl rand -hex 32)
|
||||
|
||||
{
|
||||
# Populate env var, using heredoc syntax with generated identifier
|
||||
echo "$env_var<<${random_heredoc_identifier}"
|
||||
echo "$secret_value"
|
||||
echo "${random_heredoc_identifier}"
|
||||
} >> $GITHUB_ENV
|
||||
echo "GITHUB_ENV: $(cat $GITHUB_ENV)"
|
||||
|
||||
else
|
||||
# Prepare the secret_value to be outputed properly (especially multiline secrets)
|
||||
secret_value=$(echo "$secret_value" | awk -v ORS='%0A' '1')
|
||||
|
||||
echo "::set-output name=$env_var::$secret_value"
|
||||
fi
|
||||
|
||||
# To support multiline secrets, we'll use the heredoc syntax to populate the environment variables.
|
||||
# As the heredoc identifier, we'll use a randomly generated 64-character string,
|
||||
# so that collisions are practically impossible.
|
||||
random_heredoc_identifier=$(openssl rand -hex 16)
|
||||
|
||||
{
|
||||
# Populate env var, using heredoc syntax with generated identifier
|
||||
echo "$env_var<<${random_heredoc_identifier}"
|
||||
echo "$secret_value"
|
||||
echo "${random_heredoc_identifier}"
|
||||
} >> $GITHUB_ENV
|
||||
|
||||
managed_variables+=("$env_var")
|
||||
done
|
||||
unset IFS
|
||||
}
|
||||
|
||||
# Load environment variables using op cli. Iterate over them to find 1Password references, load the secret values,
|
||||
# and make them available as environment variables in the next steps.
|
||||
extract_secrets() {
|
||||
IFS=$'\n'
|
||||
for env_var in $("${OP_INSTALL_DIR}/op" env ls); do
|
||||
populating_secret $env_var
|
||||
done
|
||||
}
|
||||
|
||||
read -r -a managed_variables <<< "$(printenv $managed_variables_var)"
|
||||
|
||||
if [ -z "$OP_CONNECT_TOKEN" ] || [ -z "$OP_CONNECT_HOST" ]; then
|
||||
if [ -z "$OP_SERVICE_ACCOUNT_TOKEN" ]; then
|
||||
echo "(\$OP_CONNECT_TOKEN and \$OP_CONNECT_HOST) or \$OP_SERVICE_ACCOUNT_TOKEN must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
auth_type=$SERVICE_ACCOUNT
|
||||
fi
|
||||
|
||||
printf "Authenticated with %s \n" $auth_type
|
||||
|
||||
unset_prev_secrets
|
||||
install_op_cli
|
||||
extract_secrets
|
||||
uninstall_op_cli
|
||||
|
||||
unset IFS
|
||||
# Add extra env var that lists which secrets are managed by 1Password so that in a later step
|
||||
# these can be unset again.
|
||||
managed_variables_str=$(IFS=','; echo "${managed_variables[*]}")
|
||||
|
||||
13560
package-lock.json
generated
Normal file
13560
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
66
package.json
Normal file
66
package.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "load-secrets-action",
|
||||
"version": "1.2.0",
|
||||
"description": "Load Secrets from 1Password",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"directories": {
|
||||
"test": "tests"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "ncc build ./src/index.ts",
|
||||
"format": "prettier --ignore-path ./config/.prettierignore",
|
||||
"format:check": "npm run format -- --check ./",
|
||||
"format:write": "npm run format -- --write ./",
|
||||
"lint": "eslint ./",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"prepare": "husky install ./config/.husky",
|
||||
"test": "jest --config=./config/jest.config.js",
|
||||
"test:clearcache": "jest --clearCache",
|
||||
"test:coverage": "npm run test -- --coverage",
|
||||
"test:watch": "npm run test -- --watch",
|
||||
"typecheck": "tsc",
|
||||
"validate": "npm run format:check && npm run lint && npm run test:coverage && npm run typecheck && npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/1Password/load-secrets-action.git"
|
||||
},
|
||||
"keywords": [
|
||||
"actions",
|
||||
"1password",
|
||||
"load secrets",
|
||||
"connect"
|
||||
],
|
||||
"author": "1Password",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/1Password/load-secrets-action/issues"
|
||||
},
|
||||
"homepage": "https://github.com/1Password/load-secrets-action#readme",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@1password/front-end-style": "^6.0.1",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/node": "^18.15.10",
|
||||
"@vercel/ncc": "^0.36.1",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.5.0",
|
||||
"lint-staged": "^13.2.0",
|
||||
"ts-jest": "^29.0.5",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "./node_modules/@1password/front-end-style/eslintrc.yml",
|
||||
"ignorePatterns": [
|
||||
"coverage/"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
}
|
||||
},
|
||||
"prettier": "./node_modules/@1password/front-end-style/prettierrc.json"
|
||||
}
|
||||
32
src/index.ts
Normal file
32
src/index.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import path from "path";
|
||||
import url from "url";
|
||||
import * as core from "@actions/core";
|
||||
import * as exec from "@actions/exec";
|
||||
|
||||
const run = async () => {
|
||||
try {
|
||||
const currentFile = url.fileURLToPath(import.meta.url);
|
||||
const currentDir = path.dirname(currentFile);
|
||||
const parentDir = path.resolve(currentDir, "..");
|
||||
|
||||
// Get action inputs
|
||||
process.env.INPUT_UNSET_PREVIOUS = core.getInput("unset-previous");
|
||||
process.env.INPUT_EXPORT_ENV = core.getInput("export-env");
|
||||
|
||||
// Execute bash script
|
||||
await exec.exec(`sh -c "` + parentDir + `/entrypoint.sh"`);
|
||||
} 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.
|
||||
// https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript
|
||||
let message = "Unknown Error";
|
||||
if (error instanceof Error) {
|
||||
message = error.message;
|
||||
} else {
|
||||
String(error);
|
||||
}
|
||||
core.setFailed(message);
|
||||
}
|
||||
};
|
||||
|
||||
void run();
|
||||
25
tsconfig.json
Normal file
25
tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": false,
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"esModuleInterop": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"importsNotUsedAsValues": "error",
|
||||
"isolatedModules": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"noEmit": true,
|
||||
"noEmitOnError": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"outDir": "./dist/",
|
||||
"rootDir": "./src/",
|
||||
"strict": true,
|
||||
"target": "es2022"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user