Skip to content

Commit 193bcea

Browse files
authored
Harden CI: Artifactory OIDC via sdk-actions, NuGet lockfiles, scoped build (#146)
Routes CI dependency resolution through curated Artifactory using OIDC, with no stored credentials. Supply chain: - Artifactory OIDC via the shared first-party twilio/sdk-actions/artifactory-oidc (ecosystem: dotnet), so fixes land once across the SDKs rather than per repo. - NuGet lockfiles committed, restored with --locked-mode. They record package identity and hash only, so they resolve identically from Artifactory in CI and from nuget.org locally. - SHA-pinned actions, tightened workflow permissions, and github.ref_name passed via env var rather than interpolated into a shell command. Publishing: - deploy.yml is tag-triggered and publishes from environment: deployment, which is where NUGET_API_KEY lives. Environment secrets are only visible to jobs naming that exact environment, and GitHub silently creates an unprotected environment for any name it does not know. Build correctness, all of which only became visible once the OIDC exchange started succeeding: - setup-dotnet installs both 6.0.x and 10.0.x; Tests targets net10.0 and net6.0, and the previous 8.0.x pin only worked because the runner image ships SDK 10. - Tests/packages.lock.json regenerated to match the project. It listed .NETFramework,v4.6, which nothing targets, and lacked net10.0, so --locked-mode refused with NU1004. - Restore, build and test are scoped via Analytics-CSharp.slnf to the library and its tests. The .sln also lists sample projects, several of them legacy msbuild Xamarin and Unity ones that dotnet cannot build on a Linux runner. The published package still targets netstandard1.3 and netstandard2.0; nothing here changes what consumers can install.
1 parent c4c92c7 commit 193bcea

7 files changed

Lines changed: 1600 additions & 8 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
workflow_dispatch:
9+
10+
permissions:
11+
id-token: write
12+
contents: read
13+
14+
env:
15+
ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }}
16+
17+
jobs:
18+
build:
19+
runs-on: ${{ github.event.pull_request.head.repo.fork && 'ubuntu-latest' || 'ubuntu-x64' }}
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
24+
25+
- name: Setup .NET SDK
26+
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
27+
with:
28+
# Tests target net10.0 and net6.0, so both runtimes are needed to
29+
# execute them; the 10 SDK alone only covers the former.
30+
dotnet-version: |
31+
6.0.x
32+
10.0.x
33+
34+
- name: Configure Artifactory NuGet source
35+
if: ${{ !github.event.pull_request.head.repo.fork }}
36+
uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main
37+
with:
38+
ecosystem: dotnet
39+
provider-name: github-actions-segmentio
40+
41+
# Scoped to the library and its tests via the solution filter. The sample
42+
# projects in the .sln include legacy msbuild Xamarin and Unity ones, which
43+
# dotnet cannot restore or build on a Linux runner at all.
44+
- name: Restore dependencies
45+
run: dotnet restore Analytics-CSharp.slnf --locked-mode
46+
47+
- name: Build
48+
run: dotnet build Analytics-CSharp.slnf --no-restore
49+
50+
- name: Test
51+
run: dotnet test Analytics-CSharp.slnf --no-build --logger "trx;LogFileName=test-results.trx"
52+
53+
- name: Upload test results
54+
if: always()
55+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
56+
with:
57+
name: test-results
58+
path: '**/TestResults/*.trx'
59+
if-no-files-found: ignore

