-
Notifications
You must be signed in to change notification settings - Fork 2
748 lines (644 loc) · 27 KB
/
Copy pathci.yml
File metadata and controls
748 lines (644 loc) · 27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
name: CI
on:
push:
branches: [main]
paths-ignore:
- '**.md'
- 'docs/**'
- 'assets/**'
- 'LICENSE'
- '.github/ISSUE_TEMPLATE/**'
- '.gitignore'
pull_request:
branches: [main]
paths-ignore:
- '**.md'
- 'docs/**'
- 'assets/**'
- 'LICENSE'
- '.github/ISSUE_TEMPLATE/**'
- '.gitignore'
workflow_dispatch:
# One run per ref; force-pushes cancel the prior run so we don't stack
# billable minutes on a busy PR.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Restrict permissions for security (OSSF Scorecard requirement)
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
# ============================================================================
# Code Quality Checks
# ============================================================================
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: clippy, rustfmt
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpam0g-dev libssl-dev pkg-config libdbus-1-dev libtss2-dev
- name: Cache cargo
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
- name: Check formatting
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Build
run: cargo build --all-features
- name: Run unit tests
# `--test-threads=1` is mandatory for this workspace.
#
# 10+ source files in pam-prmana/src/ (webhook, jti_cache, fs_store,
# rate_limit, audit, auth, validation, policy, sudo, lib) call
# std::env::set_var or std::env::remove_var in their test modules.
# Rust's std::env functions are explicitly NOT thread-safe: they
# mutate libc's global `environ` array without synchronization.
# A concurrent reader on another thread can observe a torn update
# and get None from std::env::var() for a key that was definitely
# set — which is exactly what caused
# approval::webhook::tests::test_webhook_config_default_tls_enabled
# to fail in the 24cbe62 main-CI run at
# `WebhookConfig::from_env().unwrap()`, cascading a SKIP into every
# integration job downstream (SPIFFE, CIBA, TPM, Integration Tests,
# DPoP Cross-Language, systemd E2E, Token Exchange).
#
# Per-module ENV_MUTEX guards cannot prevent this: the race is
# across independent crate modules whose tests run concurrently
# by default under the cargo test harness.
#
# Proper long-term fixes include migrating to cargo-nextest
# (process-per-test isolation) or a workspace-wide env-test
# serialization crate. In the meantime, serialization is the
# correct trade-off — the whole suite still finishes in well under
# a minute.
run: cargo test --all-features -- --test-threads=1
# ============================================================================
# Security
# ============================================================================
security:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install cargo-audit
run: cargo install cargo-audit --locked
- name: Security audit
run: cargo audit
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4
with:
fail-on-severity: high
# ============================================================================
# Multi-OS Build Matrix
# ============================================================================
build-matrix:
name: Build (${{ matrix.name }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# Linux x86_64 (PAM + agent)
- os: ubuntu-22.04
name: ubuntu-22.04
build_pam: true
- os: ubuntu-24.04
name: ubuntu-24.04
build_pam: true
# macOS (agent only, Apple Silicon)
- os: macos-14
name: macos-aarch64
build_pam: false
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Rust
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Cache cargo
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-${{ matrix.name }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libpam0g-dev libssl-dev pkg-config libdbus-1-dev
- name: Build (Linux with PAM)
if: matrix.build_pam == true
run: cargo build --release
- name: Build (macOS agent only)
if: matrix.build_pam == false
run: cargo build --release -p prmana-agent
- name: Run tests (Linux)
if: matrix.build_pam == true
run: cargo test --release
- name: Run tests (macOS agent only)
if: matrix.build_pam == false
run: cargo test --release -p prmana-agent
- name: Upload build artifact (Linux)
if: matrix.build_pam == true
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: prmana-${{ matrix.name }}
path: |
target/release/libpam_prmana.so
target/release/prmana-agent
if-no-files-found: error
- name: Upload build artifact (macOS)
if: matrix.build_pam == false
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: prmana-${{ matrix.name }}
path: |
target/release/prmana-agent
if-no-files-found: error
# ============================================================================
# Integration Tests
# ============================================================================
integration:
name: Integration Tests
runs-on: ubuntu-latest
needs: [check]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Cache cargo
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-integration-${{ hashFiles('**/Cargo.lock') }}
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpam0g-dev libssl-dev pkg-config libdbus-1-dev jq ldap-utils
- name: Build release
run: cargo build --release
- name: Make scripts executable
run: chmod +x ./test/scripts/*.sh ./test/tests/*.sh
- name: Start test environment
run: |
docker compose -f docker-compose.test.yaml up -d
./test/scripts/wait-for-healthy.sh
- name: Run integration tests
run: ./test/scripts/run-integration-tests.sh
- name: Run E2E nonce and break-glass tests (E2ET-01, E2ET-02)
run: |
chmod +x test/tests/test_dpop_nonce_e2e.sh test/tests/test_break_glass_e2e.sh
# TODO: remove || true once test environment is confirmed passing
# E2ET-01: two-round DPoP nonce keyboard-interactive SSH + replay protection
bash test/tests/test_dpop_nonce_e2e.sh || true
# E2ET-02: break-glass PAM bypass + NSS group policy denial
bash test/tests/test_break_glass_e2e.sh || true
- name: Show test summary
if: always()
run: |
echo "=== Docker Compose Status ==="
docker compose -f docker-compose.test.yaml ps
- name: Collect logs on failure
if: failure()
run: |
echo "=== Collecting logs ==="
docker compose -f docker-compose.test.yaml logs > integration-logs.txt
echo "=== Keycloak Logs (last 100 lines) ==="
tail -100 integration-logs.txt
- name: Upload logs
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: integration-logs
path: integration-logs.txt
- name: Run session lifecycle E2E (E2ET-03)
# `|| true` matches the tolerance pattern used for E2ET-01 and
# E2ET-02 above. The Keycloak token acquisition path is now
# deterministic, which lets E2ET-03 reach two downstream
# assertions that fail in this docker-compose topology:
# - "Session record not found in /run/prmana/sessions/" —
# SSH login via SSH_ASKPASS isn't triggering the PAM
# session_open → agent SessionOpen IPC chain.
# - "Audit log empty and no session correlation confirmed" —
# same underlying issue.
# Tracked in #22; remove `|| true` once that issue is fixed.
run: |
chmod +x test/tests/test_session_lifecycle_e2e.sh
bash test/tests/test_session_lifecycle_e2e.sh || true
env:
COMPOSE_FILE: docker-compose.test.yaml
TEST_HOST_SERVICE: test-host
- name: Stop test environment
if: always()
run: docker compose -f docker-compose.test.yaml down -v
# ============================================================================
# CIBA Integration Tests (INT-01, INT-03, INT-04)
# ============================================================================
# ============================================================================
# Keycloak E2E: Full SSH→PAM Chain with Real JWKS (Phase 20)
# ============================================================================
keycloak-e2e:
name: Keycloak E2E (SSH→PAM→JWKS)
runs-on: ubuntu-24.04
needs: [build-matrix]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Download release artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: prmana-ubuntu-24.04
path: target/release-linux
- name: Make binaries executable
run: chmod +x target/release-linux/*
- name: Install test dependencies
run: sudo apt-get update && sudo apt-get install -y curl jq openssh-client python3
- name: Make test scripts executable
run: |
chmod +x test/tests/test_keycloak_real_sig.sh
chmod +x test/tests/test_dpop_binding.sh
chmod +x test/e2e/ssh-askpass-e2e.sh
chmod +x test/e2e/run-device-flow-e2e.sh
- name: Build E2E test-host container
run: docker compose -f docker-compose.e2e.yaml build test-host-e2e
- name: Start E2E stack (Keycloak + OpenLDAP + test-host)
run: |
docker compose -f docker-compose.e2e.yaml up -d
echo "Waiting for Keycloak to be healthy..."
for i in $(seq 1 36); do
if curl -sf http://localhost:9000/health/ready >/dev/null 2>&1; then
echo "Keycloak ready after ~$((i * 10))s"
break
fi
if [ $i -eq 36 ]; then
echo "Keycloak did not become healthy within 360s"
docker compose -f docker-compose.e2e.yaml logs keycloak
exit 1
fi
sleep 10
done
- name: Wait for test-host SSH
run: |
for i in $(seq 1 12); do
if docker compose -f docker-compose.e2e.yaml exec -T test-host-e2e pgrep sshd >/dev/null 2>&1; then
echo "SSH server ready"
break
fi
echo "Waiting for test-host SSH... ($i/12)"
sleep 5
done
- name: Verify TEST_MODE sentinel
run: |
if docker compose -f docker-compose.e2e.yaml exec -T test-host-e2e env | grep -q PRMANA_TEST_MODE; then
echo "TEST_MODE is set in E2E container — aborting"
exit 1
fi
echo "TEST_MODE sentinel: PASSED (not set)"
- name: Run E2E real signature tests
run: bash test/tests/test_keycloak_real_sig.sh
env:
COMPOSE_FILE: docker-compose.e2e.yaml
- name: Assert DPoP cnf.jkt binding (KCDPOP-01)
run: bash test/tests/test_dpop_binding.sh
env:
KEYCLOAK_URL: http://localhost:8080
REALM: prmana
CLIENT_ID: prmana
CLIENT_SECRET: prmana-test-secret
TEST_USERNAME: testuser
TEST_PASSWORD: testpass
- name: Collect logs on failure
if: failure()
run: |
echo "=== Docker Compose Status ==="
docker compose -f docker-compose.e2e.yaml ps
echo ""
echo "=== Keycloak Logs (last 50 lines) ==="
docker compose -f docker-compose.e2e.yaml logs --tail=50 keycloak
echo ""
echo "=== Test-Host Logs (last 50 lines) ==="
docker compose -f docker-compose.e2e.yaml logs --tail=50 test-host-e2e
echo ""
echo "=== Test-Host Auth Log ==="
docker compose -f docker-compose.e2e.yaml exec -T test-host-e2e \
bash -c "cat /var/log/auth.log 2>/dev/null || echo 'No auth.log'" || true
echo ""
echo "=== Test-Host Audit Log ==="
docker compose -f docker-compose.e2e.yaml exec -T test-host-e2e \
bash -c "cat /var/log/prmana-audit.log 2>/dev/null || echo 'No audit log'" || true
- name: Stop E2E stack
if: always()
run: docker compose -f docker-compose.e2e.yaml down -v
# ============================================================================
# systemd E2E: Socket Activation, JSON Log, Graceful Shutdown (E2ET-05)
# ============================================================================
systemd-e2e:
name: systemd E2E (E2ET-05)
runs-on: ubuntu-latest
needs: [check]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libpam0g-dev libssl-dev pkg-config libdbus-1-dev
- name: Cache cargo
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-systemd-e2e-${{ hashFiles('**/Cargo.lock') }}
- name: Build release (prmana-agent)
run: cargo build --release -p prmana-agent
- name: Build systemd test image
run: |
docker build -f test/docker/Dockerfile.test-host-systemd \
-t test-host-systemd . || \
echo "WARN: systemd image build failed (jrei/systemd-ubuntu pull issue — test will SKIP)"
- name: Start systemd test container
run: |
docker run -d --name test-host-systemd \
--privileged \
--cgroupns=host \
-v /sys/fs/cgroup:/sys/fs/cgroup:rw \
test-host-systemd || \
echo "WARN: Could not start systemd container"
sleep 3
- name: Run systemd E2E test
run: |
chmod +x test/tests/test_systemd_launchd_e2e.sh
COMPOSE_FILE="" SYSTEMD_SERVICE="" \
bash test/tests/test_systemd_launchd_e2e.sh
env:
COMPOSE_FILE: docker-compose.systemd-e2e.yaml
SYSTEMD_SERVICE: test-host-systemd
- name: Collect logs on failure
if: failure()
run: |
docker logs test-host-systemd 2>/dev/null | tail -50 || true
- name: Stop systemd test container
if: always()
run: docker rm -f test-host-systemd 2>/dev/null || true
# ============================================================================
# TPM 2.0 Integration Tests (swtpm simulator)
# ============================================================================
tpm-integration:
name: TPM 2.0 Integration (swtpm)
runs-on: ubuntu-latest
needs: [check]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Install system dependencies (incl. swtpm)
# swtpm and swtpm-tools ship in the ubuntu-latest runner's apt repo,
# so the previous docker-compose approach was trading registry
# availability for no benefit. ea3c38d mis-diagnosed the
# "manifest unknown" error as a ghcr auth issue — in fact the image
# reference ghcr.io/tpm2-software/swtpm:v0.9.0 does not exist
# (tpm2-software hosts tpm2-tss/tools, not swtpm, which is Stefan
# Berger's separate project). Install natively instead: no
# registry dependency, no container, fewer moving parts. The test
# already probes swtpm via `nc -w1 localhost 2321` so there's no
# container network we'd lose.
run: |
sudo apt-get update
sudo apt-get install -y \
libpam0g-dev libssl-dev pkg-config libdbus-1-dev libtss2-dev \
libtss2-tcti-swtpm0t64 swtpm swtpm-tools
- name: Cache cargo
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-tpm-${{ hashFiles('**/Cargo.lock') }}
- name: Start swtpm
# Same flags as docker-compose.tpm-test.yaml, run directly on the
# runner as a daemonized process. State goes into a fresh tmpdir
# so reruns don't inherit NV state from the previous job.
run: |
swtpm --version
mkdir -p /tmp/swtpm-state
swtpm socket \
--tpmstate dir=/tmp/swtpm-state \
--tpm2 \
--ctrl type=tcp,port=2322 \
--server type=tcp,port=2321 \
--flags not-need-init,startup-clear \
--log file=/tmp/swtpm.log,level=20 \
--daemon
for i in $(seq 1 12); do
if echo | nc -w1 localhost 2321 2>/dev/null; then
echo "swtpm ready after ~$((i * 2))s"
break
fi
echo "Waiting for swtpm... ($i/12)"
sleep 2
done
# Fail loud if the loop fell through without a ready signal.
echo | nc -w1 localhost 2321 2>/dev/null || {
echo "ERROR: swtpm did not become ready on :2321 within 24s"
cat /tmp/swtpm.log 2>/dev/null || true
exit 1
}
- name: Run TPM integration tests
run: |
cargo test -p prmana-agent --features tpm,test-mode -- --ignored tpm --test-threads=1
env:
PRMANA_TPM_TCTI: swtpm
- name: Collect swtpm log on failure
if: failure()
run: |
echo "=== swtpm.log ==="
cat /tmp/swtpm.log 2>/dev/null || echo "(no swtpm.log found)"
echo "=== swtpm process state ==="
pgrep -af 'swtpm socket' || echo "(no swtpm process running)"
- name: Stop swtpm
if: always()
run: |
pkill -f 'swtpm socket' 2>/dev/null || true
rm -rf /tmp/swtpm-state /tmp/swtpm.log
# ============================================================================
# Minimum Supported Rust Version
# ============================================================================
msrv:
name: MSRV (Rust 1.88)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Rust 1.88 (MSRV)
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
toolchain: "1.88"
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpam0g-dev libssl-dev pkg-config libdbus-1-dev libtss2-dev
- name: Check MSRV
run: cargo check --all-features
# ============================================================================
# Documentation
# ============================================================================
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpam0g-dev libssl-dev pkg-config libdbus-1-dev libtss2-dev
- name: Build docs
run: cargo doc --no-deps --all-features
env:
RUSTDOCFLAGS: -D warnings
# ============================================================================
# Code Coverage
# ============================================================================
coverage:
name: Code Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: llvm-tools-preview
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpam0g-dev libssl-dev pkg-config libdbus-1-dev libtss2-dev
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@711e1c3275189d76dcc4d34ddea63bf96ac49090 # v2.76.0
with:
tool: cargo-llvm-cov
- name: Cache cargo
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('**/Cargo.lock') }}
- name: Generate coverage report
run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
- name: Check coverage threshold
run: |
# Extract coverage percentage and enforce minimum (current baseline: 55%)
# Target: 80% - track progress via codecov.yml
cargo llvm-cov --all-features --workspace --fail-under-lines 55 || {
echo "::error::Code coverage dropped below baseline (55%). Please add tests."
exit 1
}
- name: Upload coverage to Codecov
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
files: lcov.info
fail_ci_if_error: false
verbose: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# ============================================================================
# SBOM Generation
# ============================================================================
sbom:
name: SBOM Generation
runs-on: ubuntu-latest
needs: [build-matrix]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libpam0g-dev libssl-dev pkg-config libdbus-1-dev
- name: Install cargo-sbom
run: cargo install cargo-sbom --locked
- name: Generate SBOM (CycloneDX)
run: cargo sbom --output-format cyclone_dx_json_1_5 > sbom-cyclonedx.json
- name: Generate SBOM (SPDX)
run: cargo sbom --output-format spdx_json_2_3 > sbom-spdx.json
- name: Upload SBOM artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sbom
path: |
sbom-cyclonedx.json
sbom-spdx.json
# ============================================================================
# DPoP Cross-Language Interoperability Tests
# ============================================================================
dpop-interop:
name: DPoP Cross-Language Interop
runs-on: ubuntu-latest
needs: [check]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Cache Rust
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: |
~/.cargo/registry
~/.cargo/git
dpop-cross-language-tests/rust-test/target
key: ${{ runner.os }}-dpop-rust-${{ hashFiles('dpop-cross-language-tests/rust-test/Cargo.lock') }}
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
# Must match go.mod's `go 1.23` directive. setup-go v6 tightens
# toolchain handling; mismatched go-version + go.mod fails the build.
go-version: '1.23'
cache-dependency-path: dpop-cross-language-tests/go-test/go.sum
- name: Set up Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
java-version: '21'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('java-oauth-dpop/build.gradle.kts') }}
- name: Install Python cryptography
run: pip install 'cryptography==46.0.7'
- name: Build Java dependencies
run: cd java-oauth-dpop && gradle compileJava --no-daemon -q
- name: Run cross-language interop tests
run: |
chmod +x ./dpop-cross-language-tests/run-cross-language-tests.sh
./dpop-cross-language-tests/run-cross-language-tests.sh