Skip to content

Commit b57b851

Browse files
Authenticate CI via GitHub OIDC and AWS Secrets Manager
Migrate CI credential handling to GitHub OIDC + AWS Secrets Manager, mirroring dropbox-sdk-dotnet: credential-consuming jobs assume an AWS IAM role and fetch the api-sdk-integration-test-creds and codecov-token secrets, replacing long-lived GitHub secrets. Credential-gated jobs are skipped on fork PRs, which cannot assume the role. Also stop injecting the unused legacy user token and authenticate the team integration fixture with a refresh token instead of a static token.
1 parent eee36cb commit b57b851

3 files changed

Lines changed: 99 additions & 31 deletions

File tree

.github/workflows/ci.yml

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,13 @@ jobs:
9393
sphinx-build -b html docs build/html
9494
9595
Integration:
96-
continue-on-error: true
96+
# Skip on fork PRs: they cannot assume the OIDC role.
97+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
98+
continue-on-error: false
9799
runs-on: ${{ matrix.os }}
100+
permissions: # required for OIDC
101+
id-token: write
102+
contents: read
98103
strategy:
99104
fail-fast: false
100105
max-parallel: 1
@@ -126,19 +131,27 @@ jobs:
126131
run: |
127132
python -m pip install "$(python -c "import glob,sys; w=glob.glob('dist/*.whl'); assert w, f'No wheel built. dist contains: {glob.glob(\"dist/*\")}'; print(w[0])")"
128133
134+
- name: Configure AWS credentials (OIDC)
135+
uses: aws-actions/configure-aws-credentials@v6
136+
with:
137+
role-to-assume: arn:aws:iam::082972943155:role/oidc-github-dropbox-dropbox-sdk-python-repo
138+
aws-region: us-west-2
139+
140+
- name: Get integration credentials from AWS Secrets Manager
141+
uses: aws-actions/aws-secretsmanager-get-secrets@v3
142+
with:
143+
secret-ids: |
144+
CREDS,api-sdk-integration-test-creds
145+
parse-json-secrets: true
146+
129147
- name: Run integration tests
130148
env:
131-
LEGACY_USER_DROPBOX_TOKEN: ${{ secrets.LEGACY_USER_DROPBOX_TOKEN }}
132-
LEGACY_USER_CLIENT_ID: ${{ secrets.LEGACY_USER_CLIENT_ID }}
133-
LEGACY_USER_CLIENT_SECRET: ${{ secrets.LEGACY_USER_CLIENT_SECRET }}
134-
LEGACY_USER_REFRESH_TOKEN: ${{ secrets.LEGACY_USER_REFRESH_TOKEN }}
135-
SCOPED_USER_CLIENT_ID: ${{ secrets.SCOPED_USER_CLIENT_ID }}
136-
SCOPED_USER_CLIENT_SECRET: ${{ secrets.SCOPED_USER_CLIENT_SECRET }}
137-
SCOPED_USER_REFRESH_TOKEN: ${{ secrets.SCOPED_USER_REFRESH_TOKEN }}
138-
SCOPED_TEAM_DROPBOX_TOKEN: ${{ secrets.SCOPED_TEAM_DROPBOX_TOKEN }}
139-
SCOPED_TEAM_CLIENT_ID: ${{ secrets.SCOPED_TEAM_CLIENT_ID }}
140-
SCOPED_TEAM_CLIENT_SECRET: ${{ secrets.SCOPED_TEAM_CLIENT_SECRET }}
141-
SCOPED_TEAM_REFRESH_TOKEN: ${{ secrets.SCOPED_TEAM_REFRESH_TOKEN }}
142-
DROPBOX_SHARED_LINK: ${{ secrets.DROPBOX_SHARED_LINK }}
149+
SCOPED_USER_CLIENT_ID: ${{ env.CREDS_SCOPED_USER_CLIENT_ID }}
150+
SCOPED_USER_CLIENT_SECRET: ${{ env.CREDS_SCOPED_USER_CLIENT_SECRET }}
151+
SCOPED_USER_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_USER_REFRESH_TOKEN }}
152+
SCOPED_TEAM_CLIENT_ID: ${{ env.CREDS_SCOPED_TEAM_CLIENT_ID }}
153+
SCOPED_TEAM_CLIENT_SECRET: ${{ env.CREDS_SCOPED_TEAM_CLIENT_SECRET }}
154+
SCOPED_TEAM_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_TEAM_REFRESH_TOKEN }}
155+
DROPBOX_SHARED_LINK: ${{ env.CREDS_DROPBOX_SHARED_LINK }}
143156
run: |
144157
pytest -v test/integration/test_dropbox.py