‎.github/workflows/deploy.yml‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
name: Deploy
2+
3+
on:
4+
push:
5+
tags:
6+
- '[0-9]+.[0-9]+.[0-9]+'
7+
- '[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+'
8+
9+
permissions:
10+
id-token: write
11+
contents: read
12+
13+
env:
14+
ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }}
15+
16+
jobs:
17+
verify:
18+
runs-on: ubuntu-x64
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
22+
23+
- name: Get tag
24+
id: tag
25+
run: echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
26+
27+
- name: Verify tag matches package version
28+
run: |
29+
VERSION=$(grep '<Version>' Analytics-CSharp/Analytics-CSharp.csproj | sed "s@.*<Version>\(.*\)</Version>.*@\1@")
30+
if [ "${{ steps.tag.outputs.version }}" != "$VERSION" ]; then
31+
echo "::error::Tag ${{ steps.tag.outputs.version }} does not match the package version ($VERSION)"
32+
exit 1
33+
fi
34+
35+
- name: Setup .NET SDK
36+
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
37+
with:
38+
dotnet-version: '8.0.x'
39+
40+
- name: Configure Artifactory NuGet source
41+
uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main
42+
with:
43+
ecosystem: dotnet
44+
provider-name: github-actions-segmentio
45+
46+
- name: Restore dependencies
47+
run: dotnet restore --locked-mode
48+
49+
- name: Build
50+
run: dotnet build --no-restore
51+
52+
- name: Test
53+
run: dotnet test --no-build
54+
55+
publish:
56+
needs: verify
57+
runs-on: ubuntu-x64
58+
# Must be `deployment`: NUGET_API_KEY is an environment secret scoped to it,
59+
# and environment secrets are invisible to jobs using any other name.
60+
# Naming a non-existent environment also silently creates an unprotected one.
61+
environment: deployment
62+
63+
steps:
64+
- name: Checkout
65+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
66+
67+
- name: Setup .NET SDK
68+
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
69+
with:
70+
dotnet-version: '8.0.x'
71+
72+
- name: Configure Artifactory NuGet source
73+
uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main
74+
with:
75+
ecosystem: dotnet
76+
provider-name: github-actions-segmentio
77+
78+
- name: Restore dependencies
79+
run: dotnet restore --locked-mode
80+
81+
- name: Pack
82+
run: dotnet pack --no-restore -c Release
83+
84+
- name: Push to NuGet.org
85+
run: |
86+
dotnet nuget push "**/*.nupkg" \
87+
--source https://api.nuget.org/v3/index.json \
88+
--api-key ${{ secrets.NUGET_API_KEY }} \
89+
--skip-duplicate
90+
91+
- name: Create GitHub release
92+
env:
93+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
94+
REF_NAME: ${{ github.ref_name }}
95+
run: |
96+
gh release create "$REF_NAME" \
97+
--title "$REF_NAME" \
98+
--generate-notes

‎.github/workflows/e2e-tests.yml‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,45 @@ on:
1212
required: false
1313
default: 'main'
1414

15+
permissions:
16+
id-token: write
17+
contents: read
18+
19+
env:
20+
ARTIFACTORY_URL: ${{ vars.ARTIFACTORY_URL }}
21+
1522
jobs:
1623
e2e-tests:
17-
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
18-
runs-on: ubuntu-latest
24+
# Only run for internal PRs and pushes (not forks — e2e tests need internal access)
25+
if: ${{ !github.event.pull_request.head.repo.fork }}
26+
runs-on: ubuntu-x64
1927

2028
steps:
2129
- name: Checkout SDK
22-
uses: actions/checkout@v4
30+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
2331
with:
2432
path: sdk
2533

2634
- name: Checkout sdk-e2e-tests
27-
uses: actions/checkout@v4
35+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
2836
with:
2937
repository: segmentio/sdk-e2e-tests
3038
ref: ${{ inputs.e2e_tests_ref || 'main' }}
31-
token: ${{ secrets.E2E_TESTS_TOKEN }}
3239
path: sdk-e2e-tests
3340

3441
- name: Setup .NET
35-
uses: actions/setup-dotnet@v4
42+
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
3643
with:
3744
dotnet-version: '10.0.x'
3845

46+
- name: Configure Artifactory NuGet source
47+
uses: twilio/sdk-actions/artifactory-oidc@c94e420aa64ea686ff25bb03d4c66cdaf8e523e4 # main
48+
with:
49+
ecosystem: dotnet
50+
provider-name: github-actions-segmentio
51+
3952
- name: Setup Node.js
40-
uses: actions/setup-node@v4
53+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
4154
with:
4255
node-version: '20'
4356

@@ -54,7 +67,7 @@ jobs:
5467
5568
- name: Upload test results
5669
if: always()
57-
uses: actions/upload-artifact@v4
70+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
5871
with:
5972
name: e2e-test-results
6073
path: sdk-e2e-tests/test-results/

‎Analytics-CSharp.slnf‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"solution": {
3+
"path": "Analytics-CSharp.sln",
4+
"projects": [
5+
"Analytics-CSharp\\Analytics-CSharp.csproj",
6+
"Tests\\Tests.csproj"
7+
]
8+
}
9+
}

0 commit comments

Comments
 (0)