diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 43d0fc86..4b367034 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,14 +5,24 @@ on: [push, pull_request] jobs: build: runs-on: ubuntu-latest + permissions: + id-token: write # required to request the GitHub OIDC token for AWS + contents: read steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 + # Pull requests from forks receive neither secrets nor an OIDC token, so this + # step is skipped for them; the AWS tests then skip too, exactly as they do + # today. Without the guard the step would hard-fail every fork PR. + - uses: aws-actions/configure-aws-credentials@v4 + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository + with: + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + aws-region: us-east-1 - run: npm ci - run: npm run jslint - - run: npm run test:unit - name: Install Docker Compose run: | sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose @@ -22,8 +32,9 @@ jobs: - run: npm test env: GCP_JSON_KEY: ${{ secrets.GCP_JSON_KEY }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.AWS_REGION }} + # No AWS keys are stored as repository secrets. configure-aws-credentials + # above provides short-lived OIDC credentials, and AWS_ROLE_ARN makes the + # test speech credential use the AssumeRole path, which accepts them. + AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }} MICROSOFT_REGION: ${{ secrets.MICROSOFT_REGION }} - MICROSOFT_API_KEY: ${{ secrets.MICROSOFT_API_KEY }} \ No newline at end of file + MICROSOFT_API_KEY: ${{ secrets.MICROSOFT_API_KEY }} diff --git a/lib/config.js b/lib/config.js index 806d3834..8158de66 100644 --- a/lib/config.js +++ b/lib/config.js @@ -93,6 +93,7 @@ const getCleanupIntervalMins = () => { const AWS_REGION = process.env.AWS_REGION; const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY; +const AWS_ROLE_ARN = process.env.AWS_ROLE_ARN; const AWS_SNS_PORT = parseInt(process.env.AWS_SNS_PORT, 10) || 3001; const AWS_SNS_TOPIC_ARN = process.env.AWS_SNS_TOPIC_ARN; const AWS_SNS_PORT_MAX = parseInt(process.env.AWS_SNS_PORT_MAX, 10) || 3005; @@ -198,6 +199,7 @@ module.exports = { AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, + AWS_ROLE_ARN, AWS_SNS_PORT, AWS_SNS_TOPIC_ARN, AWS_SNS_PORT_MAX, diff --git a/test/create-test-db.js b/test/create-test-db.js index 88613596..d5a73c08 100644 --- a/test/create-test-db.js +++ b/test/create-test-db.js @@ -5,6 +5,7 @@ const {encrypt} = require('../lib/utils/encrypt-decrypt'); const { GCP_JSON_KEY, AWS_ACCESS_KEY_ID, + AWS_ROLE_ARN, AWS_SECRET_ACCESS_KEY, AWS_REGION, MICROSOFT_REGION, @@ -32,7 +33,19 @@ test('creating schema', (t) => { t.pass('adding google credentials'); sql.push(`UPDATE speech_credentials SET credential='${google_credential}' WHERE vendor='google';`); } - if (AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY) { + // Prefer role_arn. Under GitHub OIDC the ambient credentials are temporary, and + // speech-utils' getAwsAuthToken calls GetSessionToken on its access-key branch -- + // which AWS rejects for session credentials. The role_arn branch calls AssumeRole + // instead, which works with temporary credentials. + if (AWS_ROLE_ARN) { + const aws_credential = encrypt(JSON.stringify({ + role_arn: AWS_ROLE_ARN, + aws_region: AWS_REGION + })); + t.pass('adding aws credentials (role_arn)'); + sql.push(`UPDATE speech_credentials SET credential='${aws_credential}' WHERE vendor='aws';`); + } + else if (AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY) { const aws_credential = encrypt(JSON.stringify({ access_key_id: AWS_ACCESS_KEY_ID, secret_access_key: AWS_SECRET_ACCESS_KEY, diff --git a/test/gather-tests.js b/test/gather-tests.js index 904201db..11cb7b0a 100644 --- a/test/gather-tests.js +++ b/test/gather-tests.js @@ -7,6 +7,7 @@ const {provisionCallHook} = require('./utils') const { GCP_JSON_KEY, AWS_ACCESS_KEY_ID, + AWS_ROLE_ARN, AWS_SECRET_ACCESS_KEY, SONIOX_API_KEY, DEEPGRAM_API_KEY, @@ -190,7 +191,7 @@ test('\'gather\' test - microsoft', async(t) => { }); test('\'gather\' test - aws', async(t) => { - if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) { + if (!AWS_ROLE_ARN && (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY)) { t.pass('skipping aws tests'); return t.end(); } diff --git a/test/transcribe-tests.js b/test/transcribe-tests.js index a1939ced..30273b45 100644 --- a/test/transcribe-tests.js +++ b/test/transcribe-tests.js @@ -6,7 +6,8 @@ const clearModule = require('clear-module'); const {provisionCallHook} = require('./utils') const { GCP_JSON_KEY, - AWS_ACCESS_KEY_ID, + AWS_ACCESS_KEY_ID, + AWS_ROLE_ARN, AWS_SECRET_ACCESS_KEY, MICROSOFT_REGION, MICROSOFT_API_KEY, @@ -103,7 +104,7 @@ test('\'transcribe\' test - microsoft', async(t) => { }); test('\'transcribe\' test - aws', async(t) => { - if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) { + if (!AWS_ROLE_ARN && (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY)) { t.pass('skipping aws tests'); return t.end(); }