.github/workflows/coverage.yml

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,51 @@ jobs:
3232
run: |
3333
coverage run --rcfile=.coveragerc -m pytest test/unit/
3434
coverage xml
35+
- name: Upload coverage artifact
36+
uses: actions/upload-artifact@v4
37+
with:
38+
name: unit-coverage
39+
path: coverage.xml
40+
# Separate job so the whole thing can be gated: fork PRs cannot assume the
41+
# OIDC role, so they run the unit tests above but skip the upload here.
42+
CoverageUpload:
43+
needs: Coverage
44+
# Skip on fork PRs: they cannot assume the OIDC role.
45+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
46+
runs-on: ubuntu-latest
47+
permissions: # required for OIDC
48+
id-token: write
49+
contents: read
50+
steps:
51+
- uses: actions/checkout@v7
52+
- name: Download coverage artifact
53+
uses: actions/download-artifact@v4
54+
with:
55+
name: unit-coverage
56+
- name: Configure AWS credentials (OIDC)
57+
uses: aws-actions/configure-aws-credentials@v6
58+
with:
59+
role-to-assume: arn:aws:iam::082972943155:role/oidc-github-dropbox-dropbox-sdk-python-repo
60+
aws-region: us-west-2
61+
- name: Get Codecov token from AWS Secrets Manager
62+
uses: aws-actions/aws-secretsmanager-get-secrets@v3
63+
with:
64+
secret-ids: |
65+
CODECOV_TOKEN,codecov-token-dropbox-sdk-python
66+
parse-json-secrets: false
3567
- name: Publish Coverage
3668
uses: codecov/codecov-action@v7
3769
with:
38-
token: ${{ secrets.CODECOV_TOKEN }}
70+
token: ${{ env.CODECOV_TOKEN }}
3971
flags: unit
4072
fail_ci_if_error: true
4173
IntegrationCoverage:
74+
# Skip on fork PRs: they cannot assume the OIDC role.
75+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
4276
runs-on: ubuntu-latest
77+
permissions: # required for OIDC
78+
id-token: write
79+
contents: read
4380
steps:
4481
- uses: actions/checkout@v7
4582
- name: Setup Python environment
@@ -58,26 +95,38 @@ jobs:
5895
pip install -r requirements.txt
5996
pip install -r test/requirements.txt
6097
pip install .
61-
- name: Generate Unit Test Coverage
98+
- name: Configure AWS credentials (OIDC)
99+
uses: aws-actions/configure-aws-credentials@v6
100+
with:
101+
role-to-assume: arn:aws:iam::082972943155:role/oidc-github-dropbox-dropbox-sdk-python-repo
102+
aws-region: us-west-2
103+
- name: Get integration credentials from AWS Secrets Manager
104+
uses: aws-actions/aws-secretsmanager-get-secrets@v3
105+
with:
106+
secret-ids: |
107+
CREDS,api-sdk-integration-test-creds
108+
parse-json-secrets: true
109+
- name: Get Codecov token from AWS Secrets Manager
110+
uses: aws-actions/aws-secretsmanager-get-secrets@v3
111+
with:
112+
secret-ids: |
113+
CODECOV_TOKEN,codecov-token-dropbox-sdk-python
114+
parse-json-secrets: false
115+
- name: Generate Coverage
62116
env:
63-
LEGACY_USER_DROPBOX_TOKEN: ${{ secrets.LEGACY_USER_DROPBOX_TOKEN }}
64-
LEGACY_USER_CLIENT_ID: ${{ secrets.LEGACY_USER_CLIENT_ID }}
65-
LEGACY_USER_CLIENT_SECRET: ${{ secrets.LEGACY_USER_CLIENT_SECRET }}
66-
LEGACY_USER_REFRESH_TOKEN: ${{ secrets.LEGACY_USER_REFRESH_TOKEN }}
67-
SCOPED_USER_CLIENT_ID: ${{ secrets.SCOPED_USER_CLIENT_ID }}
68-
SCOPED_USER_CLIENT_SECRET: ${{ secrets.SCOPED_USER_CLIENT_SECRET }}
69-
SCOPED_USER_REFRESH_TOKEN: ${{ secrets.SCOPED_USER_REFRESH_TOKEN }}
70-
SCOPED_TEAM_DROPBOX_TOKEN: ${{ secrets.SCOPED_TEAM_DROPBOX_TOKEN }}
71-
SCOPED_TEAM_CLIENT_ID: ${{ secrets.SCOPED_TEAM_CLIENT_ID }}
72-
SCOPED_TEAM_CLIENT_SECRET: ${{ secrets.SCOPED_TEAM_CLIENT_SECRET }}
73-
SCOPED_TEAM_REFRESH_TOKEN: ${{ secrets.SCOPED_TEAM_REFRESH_TOKEN }}
74-
DROPBOX_SHARED_LINK: ${{ secrets.DROPBOX_SHARED_LINK }}
117+
SCOPED_USER_CLIENT_ID: ${{ env.CREDS_SCOPED_USER_CLIENT_ID }}
118+
SCOPED_USER_CLIENT_SECRET: ${{ env.CREDS_SCOPED_USER_CLIENT_SECRET }}
119+
SCOPED_USER_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_USER_REFRESH_TOKEN }}
120+
SCOPED_TEAM_CLIENT_ID: ${{ env.CREDS_SCOPED_TEAM_CLIENT_ID }}
121+
SCOPED_TEAM_CLIENT_SECRET: ${{ env.CREDS_SCOPED_TEAM_CLIENT_SECRET }}
122+
SCOPED_TEAM_REFRESH_TOKEN: ${{ env.CREDS_SCOPED_TEAM_REFRESH_TOKEN }}
123+
DROPBOX_SHARED_LINK: ${{ env.CREDS_DROPBOX_SHARED_LINK }}
75124
run: |
76125
coverage run --rcfile=.coveragerc -m pytest test/integration/test_dropbox.py
77126
coverage xml
78127
- name: Publish Coverage
79128
uses: codecov/codecov-action@v7
80129
with:
81-
token: ${{ secrets.CODECOV_TOKEN }}
130+
token: ${{ env.CODECOV_TOKEN }}
82131
flags: integration
83132
fail_ci_if_error: true

test/integration/test_dropbox.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,15 @@ def bad_secret_dbx_from_env(dbx_session):
114114

115115
@pytest.fixture()
116116
def dbx_team_from_env(dbx_session):
117-
team_oauth2_token = _value_from_env_or_die(
118-
format_env_name(SCOPED_KEY, TEAM_KEY, ACCESS_TOKEN_KEY))
119-
return DropboxTeam(team_oauth2_token, session=dbx_session)
117+
refresh_token = _value_from_env_or_die(
118+
format_env_name(SCOPED_KEY, TEAM_KEY, REFRESH_TOKEN_KEY))
119+
app_key = _value_from_env_or_die(
120+
format_env_name(SCOPED_KEY, TEAM_KEY, CLIENT_ID_KEY))
121+
app_secret = _value_from_env_or_die(
122+
format_env_name(SCOPED_KEY, TEAM_KEY, CLIENT_SECRET_KEY))
123+
return DropboxTeam(oauth2_refresh_token=refresh_token,
124+
app_key=app_key, app_secret=app_secret,
125+
session=dbx_session)
120126

121127

122128
@pytest.fixture()

0 commit comments

Comments
 (0)