Compare commits

..

1 Commits

Author SHA1 Message Date
Jill Regan dd76a122aa Update dists 2026-02-26 11:22:17 -05:00
38 changed files with 35014 additions and 38215 deletions
+30 -73
View File
@@ -30,22 +30,21 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: true fail-fast: true
max-parallel: 4
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
version: [latest, 2.30.0] version: [latest, 2.30.0]
export-env: [true, false] export-env: [true, false]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
ref: ${{ inputs.ref }} ref: ${{ inputs.ref }}
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 20
cache: npm cache: npm
- name: Install dependencies - name: Install dependencies
@@ -57,15 +56,10 @@ jobs:
- name: Generate .env.tpl - name: Generate .env.tpl
shell: bash shell: bash
run: | run: |
echo "FILE_SECRET=op://${VAULT}/test-secret/password" > tests/.env.tpl echo "FILE_SECRET=op://${{ secrets.VAULT }}/test-secret/password" > tests/.env.tpl
echo "FILE_SECRET_IN_SECTION=op://${VAULT}/test-secret/test-section/password" >> tests/.env.tpl echo "FILE_SECRET_IN_SECTION=op://${{ secrets.VAULT }}/test-secret/test-section/password" >> tests/.env.tpl
echo "FILE_MULTILINE_SECRET=op://${VAULT}/multiline-secret/notesPlain" >> tests/.env.tpl echo "FILE_MULTILINE_SECRET=op://${{ secrets.VAULT }}/multiline-secret/notesPlain" >> tests/.env.tpl
echo "FILE_WEBSITE=op://${VAULT}/test-secret/website" >> tests/.env.tpl
echo "FILE_TEST_SSH_KEY=op://${VAULT}/test-ssh-key/private key" >> tests/.env.tpl
echo "FILE_TEST_SSH_KEY_OPENSSH=op://${VAULT}/test-ssh-key/private key?ssh-format=openssh" >> tests/.env.tpl
env:
VAULT: ${{ secrets.VAULT }}
- name: Configure Service account - name: Configure Service account
uses: ./configure uses: ./configure
with: with:
@@ -81,52 +75,25 @@ jobs:
SECRET: op://${{ secrets.VAULT }}/test-secret/password SECRET: op://${{ secrets.VAULT }}/test-secret/password
SECRET_IN_SECTION: op://${{ secrets.VAULT }}/test-secret/test-section/password SECRET_IN_SECTION: op://${{ secrets.VAULT }}/test-secret/test-section/password
MULTILINE_SECRET: op://${{ secrets.VAULT }}/multiline-secret/notesPlain MULTILINE_SECRET: op://${{ secrets.VAULT }}/multiline-secret/notesPlain
WEBSITE: op://${{ secrets.VAULT }}/test-secret/website
TEST_SSH_KEY: op://${{ secrets.VAULT }}/test-ssh-key/private key
TEST_SSH_KEY_OPENSSH: "op://${{ secrets.VAULT }}/test-ssh-key/private key?ssh-format=openssh"
OP_ENV_FILE: ./tests/.env.tpl OP_ENV_FILE: ./tests/.env.tpl
- name: Assert test secret values [step output] - name: Assert test secret values [step output]
if: ${{ !matrix.export-env }} if: ${{ !matrix.export-env }}
shell: bash shell: bash
env: env:
ASSERT_WEBSITE: "true"
SECRET: ${{ steps.load_secrets.outputs.SECRET }} SECRET: ${{ steps.load_secrets.outputs.SECRET }}
SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.SECRET_IN_SECTION }} SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.SECRET_IN_SECTION }}
MULTILINE_SECRET: ${{ steps.load_secrets.outputs.MULTILINE_SECRET }} MULTILINE_SECRET: ${{ steps.load_secrets.outputs.MULTILINE_SECRET }}
FILE_SECRET: ${{ steps.load_secrets.outputs.FILE_SECRET }} FILE_SECRET: ${{ steps.load_secrets.outputs.FILE_SECRET }}
FILE_SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.FILE_SECRET_IN_SECTION }} FILE_SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.FILE_SECRET_IN_SECTION }}
FILE_MULTILINE_SECRET: ${{ steps.load_secrets.outputs.FILE_MULTILINE_SECRET }} FILE_MULTILINE_SECRET: ${{ steps.load_secrets.outputs.FILE_MULTILINE_SECRET }}
WEBSITE: ${{ steps.load_secrets.outputs.WEBSITE }}
FILE_WEBSITE: ${{ steps.load_secrets.outputs.FILE_WEBSITE }}
TEST_SSH_KEY: ${{ steps.load_secrets.outputs.TEST_SSH_KEY }}
FILE_TEST_SSH_KEY: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY }}
TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.TEST_SSH_KEY_OPENSSH }}
FILE_TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY_OPENSSH }}
run: ./tests/assert-env-set.sh run: ./tests/assert-env-set.sh
- name: Assert SSH key env vars [step output]
if: ${{ !matrix.export-env }}
shell: bash
env:
TEST_SSH_KEY: ${{ steps.load_secrets.outputs.TEST_SSH_KEY }}
FILE_TEST_SSH_KEY: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY }}
TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.TEST_SSH_KEY_OPENSSH }}
FILE_TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY_OPENSSH }}
run: ./tests/assert-ssh-keys-set.sh
- name: Assert test secret values [exported env] - name: Assert test secret values [exported env]
if: ${{ matrix.export-env }} if: ${{ matrix.export-env }}
shell: bash shell: bash
env:
ASSERT_WEBSITE: "true"
run: ./tests/assert-env-set.sh run: ./tests/assert-env-set.sh
- name: Assert SSH key env vars [exported env]
if: ${{ matrix.export-env }}
shell: bash
run: ./tests/assert-ssh-keys-set.sh
- name: Remove secrets [exported env] - name: Remove secrets [exported env]
if: ${{ matrix.export-env }} if: ${{ matrix.export-env }}
uses: ./ uses: ./
@@ -138,25 +105,42 @@ jobs:
shell: bash shell: bash
run: ./tests/assert-env-unset.sh run: ./tests/assert-env-unset.sh
- name: Load secrets (invalid ref - expect failure)
id: load_invalid
continue-on-error: true
uses: ./
env:
BAD_REF: "op://x"
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
with:
export-env: true
- name: Assert invalid ref failed
shell: bash
run: ./tests/assert-invalid-ref-failed.sh
env:
STEP_OUTCOME: ${{ steps.load_invalid.outcome }}
test-connect: test-connect:
name: Connect (ubuntu-latest, ${{ matrix.version }}, export-env=${{ matrix.export-env }}) name: Connect (ubuntu-latest, ${{ matrix.version }}, export-env=${{ matrix.export-env }})
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
version: [latest, 2.30.0] version: [latest, 2.30.0]
export-env: [true, false] export-env: [true, false]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
ref: ${{ inputs.ref }} ref: ${{ inputs.ref }}
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 20
cache: npm cache: npm
- name: Install dependencies - name: Install dependencies
@@ -168,21 +152,16 @@ jobs:
- name: Generate .env.tpl - name: Generate .env.tpl
run: | run: |
mkdir -p tests mkdir -p tests
echo "FILE_SECRET=op://${VAULT}/test-secret/password" > tests/.env.tpl echo "FILE_SECRET=op://${{ secrets.VAULT }}/test-secret/password" > tests/.env.tpl
echo "FILE_SECRET_IN_SECTION=op://${VAULT}/test-secret/test-section/password" >> tests/.env.tpl echo "FILE_SECRET_IN_SECTION=op://${{ secrets.VAULT }}/test-secret/test-section/password" >> tests/.env.tpl
echo "FILE_MULTILINE_SECRET=op://${VAULT}/multiline-secret/notesPlain" >> tests/.env.tpl echo "FILE_MULTILINE_SECRET=op://${{ secrets.VAULT }}/multiline-secret/notesPlain" >> tests/.env.tpl
echo "FILE_TEST_SSH_KEY=op://${VAULT}/test-ssh-key/private key" >> tests/.env.tpl
echo "FILE_TEST_SSH_KEY_OPENSSH=op://${VAULT}/test-ssh-key/private key?ssh-format=openssh" >> tests/.env.tpl
env:
VAULT: ${{ secrets.VAULT }}
- name: Launch 1Password Connect instance - name: Launch 1Password Connect instance
env: env:
OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }} OP_CONNECT_CREDENTIALS: ${{ secrets.OP_CONNECT_CREDENTIALS }}
run: | run: |
echo "$OP_CONNECT_CREDENTIALS" > 1password-credentials.json echo "$OP_CONNECT_CREDENTIALS" > 1password-credentials.json
docker compose -f tests/fixtures/docker-compose.yml up -d docker compose -f tests/fixtures/docker-compose.yml up -d && sleep 10
timeout 60 bash -c 'until curl -sf http://localhost:8080/health >/dev/null 2>&1; do sleep 2; done'
- name: Configure 1Password Connect - name: Configure 1Password Connect
uses: ./configure uses: ./configure
@@ -200,45 +179,23 @@ jobs:
SECRET: op://${{ secrets.VAULT }}/test-secret/password SECRET: op://${{ secrets.VAULT }}/test-secret/password
SECRET_IN_SECTION: op://${{ secrets.VAULT }}/test-secret/test-section/password SECRET_IN_SECTION: op://${{ secrets.VAULT }}/test-secret/test-section/password
MULTILINE_SECRET: op://${{ secrets.VAULT }}/multiline-secret/notesPlain MULTILINE_SECRET: op://${{ secrets.VAULT }}/multiline-secret/notesPlain
TEST_SSH_KEY: op://${{ secrets.VAULT }}/test-ssh-key/private key
TEST_SSH_KEY_OPENSSH: "op://${{ secrets.VAULT }}/test-ssh-key/private key?ssh-format=openssh"
OP_ENV_FILE: ./tests/.env.tpl OP_ENV_FILE: ./tests/.env.tpl
- name: Assert test secret values [step output] - name: Assert test secret values [step output]
if: ${{ !matrix.export-env }} if: ${{ !matrix.export-env }}
env: env:
ASSERT_WEBSITE: "false"
SECRET: ${{ steps.load_secrets.outputs.SECRET }} SECRET: ${{ steps.load_secrets.outputs.SECRET }}
SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.SECRET_IN_SECTION }} SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.SECRET_IN_SECTION }}
MULTILINE_SECRET: ${{ steps.load_secrets.outputs.MULTILINE_SECRET }} MULTILINE_SECRET: ${{ steps.load_secrets.outputs.MULTILINE_SECRET }}
FILE_SECRET: ${{ steps.load_secrets.outputs.FILE_SECRET }} FILE_SECRET: ${{ steps.load_secrets.outputs.FILE_SECRET }}
FILE_SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.FILE_SECRET_IN_SECTION }} FILE_SECRET_IN_SECTION: ${{ steps.load_secrets.outputs.FILE_SECRET_IN_SECTION }}
FILE_MULTILINE_SECRET: ${{ steps.load_secrets.outputs.FILE_MULTILINE_SECRET }} FILE_MULTILINE_SECRET: ${{ steps.load_secrets.outputs.FILE_MULTILINE_SECRET }}
TEST_SSH_KEY: ${{ steps.load_secrets.outputs.TEST_SSH_KEY }}
FILE_TEST_SSH_KEY: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY }}
TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.TEST_SSH_KEY_OPENSSH }}
FILE_TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY_OPENSSH }}
run: ./tests/assert-env-set.sh run: ./tests/assert-env-set.sh
- name: Assert SSH key env vars [step output]
if: ${{ !matrix.export-env }}
env:
TEST_SSH_KEY: ${{ steps.load_secrets.outputs.TEST_SSH_KEY }}
FILE_TEST_SSH_KEY: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY }}
TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.TEST_SSH_KEY_OPENSSH }}
FILE_TEST_SSH_KEY_OPENSSH: ${{ steps.load_secrets.outputs.FILE_TEST_SSH_KEY_OPENSSH }}
run: ./tests/assert-ssh-keys-set.sh
- name: Assert test secret values [exported env] - name: Assert test secret values [exported env]
if: ${{ matrix.export-env }} if: ${{ matrix.export-env }}
env:
ASSERT_WEBSITE: "false"
run: ./tests/assert-env-set.sh run: ./tests/assert-env-set.sh
- name: Assert SSH key env vars [exported env]
if: ${{ matrix.export-env }}
run: ./tests/assert-ssh-keys-set.sh
- name: Remove secrets [exported env] - name: Remove secrets [exported env]
if: ${{ matrix.export-env }} if: ${{ matrix.export-env }}
uses: ./ uses: ./
+4 -4
View File
@@ -9,18 +9,18 @@ jobs:
lint-and-test: lint-and-test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- name: Run ShellCheck - name: Run ShellCheck
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0 uses: ludeeus/action-shellcheck@2.0.0
with: with:
ignore_paths: >- ignore_paths: >-
.husky .husky
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v4
with: with:
node-version: 24 node-version: 20
cache: npm cache: npm
- name: Install dependencies - name: Install dependencies
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
if: ${{ github.event.issue.pull_request }} if: ${{ github.event.issue.pull_request }}
steps: steps:
- name: Slash Command Dispatch - name: Slash Command Dispatch
uses: peter-evans/slash-command-dispatch@9bdcd7914ec1b75590b790b844aa3b8eee7c683a # v5 uses: peter-evans/slash-command-dispatch@v5
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
reaction-token: ${{ secrets.GITHUB_TOKEN }} reaction-token: ${{ secrets.GITHUB_TOKEN }}
@@ -10,4 +10,4 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check signed commits in PR - name: Check signed commits in PR
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1 uses: 1Password/check-signed-commits-action@v1
+2 -4
View File
@@ -65,7 +65,7 @@ jobs:
echo "condition=skip" >> $GITHUB_OUTPUT echo "condition=skip" >> $GITHUB_OUTPUT
echo "Setting condition=skip (sha does not match or empty)" echo "Setting condition=skip (sha does not match or empty)"
fi fi
elif [ "${{ github.event_name }}" == "push" ] && [ "${REF_NAME}" == "main" ]; then elif [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref_name }}" == "main" ]; then
echo "condition=push-to-main" >> $GITHUB_OUTPUT echo "condition=push-to-main" >> $GITHUB_OUTPUT
echo "Setting condition=push-to-main (push to main)" echo "Setting condition=push-to-main (push to main)"
echo "ref=${{ github.sha }}" >> $GITHUB_OUTPUT echo "ref=${{ github.sha }}" >> $GITHUB_OUTPUT
@@ -75,8 +75,6 @@ jobs:
echo "Setting condition=skip (unknown event type: ${{ github.event_name }})" echo "Setting condition=skip (unknown event type: ${{ github.event_name }})"
fi fi
env:
REF_NAME: ${{ github.ref_name }}
e2e: e2e:
needs: check-external-pr needs: check-external-pr
if: | if: |
@@ -107,7 +105,7 @@ jobs:
run: echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT run: echo "run-url=https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_OUTPUT
- name: Create comment on PR - name: Create comment on PR
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5 uses: peter-evans/create-or-update-comment@v5
with: with:
issue-number: ${{ github.event.client_payload.pull_request.number }} issue-number: ${{ github.event.client_payload.pull_request.number }}
body: | body: |
+3 -3
View File
@@ -35,7 +35,7 @@ jobs:
- name: Load secret - name: Load secret
id: load_secrets id: load_secrets
uses: 1password/load-secrets-action@v4 uses: 1password/load-secrets-action@v3
env: env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
SECRET: op://app-cicd/hello-world/secret SECRET: op://app-cicd/hello-world/secret
@@ -57,7 +57,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Load secret - name: Load secret
uses: 1password/load-secrets-action@v4 uses: 1password/load-secrets-action@v3
with: with:
# Export loaded secrets as environment variables # Export loaded secrets as environment variables
export-env: true export-env: true
@@ -77,7 +77,7 @@ When loading SSH keys, you can specify the format using the `ssh-format` query p
```yml ```yml
- name: Load SSH key - name: Load SSH key
uses: 1password/load-secrets-action@v4 uses: 1password/load-secrets-action@v3
env: env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
# Load SSH private key in OpenSSH format # Load SSH private key in OpenSSH format
+1 -1
View File
@@ -15,5 +15,5 @@ inputs:
description: Specify which 1Password CLI version to install. Defaults to "latest". description: Specify which 1Password CLI version to install. Defaults to "latest".
default: "latest" default: "latest"
runs: runs:
using: "node24" using: "node20"
main: "dist/index.js" main: "dist/index.js"
+1 -6
View File
@@ -10,11 +10,6 @@ const jestConfig = {
rootDir: "../src/", rootDir: "../src/",
testEnvironment: "node", testEnvironment: "node",
testRegex: "(/__tests__/.*|(\\.|/)test)\\.ts", testRegex: "(/__tests__/.*|(\\.|/)test)\\.ts",
moduleNameMapper: {
"^@actions/core$": "<rootDir>/__mocks__/actions-core.ts",
"^@actions/tool-cache$": "<rootDir>/__mocks__/actions-tool-cache.ts",
"^@actions/exec$": "<rootDir>/__mocks__/actions-exec.ts",
},
transform: { transform: {
".ts": [ ".ts": [
"ts-jest", "ts-jest",
@@ -30,4 +25,4 @@ const jestConfig = {
verbose: true, verbose: true,
}; };
module.exports = jestConfig; export default jestConfig;
+1 -1
View File
@@ -9,5 +9,5 @@ inputs:
service-account-token: service-account-token:
description: Your 1Password service account token description: Your 1Password service account token
runs: runs:
using: "node24" using: "node20"
main: "dist/index.js" main: "dist/index.js"
+14524 -17958
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,4 +1,4 @@
import * as core from "@actions/core"; const core = require("@actions/core");
const configure = () => { const configure = () => {
const OP_CONNECT_HOST = const OP_CONNECT_HOST =
BIN
View File
Binary file not shown.
+19703 -19231
View File
File diff suppressed because one or more lines are too long
-49
View File
@@ -1,49 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
P4I7jHwe0BN3iNAG1nKbGg==
=+LeX
-----END PGP PUBLIC KEY BLOCK-----
+150 -56
View File
@@ -1,18 +1,19 @@
{ {
"name": "load-secrets-action", "name": "load-secrets-action",
"version": "4.0.0", "version": "3.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "load-secrets-action", "name": "load-secrets-action",
"version": "4.0.0", "version": "3.1.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@1password/op-js": "^0.1.11", "@1password/op-js": "^0.1.11",
"@actions/core": "^3.0.0", "@1password/sdk": "^0.4.0",
"@actions/exec": "^3.0.0", "@actions/core": "^1.10.1",
"@actions/tool-cache": "^4.0.0", "@actions/exec": "^1.1.1",
"@actions/tool-cache": "^2.0.2",
"dotenv": "^17.2.2" "dotenv": "^17.2.2"
}, },
"devDependencies": { "devDependencies": {
@@ -72,50 +73,74 @@
"prettier": "^2.0.0 || ^3.0.0" "prettier": "^2.0.0 || ^3.0.0"
} }
}, },
"node_modules/@actions/core": { "node_modules/@1password/sdk": {
"version": "3.0.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@1password/sdk/-/sdk-0.4.0.tgz",
"integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==", "integrity": "sha512-RIypujc9R/UeUaobjyClTYokqRFpcaIkHq+EO/X9XoHId98Vg+SbjwGV+yygRC4MyHwYNo1KP1iEbZcqJ4ZTdw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/exec": "^3.0.0", "@1password/sdk-core": "0.4.0"
"@actions/http-client": "^4.0.0" }
},
"node_modules/@1password/sdk-core": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@1password/sdk-core/-/sdk-core-0.4.0.tgz",
"integrity": "sha512-vjeI1o4wiONY+t1naA4dtUp6HktdLH1D2S+tN1Lh4l41S9XIUHxrljov9B5u6G+VHr7f2MUoxmzXA9zT3aokQQ==",
"license": "MIT"
},
"node_modules/@actions/core": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"license": "MIT",
"dependencies": {
"@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1"
} }
}, },
"node_modules/@actions/exec": { "node_modules/@actions/exec": {
"version": "3.0.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==", "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"license": "MIT",
"dependencies": { "dependencies": {
"@actions/io": "^3.0.2" "@actions/io": "^1.0.1"
} }
}, },
"node_modules/@actions/http-client": { "node_modules/@actions/http-client": {
"version": "4.0.0", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.0.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
"integrity": "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==", "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tunnel": "^0.0.6", "tunnel": "^0.0.6",
"undici": "^6.23.0" "undici": "^5.25.4"
} }
}, },
"node_modules/@actions/io": { "node_modules/@actions/io": {
"version": "3.0.2", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz", "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
"integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==" "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
"license": "MIT"
}, },
"node_modules/@actions/tool-cache": { "node_modules/@actions/tool-cache": {
"version": "4.0.0", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-4.0.0.tgz", "resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-2.0.2.tgz",
"integrity": "sha512-L8P9HbXvpvqjZDveb/fdsa55IVC0trfPgQ4ZwGo6r5af6YDVdM9vMGPZ7rgY2fAT9gGj4PSYd6bYlg3p3jD78A==", "integrity": "sha512-fBhNNOWxuoLxztQebpOaWu6WeVmuwa77Z+DxIZ1B+OYvGkGQon6kTVg6Z32Cb13WCuw0szqonK+hh03mJV7Z6w==",
"license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^3.0.0", "@actions/core": "^1.11.1",
"@actions/exec": "^3.0.0", "@actions/exec": "^1.0.0",
"@actions/http-client": "^4.0.0", "@actions/http-client": "^2.0.1",
"@actions/io": "^3.0.0", "@actions/io": "^1.1.1",
"semver": "^7.7.3" "semver": "^6.1.0"
}
},
"node_modules/@actions/tool-cache/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
@@ -750,6 +775,15 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.13.0", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -1556,6 +1590,32 @@
} }
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
@@ -1644,10 +1704,11 @@
} }
}, },
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.14.0", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true, "dev": true,
"license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
@@ -2089,13 +2150,14 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "2.0.2", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0",
"concat-map": "0.0.1"
} }
}, },
"node_modules/braces": { "node_modules/braces": {
@@ -2475,6 +2537,13 @@
"node": ">= 12.0.0" "node": ">= 12.0.0"
} }
}, },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT"
},
"node_modules/convert-source-map": { "node_modules/convert-source-map": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -3698,6 +3767,29 @@
"minimatch": "^5.0.1" "minimatch": "^5.0.1"
} }
}, },
"node_modules/filelist/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/filelist/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3742,10 +3834,11 @@
} }
}, },
"node_modules/flatted": { "node_modules/flatted": {
"version": "3.4.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
"dev": true, "dev": true,
"license": "ISC",
"peer": true "peer": true
}, },
"node_modules/for-each": { "node_modules/for-each": {
@@ -6119,19 +6212,16 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "9.0.9", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.2" "brace-expansion": "^1.1.7"
}, },
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/minimist": { "node_modules/minimist": {
@@ -6949,9 +7039,9 @@
} }
}, },
"node_modules/semver": { "node_modules/semver": {
"version": "7.7.4", "version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
@@ -7818,11 +7908,15 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "6.24.1", "version": "5.29.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
"integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
"license": "MIT",
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
"engines": { "engines": {
"node": ">=18.17" "node": ">=14.0"
} }
}, },
"node_modules/undici-types": { "node_modules/undici-types": {
+5 -7
View File
@@ -1,6 +1,6 @@
{ {
"name": "load-secrets-action", "name": "load-secrets-action",
"version": "4.0.0", "version": "3.1.0",
"description": "Load Secrets from 1Password", "description": "Load Secrets from 1Password",
"main": "dist/index.js", "main": "dist/index.js",
"directories": { "directories": {
@@ -41,14 +41,12 @@
"homepage": "https://github.com/1Password/load-secrets-action#readme", "homepage": "https://github.com/1Password/load-secrets-action#readme",
"dependencies": { "dependencies": {
"@1password/op-js": "^0.1.11", "@1password/op-js": "^0.1.11",
"@actions/core": "^3.0.0", "@1password/sdk": "^0.4.0",
"@actions/exec": "^3.0.0", "@actions/core": "^1.10.1",
"@actions/tool-cache": "^4.0.0", "@actions/exec": "^1.1.1",
"@actions/tool-cache": "^2.0.2",
"dotenv": "^17.2.2" "dotenv": "^17.2.2"
}, },
"overrides": {
"minimatch": "^9.0.7"
},
"devDependencies": { "devDependencies": {
"@1password/eslint-config": "^4.3.1", "@1password/eslint-config": "^4.3.1",
"@1password/prettier-config": "^1.2.0", "@1password/prettier-config": "^1.2.0",
-14
View File
@@ -1,14 +0,0 @@
module.exports = {
getInput: jest.fn(() => ""),
getBooleanInput: jest.fn(() => false),
setOutput: jest.fn(),
setSecret: jest.fn(),
exportVariable: jest.fn(),
setFailed: jest.fn(),
info: jest.fn(),
warning: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
addPath: jest.fn(),
isDebug: jest.fn(() => false),
};
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
getExecOutput: jest.fn(() => ({
stdout: "MOCK_SECRET",
})),
};
-10
View File
@@ -1,10 +0,0 @@
module.exports = {
downloadTool: jest.fn(),
extractTar: jest.fn(),
extractZip: jest.fn(),
cacheDir: jest.fn<Promise<string>, [string]>(async (dir) => {
await Promise.resolve();
return dir;
}),
find: jest.fn<string, [string, string?, string?]>(() => ""),
};
+6 -2
View File
@@ -3,7 +3,7 @@ import * as core from "@actions/core";
import { validateCli } from "@1password/op-js"; import { validateCli } from "@1password/op-js";
import { installCliOnGithubActionRunner } from "./op-cli-installer"; import { installCliOnGithubActionRunner } from "./op-cli-installer";
import { loadSecrets, unsetPrevious, validateAuth } from "./utils"; import { loadSecrets, unsetPrevious, validateAuth } from "./utils";
import { envFilePath } from "./constants"; import { envFilePath, envConnectHost, envConnectToken } from "./constants";
const loadSecretsAction = async () => { const loadSecretsAction = async () => {
try { try {
@@ -26,8 +26,12 @@ const loadSecretsAction = async () => {
dotenv.config({ path: file }); dotenv.config({ path: file });
} }
// Download and install the CLI const isConnect =
process.env[envConnectHost] && process.env[envConnectToken];
// If Connect is used, download and install the CLI
if (isConnect) {
await installCLI(); await installCLI();
}
// Load secrets // Load secrets
await loadSecrets(shouldExportEnv); await loadSecrets(shouldExportEnv);
@@ -1,58 +0,0 @@
import {
ONEPASSWORD_GPG_KEY_FINGERPRINT,
verifyLinuxSignature,
} from "./linux-signature";
describe("verifyLinuxSignature", () => {
const OP_PATH = "/tmp/op";
const SIG_PATH = `${OP_PATH}.sig`;
const CORRECT_FPR = `fpr:::::::::${ONEPASSWORD_GPG_KEY_FINGERPRINT}:\n`;
const WRONG_FPR = `fpr:::::::::DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF:\n`;
const gpgRunner = (...responses: (string | Error)[]) => {
const runner = jest.fn<Promise<string>, [readonly string[]]>();
for (const r of responses) {
if (r instanceof Error) {
runner.mockRejectedValueOnce(r);
} else {
runner.mockResolvedValueOnce(r);
}
}
return runner;
};
const subcommandsCalled = (runner: ReturnType<typeof gpgRunner>) =>
runner.mock.calls.map(([args]: [readonly string[]]) =>
args.find(
(a) => a === "--import" || a === "--list-keys" || a === "--verify",
),
);
it("imports the bundled key and verifies the signature", async () => {
const runner = gpgRunner("", CORRECT_FPR, "");
await expect(
verifyLinuxSignature(OP_PATH, SIG_PATH, runner),
).resolves.toBeUndefined();
expect(subcommandsCalled(runner)).toEqual([
"--import",
"--list-keys",
"--verify",
]);
});
it("throws and skips --verify when the imported key has the wrong fingerprint", async () => {
const runner = gpgRunner("", WRONG_FPR);
await expect(
verifyLinuxSignature(OP_PATH, SIG_PATH, runner),
).rejects.toThrow(/does not match expected/);
expect(subcommandsCalled(runner)).toEqual(["--import", "--list-keys"]);
});
it("throws when gpg --verify rejects the signature", async () => {
const runner = gpgRunner("", CORRECT_FPR, new Error("BAD signature"));
await expect(
verifyLinuxSignature(OP_PATH, SIG_PATH, runner),
).rejects.toThrow(/BAD signature/);
});
});
@@ -1,64 +0,0 @@
import { execFile } from "child_process";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { promisify } from "util";
const execFileAsync = promisify(execFile);
// 1Password's code-signing GPG key fingerprint. See
// https://www.1password.dev/cli/verify.
export const ONEPASSWORD_GPG_KEY_FINGERPRINT =
"3FEF9748469ADBE15DA7CA80AC2D62742012EA22";
// Bundled 1Password code-signing public key `linux-signing-key.asc` in
// this directory. Bundled to avoid a runtime keyserver/URL dependency.
// Source: https://downloads.1password.com/linux/keys/1password.asc
const ONEPASSWORD_GPG_PUBLIC_KEY_PATH = path.join(
__dirname,
"linux-signing-key.asc",
);
const defaultGpgRunner = async (args: readonly string[]): Promise<string> => {
const { stdout } = await execFileAsync("gpg", args);
return stdout;
};
// Throws unless the binary at opPath carries a valid GPG signature (at
// sigPath) from the pinned 1Password key. The key is bundled with the action
export const verifyLinuxSignature = async (
opPath: string,
sigPath: string,
runGpg: (args: readonly string[]) => Promise<string> = defaultGpgRunner,
): Promise<void> => {
const gpgHome = fs.mkdtempSync(path.join(os.tmpdir(), "op-verify-"));
try {
const baseArgs = ["--homedir", gpgHome, "--batch", "--no-tty"];
// Import the bundled key into the temp keyring.
await runGpg([...baseArgs, "--import", ONEPASSWORD_GPG_PUBLIC_KEY_PATH]);
// Confirm we imported the pinned key.
const keyringListing = await runGpg([
...baseArgs,
"--list-keys",
"--with-colons",
]);
if (!keyringListing.includes(`${ONEPASSWORD_GPG_KEY_FINGERPRINT}:`)) {
throw new Error(
`bundled GPG key does not match expected fingerprint ${ONEPASSWORD_GPG_KEY_FINGERPRINT}.`,
);
}
// Verify op.sig against op using the imported key.
await runGpg([...baseArgs, "--verify", sigPath, opPath]);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
throw new Error(
`1Password CLI signature verification failed: ${message}. ` +
"If 1Password has rotated their GPG signing key, this action needs to be updated — please file an issue at https://github.com/1Password/load-secrets-action/issues.",
);
} finally {
fs.rmSync(gpgHome, { recursive: true, force: true });
}
};
@@ -1,49 +0,0 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
P4I7jHwe0BN3iNAG1nKbGg==
=+LeX
-----END PGP PUBLIC KEY BLOCK-----
@@ -2,6 +2,7 @@ import os from "os";
import { import {
archMap, archMap,
CliInstaller,
cliUrlBuilder, cliUrlBuilder,
type SupportedPlatform, type SupportedPlatform,
} from "./cli-installer"; } from "./cli-installer";
@@ -24,7 +25,9 @@ describe("LinuxInstaller", () => {
it("should call install with correct URL", async () => { it("should call install with correct URL", async () => {
const installer = new LinuxInstaller(version); const installer = new LinuxInstaller(version);
const installMock = jest.spyOn(installer, "install").mockResolvedValue(); const installMock = jest
.spyOn(CliInstaller.prototype, "install")
.mockResolvedValue();
await installer.installCli(); await installer.installCli();
@@ -1,15 +1,9 @@
import * as path from "path";
import * as core from "@actions/core";
import * as tc from "@actions/tool-cache";
import { import {
CliInstaller, CliInstaller,
cliUrlBuilder, cliUrlBuilder,
type SupportedPlatform, type SupportedPlatform,
} from "./cli-installer"; } from "./cli-installer";
import type { Installer } from "./installer"; import type { Installer } from "./installer";
import { verifyLinuxSignature } from "./linux-signature";
export class LinuxInstaller extends CliInstaller implements Installer { export class LinuxInstaller extends CliInstaller implements Installer {
private readonly platform: SupportedPlatform = "linux"; // Node.js platform identifier for Linux private readonly platform: SupportedPlatform = "linux"; // Node.js platform identifier for Linux
@@ -20,23 +14,6 @@ export class LinuxInstaller extends CliInstaller implements Installer {
public async installCli(): Promise<void> { public async installCli(): Promise<void> {
const urlBuilder = cliUrlBuilder[this.platform]; const urlBuilder = cliUrlBuilder[this.platform];
await this.install(urlBuilder(this.version, this.arch)); await super.install(urlBuilder(this.version, this.arch));
}
public override async install(url: string): Promise<void> {
console.info(`Downloading 1Password CLI from: ${url}`);
const downloadPath = await tc.downloadTool(url);
console.info("Installing 1Password CLI");
const extractedPath = await tc.extractZip(downloadPath);
core.info("Verifying 1Password CLI signature");
await verifyLinuxSignature(
path.join(extractedPath, "op"),
path.join(extractedPath, "op.sig"),
);
core.info("1Password CLI signature verified");
core.addPath(extractedPath);
core.info("1Password CLI installed");
} }
} }
@@ -1,57 +0,0 @@
import {
ALLOWED_MACOS_SIGNING_CERT_FINGERPRINTS,
APPLE_DEVELOPER_TEAM_ID,
verifyMacOsPackageSignature,
} from "./macos-signature";
const VALID_FINGERPRINT = ALLOWED_MACOS_SIGNING_CERT_FINGERPRINTS[0]!;
const buildPkgutilOutput = ({
teamId = APPLE_DEVELOPER_TEAM_ID,
signerFingerprint = VALID_FINGERPRINT,
}: {
teamId?: string;
signerFingerprint?: string;
} = {}): string => {
const bytes = signerFingerprint.match(/.{2}/g)!;
const fprLines = ` ${bytes.slice(0, 24).join(" ")}\n ${bytes.slice(24).join(" ")}`;
return `Package "op.pkg":
Certificate Chain:
1. Developer ID Installer: AgileBits Inc. (${teamId})
SHA256 Fingerprint:
${fprLines}
------------------------------------------------------------------------
2. Developer ID Certification Authority
`;
};
const pkgutilRunner = (output: string) =>
jest.fn<Promise<string>, [string]>().mockResolvedValue(output);
describe("verifyMacOsPackageSignature", () => {
it("passes for a pkg signed by AgileBits with an allowlisted cert", async () => {
const runner = pkgutilRunner(buildPkgutilOutput());
await expect(
verifyMacOsPackageSignature("/tmp/op.pkg", runner),
).resolves.toBeUndefined();
});
it("throws if the signer is not under the AgileBits team ID", async () => {
const runner = pkgutilRunner(buildPkgutilOutput({ teamId: "ATTACKER" }));
await expect(
verifyMacOsPackageSignature("/tmp/op.pkg", runner),
).rejects.toThrow(/expected developer team ID 2BUA8C4S2C not found/);
});
it("throws if the signer cert fingerprint is not on the allowlist", async () => {
const runner = pkgutilRunner(
buildPkgutilOutput({
signerFingerprint:
"DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
}),
);
await expect(
verifyMacOsPackageSignature("/tmp/op.pkg", runner),
).rejects.toThrow(/not on the allowlist/);
});
});
@@ -1,78 +0,0 @@
import { execFile } from "child_process";
import { promisify } from "util";
const execFileAsync = promisify(execFile);
// See https://www.1password.dev/cli/verify.
export const APPLE_DEVELOPER_TEAM_ID = "2BUA8C4S2C";
// Append-only: old certs stay listed so historical `op` versions still verify.
// See https://www.1password.dev/cli/verify.
export const ALLOWED_MACOS_SIGNING_CERT_FINGERPRINTS = [
"CAB578061B0209FB70934DA344EF6FEBCD3279B1C074C54B0D7D555743B9D89F",
"141DD87B2B231211F1440849798007DF621DE6EB3DAB985BC964EE9704C4A1C1",
];
const defaultPkgutilRunner = async (pkgPath: string): Promise<string> => {
const { stdout } = await execFileAsync("pkgutil", [
"--check-signature",
pkgPath,
]);
return stdout;
};
// Returns just entry 1 (the signer cert) from the chain.
const extractSignerCertSection = (pkgutilOutput: string): string | null => {
const chainStart = pkgutilOutput.indexOf("Certificate Chain:");
if (chainStart === -1) {
return null;
}
const chainBody = pkgutilOutput.slice(chainStart);
const secondCert = /\n\s*2\.\s/.exec(chainBody);
return secondCert ? chainBody.slice(0, secondCert.index) : chainBody;
};
const parseSignerFingerprint = (signerSection: string): string | null => {
const match = /SHA256 Fingerprint:\s*\n((?:[ \t]+[0-9A-Fa-f ]+\n?)+)/.exec(
signerSection,
);
const captured = match?.[1];
return captured ? captured.replace(/\s+/g, "").toUpperCase() : null;
};
// Hard-fails if the .pkg at pkgPath is not signed by AgileBits Inc.
// (2BUA8C4S2C) with a certificate on the allowlist above. Must run
// before any extraction of the .pkg contents.
export const verifyMacOsPackageSignature = async (
pkgPath: string,
runPkgutil: (pkgPath: string) => Promise<string> = defaultPkgutilRunner,
): Promise<void> => {
const stdout = await runPkgutil(pkgPath);
const signerSection = extractSignerCertSection(stdout);
if (!signerSection) {
throw new Error(
`1Password CLI signature verification failed: could not locate certificate chain in pkgutil output.\npkgutil output:\n${stdout}`,
);
}
if (!signerSection.includes(`(${APPLE_DEVELOPER_TEAM_ID})`)) {
throw new Error(
`1Password CLI signature verification failed: expected developer team ID ${APPLE_DEVELOPER_TEAM_ID} not found in signer certificate.\npkgutil output:\n${stdout}`,
);
}
const signerFingerprint = parseSignerFingerprint(signerSection);
if (!signerFingerprint) {
throw new Error(
`1Password CLI signature verification failed: could not parse signer cert SHA-256 fingerprint.\npkgutil output:\n${stdout}`,
);
}
if (!ALLOWED_MACOS_SIGNING_CERT_FINGERPRINTS.includes(signerFingerprint)) {
throw new Error(
`1Password CLI signature verification failed: signer cert SHA-256 fingerprint ${signerFingerprint} is not on the allowlist. ` +
"If 1Password has rotated their installer signing cert, this action needs to be updated — please file an issue at https://github.com/1Password/load-secrets-action/issues.",
);
}
};
@@ -12,7 +12,6 @@ import {
type SupportedPlatform, type SupportedPlatform,
} from "./cli-installer"; } from "./cli-installer";
import { type Installer } from "./installer"; import { type Installer } from "./installer";
import { verifyMacOsPackageSignature } from "./macos-signature";
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
@@ -35,10 +34,6 @@ export class MacOsInstaller extends CliInstaller implements Installer {
const pkgWithExtension = `${pkgPath}.pkg`; const pkgWithExtension = `${pkgPath}.pkg`;
fs.renameSync(pkgPath, pkgWithExtension); fs.renameSync(pkgPath, pkgWithExtension);
core.info("Verifying 1Password CLI signature");
await verifyMacOsPackageSignature(pkgWithExtension);
core.info("1Password CLI signature verified");
const expandDir = "temp-pkg"; const expandDir = "temp-pkg";
await execFileAsync("pkgutil", ["--expand", pkgWithExtension, expandDir]); await execFileAsync("pkgutil", ["--expand", pkgWithExtension, expandDir]);
const payloadPath = path.join(expandDir, "op.pkg", "Payload"); const payloadPath = path.join(expandDir, "op.pkg", "Payload");
@@ -1,42 +0,0 @@
import {
verifyAuthenticodeSignature,
WINDOWS_SIGNER_SUBJECT_CN,
} from "./windows-signature";
describe("verifyAuthenticodeSignature", () => {
const OP_EXE = "C:\\op\\op.exe";
const buildAuthenticodeOutput = ({
status = "Valid",
subject = `CN=${WINDOWS_SIGNER_SUBJECT_CN}, O=Agilebits, C=CA`,
}: { status?: string; subject?: string } = {}): string =>
[`Status=${status}`, `Subject=${subject}`].join("\n") + "\n";
const powershellRunner = (output: string) =>
jest.fn<Promise<string>, [string]>().mockResolvedValue(output);
it("passes for a valid AgileBits-signed binary", async () => {
const runner = powershellRunner(buildAuthenticodeOutput());
await expect(
verifyAuthenticodeSignature(OP_EXE, runner),
).resolves.toBeUndefined();
});
it("throws if Status is not Valid (unsigned or tampered)", async () => {
const runner = powershellRunner(
buildAuthenticodeOutput({ status: "HashMismatch" }),
);
await expect(verifyAuthenticodeSignature(OP_EXE, runner)).rejects.toThrow(
/Authenticode status is HashMismatch/,
);
});
it("throws if the signer is not AgileBits", async () => {
const runner = powershellRunner(
buildAuthenticodeOutput({ subject: "CN=Attacker, O=Attacker, C=US" }),
);
await expect(verifyAuthenticodeSignature(OP_EXE, runner)).rejects.toThrow(
/does not contain CN=Agilebits/,
);
});
});
@@ -1,62 +0,0 @@
import { execFile } from "child_process";
import { promisify } from "util";
const execFileAsync = promisify(execFile);
// Identifying field of 1Password's Authenticode signing cert for op.exe.
// See https://www.1password.dev/cli/verify.
export const WINDOWS_SIGNER_SUBJECT_CN = "Agilebits";
const defaultPowerShellRunner = async (script: string): Promise<string> => {
const { stdout } = await execFileAsync("powershell.exe", [
"-NoProfile",
"-NonInteractive",
"-Command",
script,
]);
return stdout;
};
// Verifies op.exe's Authenticode signature against 1Password's signing cert.
// Throws unless the signature is cryptographically valid and the signer is AgileBits.
export const verifyAuthenticodeSignature = async (
opExePath: string,
runPowerShell: (script: string) => Promise<string> = defaultPowerShellRunner,
): Promise<void> => {
const escapedPath = opExePath.replace(/'/g, "''");
const script = [
`$sig = Get-AuthenticodeSignature -FilePath '${escapedPath}'`,
`"Status=$($sig.Status)"`,
`"Subject=$($sig.SignerCertificate.Subject)"`,
].join("; ");
const output = await runPowerShell(script);
const outputLines = output.split("\n").map((l) => l.trim());
const fieldValue = (prefix: string): string | undefined => {
const matchingLine = outputLines.find((l) => l.startsWith(prefix));
if (!matchingLine) {
return undefined;
}
return matchingLine.slice(prefix.length);
};
// Reject unsigned or tampered binaries.
const status = fieldValue("Status=");
if (status !== "Valid") {
throw new Error(
`Authenticode status is ${status ?? "unknown"}, expected Valid.\nGet-AuthenticodeSignature output:\n${output}`,
);
}
// Confirm the signer is AgileBits, not some other publisher. Trailing comma
// anchors the CN value so e.g. "CN=AgilebitsAttacker, ..." cannot match.
const subject = fieldValue("Subject=") ?? "";
const expectedCn = `CN=${WINDOWS_SIGNER_SUBJECT_CN},`;
if (!subject.includes(expectedCn)) {
throw new Error(
`1Password CLI signature verification failed: signer Subject (${subject}) does not contain ${expectedCn} ` +
"If 1Password has rotated or renamed their signing identity, this action needs to be updated — please file an issue at https://github.com/1Password/load-secrets-action/issues.",
);
}
};
@@ -1,21 +1,13 @@
import fs from "fs";
import os from "os"; import os from "os";
import * as core from "@actions/core";
import * as tc from "@actions/tool-cache";
import { import {
archMap, archMap,
CliInstaller,
cliUrlBuilder, cliUrlBuilder,
type SupportedPlatform, type SupportedPlatform,
} from "./cli-installer"; } from "./cli-installer";
import { WindowsInstaller } from "./windows"; import { WindowsInstaller } from "./windows";
jest.mock("fs");
jest.mock("./windows-signature", () => ({
verifyAuthenticodeSignature: jest.fn().mockResolvedValue(undefined),
}));
afterEach(() => { afterEach(() => {
jest.restoreAllMocks(); jest.restoreAllMocks();
}); });
@@ -33,7 +25,9 @@ describe("WindowsInstaller", () => {
it("should call install with correct URL", async () => { it("should call install with correct URL", async () => {
const installer = new WindowsInstaller(version); const installer = new WindowsInstaller(version);
const installMock = jest.spyOn(installer, "install").mockResolvedValue(); const installMock = jest
.spyOn(CliInstaller.prototype, "install")
.mockResolvedValue();
await installer.installCli(); await installer.installCli();
@@ -41,23 +35,4 @@ describe("WindowsInstaller", () => {
const url = builder(version, installer.arch); const url = builder(version, installer.arch);
expect(installMock).toHaveBeenCalledWith(url); expect(installMock).toHaveBeenCalledWith(url);
}); });
it("should rename downloaded file with .zip extension before extracting", async () => {
const downloadPath = "/tmp/abc-123";
const extractedPath = "/tmp/extracted";
(tc.downloadTool as jest.Mock).mockResolvedValue(downloadPath);
(tc.extractZip as jest.Mock).mockResolvedValue(extractedPath);
const installer = new WindowsInstaller(version);
await installer.installCli();
expect(tc.downloadTool).toHaveBeenCalled();
expect(fs.renameSync).toHaveBeenCalledWith(
downloadPath,
`${downloadPath}.zip`,
);
expect(tc.extractZip).toHaveBeenCalledWith(`${downloadPath}.zip`);
expect(core.addPath).toHaveBeenCalledWith(extractedPath);
});
}); });
@@ -1,16 +1,9 @@
import * as fs from "fs";
import * as path from "path";
import * as core from "@actions/core";
import * as tc from "@actions/tool-cache";
import { import {
CliInstaller, CliInstaller,
cliUrlBuilder, cliUrlBuilder,
type SupportedPlatform, type SupportedPlatform,
} from "./cli-installer"; } from "./cli-installer";
import type { Installer } from "./installer"; import type { Installer } from "./installer";
import { verifyAuthenticodeSignature } from "./windows-signature";
export class WindowsInstaller extends CliInstaller implements Installer { export class WindowsInstaller extends CliInstaller implements Installer {
private readonly platform: SupportedPlatform = "win32"; // Node.js platform identifier for Windows private readonly platform: SupportedPlatform = "win32"; // Node.js platform identifier for Windows
@@ -21,24 +14,6 @@ export class WindowsInstaller extends CliInstaller implements Installer {
public async installCli(): Promise<void> { public async installCli(): Promise<void> {
const urlBuilder = cliUrlBuilder[this.platform]; const urlBuilder = cliUrlBuilder[this.platform];
await this.install(urlBuilder(this.version, this.arch)); await super.install(urlBuilder(this.version, this.arch));
}
// Windows PowerShell's Expand-Archive requires files to have a .zip extension.
// tc.downloadTool saves to a UUID filename with no extension, so we rename it.
public override async install(url: string): Promise<void> {
console.info(`Downloading 1Password CLI from: ${url}`);
const downloadPath = await tc.downloadTool(url);
const zipPath = `${downloadPath}.zip`;
fs.renameSync(downloadPath, zipPath);
console.info("Installing 1Password CLI");
const extractedPath = await tc.extractZip(zipPath);
core.info("Verifying 1Password CLI signature");
await verifyAuthenticodeSignature(path.join(extractedPath, "op.exe"));
core.info("1Password CLI signature verified");
core.addPath(extractedPath);
core.info("1Password CLI installed");
} }
} }
+214 -1
View File
@@ -1,6 +1,7 @@
import * as core from "@actions/core"; import * as core from "@actions/core";
import * as exec from "@actions/exec"; import * as exec from "@actions/exec";
import { read, setClientInfo } from "@1password/op-js"; import { read, setClientInfo } from "@1password/op-js";
import { createClient, Secrets } from "@1password/sdk";
import { import {
extractSecret, extractSecret,
loadSecrets, loadSecrets,
@@ -15,7 +16,20 @@ import {
envServiceAccountToken, envServiceAccountToken,
} from "./constants"; } from "./constants";
jest.mock("@actions/core");
jest.mock("@actions/exec", () => ({
getExecOutput: jest.fn(() => ({
stdout: "MOCK_SECRET",
})),
}));
jest.mock("@1password/op-js"); jest.mock("@1password/op-js");
jest.mock("@1password/sdk", () => ({
createClient: jest.fn(),
// eslint-disable-next-line @typescript-eslint/naming-convention
Secrets: {
validateSecretReference: jest.fn(),
},
}));
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@@ -137,7 +151,13 @@ describe("extractSecret", () => {
}); });
}); });
describe("loadSecrets", () => { describe("loadSecrets when using Connect", () => {
beforeEach(() => {
process.env[envConnectHost] = "https://localhost:8000";
process.env[envConnectToken] = "token";
process.env[envServiceAccountToken] = "";
});
it("sets the client info and gets the executed output", async () => { it("sets the client info and gets the executed output", async () => {
await loadSecrets(true); await loadSecrets(true);
@@ -175,6 +195,199 @@ describe("loadSecrets", () => {
}); });
}); });
describe("loadSecrets when using Service Account", () => {
const mockResolve = jest.fn();
beforeEach(() => {
process.env[envConnectHost] = "";
process.env[envConnectToken] = "";
process.env[envServiceAccountToken] = "ops_token";
Object.keys(process.env).forEach((key) => {
if (
typeof process.env[key] === "string" &&
process.env[key]?.startsWith("op://")
) {
delete process.env[key];
}
});
process.env.MY_SECRET = "op://vault/item/field";
(createClient as jest.Mock).mockResolvedValue({
secrets: { resolve: mockResolve },
});
mockResolve.mockResolvedValue("resolved-secret-value");
});
it("does not call op env ls when using Service Account", async () => {
await loadSecrets(false);
expect(exec.getExecOutput).not.toHaveBeenCalled();
});
it("sets step output with resolved value when export-env is false", async () => {
await loadSecrets(false);
expect(core.setOutput).toHaveBeenCalledTimes(1);
expect(core.setOutput).toHaveBeenCalledWith(
"MY_SECRET",
"resolved-secret-value",
);
});
it("masks secret with setSecret when export-env is false", async () => {
await loadSecrets(false);
expect(core.setSecret).toHaveBeenCalledTimes(1);
expect(core.setSecret).toHaveBeenCalledWith("resolved-secret-value");
});
it("does not call exportVariable when export-env is false", async () => {
await loadSecrets(false);
expect(core.exportVariable).not.toHaveBeenCalled();
});
it("exports env and sets OP_MANAGED_VARIABLES when export-env is true", async () => {
await loadSecrets(true);
expect(core.exportVariable).toHaveBeenCalledWith(
"MY_SECRET",
"resolved-secret-value",
);
expect(core.exportVariable).toHaveBeenCalledWith(
envManagedVariables,
"MY_SECRET",
);
});
it("does not set step output when export-env is true", async () => {
await loadSecrets(true);
expect(core.setOutput).not.toHaveBeenCalledWith(
"MY_SECRET",
expect.anything(),
);
});
it("masks secret with setSecret when export-env is true", async () => {
await loadSecrets(true);
expect(core.setSecret).toHaveBeenCalledTimes(1);
expect(core.setSecret).toHaveBeenCalledWith("resolved-secret-value");
});
it("returns early when no env vars have op:// refs", async () => {
Object.keys(process.env).forEach((key) => {
if (
typeof process.env[key] === "string" &&
process.env[key]?.startsWith("op://")
) {
delete process.env[key];
}
});
await loadSecrets(true);
expect(exec.getExecOutput).not.toHaveBeenCalled();
expect(core.exportVariable).not.toHaveBeenCalled();
});
it("wraps createClient errors with a descriptive message", async () => {
(createClient as jest.Mock).mockRejectedValue(
new Error("invalid token format"),
);
await expect(loadSecrets(false)).rejects.toThrow(
"Service account authentication failed: invalid token format",
);
});
describe("multiple refs", () => {
const ref1 = "op://vault/item/field";
const ref2 = "op://vault/other/item";
const ref3 = "op://vault/file/secret";
beforeEach(() => {
process.env.MY_SECRET = ref1;
process.env.ANOTHER_SECRET = ref2;
process.env.FILE_SECRET = ref3;
mockResolve
.mockResolvedValueOnce("value1")
.mockResolvedValueOnce("value2")
.mockResolvedValueOnce("value3");
});
it("resolves each ref and sets step output for each when export-env is false", async () => {
await loadSecrets(false);
expect(mockResolve).toHaveBeenCalledTimes(3);
expect(mockResolve).toHaveBeenCalledWith(ref1);
expect(mockResolve).toHaveBeenCalledWith(ref2);
expect(mockResolve).toHaveBeenCalledWith(ref3);
expect(core.setOutput).toHaveBeenCalledTimes(3);
expect(core.setOutput).toHaveBeenCalledWith("MY_SECRET", "value1");
expect(core.setOutput).toHaveBeenCalledWith("ANOTHER_SECRET", "value2");
expect(core.setOutput).toHaveBeenCalledWith("FILE_SECRET", "value3");
expect(core.setSecret).toHaveBeenCalledTimes(3);
});
it("resolves each ref and exports each and sets OP_MANAGED_VARIABLES when export-env is true", async () => {
await loadSecrets(true);
expect(mockResolve).toHaveBeenCalledTimes(3);
expect(core.exportVariable).toHaveBeenCalledWith("MY_SECRET", "value1");
expect(core.exportVariable).toHaveBeenCalledWith(
"ANOTHER_SECRET",
"value2",
);
expect(core.exportVariable).toHaveBeenCalledWith("FILE_SECRET", "value3");
const exportVariableCalls = (core.exportVariable as jest.Mock).mock
.calls as [string, string][];
const managedVarsCall = exportVariableCalls.find(
([name]) => name === envManagedVariables,
);
expect(managedVarsCall).toBeDefined();
const managedList = (managedVarsCall as [string, string])[1].split(",");
expect(managedList).toContain("MY_SECRET");
expect(managedList).toContain("ANOTHER_SECRET");
expect(managedList).toContain("FILE_SECRET");
expect(managedList).toHaveLength(3);
expect(core.setSecret).toHaveBeenCalledTimes(3);
});
});
describe("secret reference validation", () => {
it("fails with clear message when a secret reference is invalid", async () => {
process.env.MY_SECRET = "op://x";
(Secrets.validateSecretReference as jest.Mock).mockImplementationOnce(
() => {
throw new Error("invalid reference format");
},
);
await expect(loadSecrets(true)).rejects.toThrow(
"Invalid secret reference(s): MY_SECRET",
);
expect(mockResolve).not.toHaveBeenCalled();
});
it("validates all refs before resolving any secrets", async () => {
process.env.MY_SECRET = "op://vault/item/field";
process.env.OTHER = "op://vault/other/item";
(Secrets.validateSecretReference as jest.Mock).mockImplementation(
(ref: string) => {
if (ref === "op://vault/other/item") {
throw new Error("invalid");
}
},
);
await expect(loadSecrets(false)).rejects.toThrow(
"Invalid secret reference(s): OTHER",
);
expect(mockResolve).not.toHaveBeenCalled();
});
});
});
describe("unsetPrevious", () => { describe("unsetPrevious", () => {
const testManagedEnv = "TEST_SECRET"; const testManagedEnv = "TEST_SECRET";
const testSecretValue = "MyS3cr#T"; const testSecretValue = "MyS3cr#T";
+112 -13
View File
@@ -1,6 +1,7 @@
import * as core from "@actions/core"; import * as core from "@actions/core";
import * as exec from "@actions/exec"; import * as exec from "@actions/exec";
import { read, setClientInfo, semverToInt } from "@1password/op-js"; import { read, setClientInfo, semverToInt } from "@1password/op-js";
import { createClient, Secrets } from "@1password/sdk";
import { version } from "../package.json"; import { version } from "../package.json";
import { import {
authErr, authErr,
@@ -29,12 +30,60 @@ export const validateAuth = (): void => {
core.info(`Authenticated with ${authType}.`); core.info(`Authenticated with ${authType}.`);
}; };
export const extractSecret = ( const getEnvVarNamesWithSecretRefs = (): string[] =>
Object.keys(process.env).filter(
(key) =>
typeof process.env[key] === "string" &&
process.env[key]?.startsWith("op://"),
);
const validateSecretRefs = (envNames: string[]): void => {
const invalid: { name: string; message: string }[] = [];
for (const envName of envNames) {
const ref = process.env[envName];
if (!ref) {
continue;
}
try {
Secrets.validateSecretReference(ref);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
invalid.push({ name: envName, message });
}
}
// Throw an error if any secret references are invalid
if (invalid.length > 0) {
const details = invalid
.map(({ name, message }) => `${name}: ${message}`)
.join("; ");
throw new Error(`Invalid secret reference(s): ${details}`);
}
};
const setResolvedSecret = (
envName: string, envName: string,
secretValue: string,
shouldExportEnv: boolean, shouldExportEnv: boolean,
): void => { ): void => {
core.info(`Populating variable: ${envName}`); core.info(`Populating variable: ${envName}`);
if (shouldExportEnv) {
core.exportVariable(envName, secretValue);
} else {
core.setOutput(envName, secretValue);
}
if (secretValue) {
core.setSecret(secretValue);
}
};
export const extractSecret = (
envName: string,
shouldExportEnv: boolean,
): void => {
const ref = process.env[envName]; const ref = process.env[envName];
if (!ref) { if (!ref) {
return; return;
@@ -45,20 +94,13 @@ export const extractSecret = (
return; return;
} }
if (shouldExportEnv) { setResolvedSecret(envName, secretValue, shouldExportEnv);
core.exportVariable(envName, secretValue);
} else {
core.setOutput(envName, secretValue);
}
// Skip setSecret for empty strings to avoid the warning:
// "Can't add secret mask for empty string in ##[add-mask] command."
if (secretValue) {
core.setSecret(secretValue);
}
}; };
export const loadSecrets = async (shouldExportEnv: boolean): Promise<void> => { // Connect loads secrets via the 1Password CLI
// Pass User-Agent Information to the 1Password CLI const loadSecretsViaConnect = async (
shouldExportEnv: boolean,
): Promise<void> => {
setClientInfo({ setClientInfo({
name: "1Password GitHub Action", name: "1Password GitHub Action",
id: "GHA", id: "GHA",
@@ -83,6 +125,63 @@ export const loadSecrets = async (shouldExportEnv: boolean): Promise<void> => {
} }
}; };
// Service Account loads secrets via the 1Password SDK
const loadSecretsViaServiceAccount = async (
shouldExportEnv: boolean,
): Promise<void> => {
const envs = getEnvVarNamesWithSecretRefs();
if (envs.length === 0) {
return;
}
validateSecretRefs(envs);
const token = process.env[envServiceAccountToken];
if (!token) {
throw new Error(authErr);
}
// Authenticate with the 1Password SDK
let client;
try {
client = await createClient({
auth: token,
integrationName: "1Password GitHub Action",
integrationVersion: version,
});
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
throw new Error(`Service account authentication failed: ${message}`);
}
for (const envName of envs) {
const ref = process.env[envName];
if (!ref) {
continue;
}
// Resolve the secret value using the 1Password SDK
// and make it available either as step outputs or as environment variables
const secretValue = await client.secrets.resolve(ref);
setResolvedSecret(envName, secretValue, shouldExportEnv);
}
if (shouldExportEnv) {
core.exportVariable(envManagedVariables, envs.join());
}
};
export const loadSecrets = async (shouldExportEnv: boolean): Promise<void> => {
const isConnect = process.env[envConnectHost] && process.env[envConnectToken];
if (isConnect) {
await loadSecretsViaConnect(shouldExportEnv);
return;
}
await loadSecretsViaServiceAccount(shouldExportEnv);
};
export const unsetPrevious = (): void => { export const unsetPrevious = (): void => {
if (process.env[envManagedVariables]) { if (process.env[envManagedVariables]) {
core.info("Unsetting previous values ..."); core.info("Unsetting previous values ...");
-7
View File
@@ -26,7 +26,6 @@ IApTbyBwbGVhc2UgZG9uJ3QgcmVwb3J0IGl0IQo=
EOF EOF
)" )"
readonly MULTILINE_SECRET readonly MULTILINE_SECRET
readonly WEBSITE="www.test.com"
assert_env_equals "SECRET" "${SECRET}" assert_env_equals "SECRET" "${SECRET}"
assert_env_equals "FILE_SECRET" "${SECRET}" assert_env_equals "FILE_SECRET" "${SECRET}"
@@ -36,9 +35,3 @@ assert_env_equals "FILE_SECRET_IN_SECTION" "${SECRET}"
assert_env_equals "MULTILINE_SECRET" "${MULTILINE_SECRET}" assert_env_equals "MULTILINE_SECRET" "${MULTILINE_SECRET}"
assert_env_equals "FILE_MULTILINE_SECRET" "${MULTILINE_SECRET}" assert_env_equals "FILE_MULTILINE_SECRET" "${MULTILINE_SECRET}"
# WEBSITE/FILE_WEBSITE: required when ASSERT_WEBSITE=true (Service Account), skipped when false (Connect)
if [ "${ASSERT_WEBSITE:-false}" = "true" ]; then
assert_env_equals "WEBSITE" "${WEBSITE}"
assert_env_equals "FILE_WEBSITE" "${WEBSITE}"
fi
-8
View File
@@ -17,11 +17,3 @@ assert_env_unset "FILE_SECRET_IN_SECTION"
assert_env_unset "MULTILINE_SECRET" assert_env_unset "MULTILINE_SECRET"
assert_env_unset "FILE_MULTILINE_SECRET" assert_env_unset "FILE_MULTILINE_SECRET"
assert_env_unset "WEBSITE"
assert_env_unset "FILE_WEBSITE"
assert_env_unset "TEST_SSH_KEY"
assert_env_unset "FILE_TEST_SSH_KEY"
assert_env_unset "TEST_SSH_KEY_OPENSSH"
assert_env_unset "FILE_TEST_SSH_KEY_OPENSSH"
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
set -e
if [ "$STEP_OUTCOME" != "failure" ]; then
echo "Expected action to fail on invalid ref, got: $STEP_OUTCOME"
exit 1
fi
echo "Action correctly failed on invalid ref"
-26
View File
@@ -1,26 +0,0 @@
#!/bin/bash
set -e
assert_ssh_key_set() {
local var="$1"
local val
val="$(printenv "$var" || true)"
if [ -z "$val" ]; then
echo "Expected $var to be set"
exit 1
fi
[ "$val" = "***" ] && return 0
local line
line="$(echo "$val" | head -1)"
if echo "$var" | grep -q "OPENSSH"; then
echo "$line" | grep -q "OPENSSH" || { echo "Expected $var to start with -----BEGIN OPENSSH PRIVATE KEY-----"; exit 1; }
else
echo "$line" | grep -q "BEGIN.*PRIVATE KEY" || { echo "Expected $var to be a private key"; exit 1; }
fi
echo "$var OK"
}
assert_ssh_key_set "TEST_SSH_KEY"
assert_ssh_key_set "TEST_SSH_KEY_OPENSSH"
assert_ssh_key_set "FILE_TEST_SSH_KEY"
assert_ssh_key_set "FILE_TEST_SSH_KEY_OPENSSH"