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
name: CI
permissions:
contents: read
on:
push:
branches:
pull_request:
branches:
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-Dwarnings"
jobs:
quality-checks:
name: Quality Checks (${{ matrix.backend.name }})
runs-on: ubuntu-latest
services:
# Shared store for the rate-limit cross-instance reconciliation test.
# Pulled through Google's Docker Hub mirror: anonymous pulls from
# Docker Hub on shared runner addresses hit its rate limit.
redis:
image: mirror.gcr.io/library/redis:7
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
strategy:
fail-fast: false
matrix:
backend:
# One leg per crypto backend, plus the build that links both: Cargo
# features are additive, so feature unification (and every tool that
# reaches for `--all-features`, docs.rs and cargo-semver-checks
# included) can produce that combination.
- name: rust_crypto
# Pure-Rust default backend (RustCrypto / rsa), with the binary
# (`cli` carries `redis`).
flags: "--features cli"
- name: aws_lc_rs
# Opt-in constant-time backend (aws-lc, C FFI); disables the default.
flags: "--no-default-features --features aws_lc_rs,redis"
- name: injected_verifier
# No built-in verifier at all: the build a consumer takes when it
# injects its own `hooks::TokenVerifier`. Links no JWT or TLS
# crypto (and so no `rsa`), which is only true as long as nothing
# outside the backend features reaches for jsonwebtoken or a
# rustls provider — this leg is what keeps that honest. The binary
# builds without a verifier too.
flags: "--no-default-features --features cli"
- name: all_features
# Both backends at once, the build cargo-semver-checks and docs.rs
# take. `aws_lc_rs` wins the tie; this leg keeps that wiring honest.
flags: "--all-features"
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
# Third-party action: pinned to a commit SHA (dependabot keeps it fresh).
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
# Runs each test in its own process, so a hang names the test that hung
# instead of timing the whole job out. Third-party action: pinned to a
# commit SHA (dependabot keeps it fresh).
- uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2
with:
tool: cargo-nextest
- name: Format
if: matrix.backend.name == 'rust_crypto'
run: cargo fmt --all -- --check
# The default and injected-verifier builds are pure Rust: no C crypto
# (ring, aws-lc) on any target. The aws_lc_rs and all-features legs link
# aws-lc by choice.
- name: No C crypto
if: matrix.backend.name == 'rust_crypto' || matrix.backend.name == 'injected_verifier'
run: |
tree=$(cargo tree -e normal --target all --prefix none --format '{p}' ${{ matrix.backend.flags }})
if printf '%s\n' "$tree" | grep -E '^(ring|aws-lc-rs|aws-lc-sys) v'; then
echo "::error::C crypto is linked into the ${{ matrix.backend.name }} build"
exit 1
fi
# A build without a crypto backend (`default-features = false`, what a
# transcoding-only consumer takes) must pass an advisory scan without
# the ignores in deny.toml: none of the crates they cover is linked.
- name: No ignored advisories without a crypto backend
if: matrix.backend.name == 'injected_verifier'
run: |
tree=$(cargo tree -e normal --target all --prefix none --format '{p}' ${{ matrix.backend.flags }})
if printf '%s\n' "$tree" | grep -E '^(rsa|rustls-rustcrypto|paste) v|^rustls-webpki v0\.102\.'; then
echo "::error::the build without a crypto backend links a crate with an ignored advisory"
exit 1
fi
# `cargo add structured-proxy` takes the default features: the library
# must compile none of the binary's dependencies, which sit behind `cli`.
- name: No CLI dependencies by default
if: matrix.backend.name == 'rust_crypto'
run: |
tree=$(cargo tree -e normal --target all --prefix none --format '{p}')
if printf '%s\n' "$tree" | grep -E '^(clap|tracing-subscriber|redis) v'; then
echo "::error::the default build links a CLI-only dependency"
exit 1
fi
- name: Clippy
run: cargo clippy --all-targets ${{ matrix.backend.flags }}
- name: Build
run: cargo build --release ${{ matrix.backend.flags }}
- name: Test
env:
# Exercises the rate-limit reconciliation against the Redis service.
SHIELD_REDIS_TEST_URL: redis://127.0.0.1:6379/
run: cargo nextest run ${{ matrix.backend.flags }}
# nextest does not run doctests; cargo does.
- name: Doc tests
run: cargo test --doc ${{ matrix.backend.flags }}
# With `cli`, so the packaged crate's binary compiles too: without the
# feature cargo verifies the library alone.
- name: Publish dry-run
if: matrix.backend.name == 'rust_crypto'
run: cargo publish --dry-run --features cli
# The release packages ship static musl binaries; build and test that target
# on every change, so a dependency that breaks it fails here, not at release.
musl:
name: musl (${{ matrix.target }})
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-musl
runs-on: ubuntu-latest
- target: aarch64-unknown-linux-musl
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install musl tools
run: |
sudo apt-get update
sudo apt-get install -y musl-tools
# Third-party action: pinned to a commit SHA (dependabot keeps it fresh).
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
key: ${{ matrix.target }}
# Third-party action: pinned to a commit SHA (dependabot keeps it fresh).
- uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2
with:
tool: cargo-nextest
# The build the release workflow packages.
- name: Build
run: cargo build --release --target ${{ matrix.target }} --features cli --bin structured-proxy
- name: Test
run: cargo nextest run --target ${{ matrix.target }} --features cli
# The `rust-version` in Cargo.toml: every target builds on exactly that
# toolchain, with and without a built-in verifier (their code differs).
msrv:
name: MSRV (1.99)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@1.99
# Third-party action: pinned to a commit SHA (dependabot keeps it fresh).
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Check (all features)
run: cargo check --all-targets --all-features
- name: Check (injected verifier)
run: cargo check --all-targets --no-default-features --features cli
security-audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
# Scans Cargo.lock for RustSec advisories; honors the ignore list in
# deny.toml. The released binary rather than the Docker action, whose
# image build pulls from Docker Hub and hits its anonymous rate limit
# on shared runners.
- uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2
with:
tool: cargo-deny
- name: Check advisories
run: cargo deny check advisories