Skip to content

Commit e3960a2

Browse files
committed
doc(adr): add ADR 00011 for CSAF remediation support
Propose ingesting CSAF remediation guidance and exposing it via APIs. Remediations provide actionable guidance (upgrade paths, workarounds) alongside vulnerability status information. Assisted-by: Claude Signed-off-by: Dejan Bosanac <dbosanac@redhat.com>
1 parent 3d9314c commit e3960a2

File tree

1 file changed

+197
-0
lines changed

1 file changed

+197
-0
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# 00011. CSAF Remediation Support
2+
3+
Date: 2025-01-09
4+
5+
## Status
6+
7+
PROPOSED
8+
9+
## Context
10+
11+
Trustify ingests CSAF advisories and exposes which packages are affected by vulnerabilities through the `purl_status` and `product_status` tables. However, CSAF documents also contain **remediation** guidance that tells users HOW to fix vulnerabilities (e.g., "upgrade to version X", "apply workaround Y").
12+
13+
### Current Limitation
14+
15+
When a user queries for vulnerability status, they receive information about WHAT is affected:
16+
- Package X version 1.2.3 is **affected** by CVE-2024-1234
17+
- Package X version 2.0.0 is **fixed**
18+
19+
But they don't receive actionable guidance on HOW to fix it:
20+
- "Upgrade to version 2.0.0 or apply workaround described at https://..."
21+
- "Restart required after upgrade"
22+
- "Workaround: disable feature Y"
23+
24+
### CSAF Remediation Structure
25+
26+
CSAF remediations are linked to specific `product_ids` within the advisory, which resolve to specific packages during ingestion. Each remediation includes:
27+
- **Category**: `vendor_fix`, `workaround`, `mitigation`, `no_fix_planned`, `none_available`, `will_not_fix`
28+
- **Details**: Human-readable description of the remediation
29+
- **URL**: Link to detailed guidance
30+
- **Metadata**: Restart requirements, dates, entitlements
31+
32+
Critically, remediation `product_ids` often overlap with `product_status` product_ids (e.g., vendor_fix applies to the same products marked as "fixed").
33+
34+
## Decision
35+
36+
Add remediation support by creating a `remediation` table with junction tables linking to specific `purl_status` and `product_status` records.
37+
38+
### Schema Design
39+
40+
**Core remediation table:**
41+
```sql
42+
CREATE TABLE remediation (
43+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
44+
advisory_id UUID NOT NULL,
45+
vulnerability_id VARCHAR NOT NULL,
46+
category VARCHAR NOT NULL, -- vendor_fix, workaround, mitigation,
47+
-- no_fix_planned, none_available, will_not_fix
48+
details TEXT,
49+
url VARCHAR,
50+
data JSONB, -- {restart_required, date, entitlements, ...}
51+
52+
FOREIGN KEY (advisory_id, vulnerability_id)
53+
REFERENCES advisory_vulnerability(advisory_id, vulnerability_id)
54+
ON DELETE CASCADE
55+
);
56+
57+
CREATE INDEX idx_remediation_advisory_vuln
58+
ON remediation(advisory_id, vulnerability_id);
59+
```
60+
61+
**Junction tables linking remediations to specific status records:**
62+
```sql
63+
CREATE TABLE remediation_purl_status (
64+
remediation_id UUID REFERENCES remediation(id) ON DELETE CASCADE,
65+
purl_status_id UUID REFERENCES purl_status(id) ON DELETE CASCADE,
66+
PRIMARY KEY (remediation_id, purl_status_id)
67+
);
68+
69+
CREATE INDEX idx_remediation_purl_status_purl
70+
ON remediation_purl_status(purl_status_id);
71+
72+
CREATE TABLE remediation_product_status (
73+
remediation_id UUID REFERENCES remediation(id) ON DELETE CASCADE,
74+
product_status_id UUID REFERENCES product_status(id) ON DELETE CASCADE,
75+
PRIMARY KEY (remediation_id, product_status_id)
76+
);
77+
78+
CREATE INDEX idx_remediation_product_status_product
79+
ON remediation_product_status(product_status_id);
80+
```
81+
82+
**JSONB data field structure:**
83+
```json
84+
{
85+
"restart_required": {"category": "none|system|zone|service|parent|dependencies|..."},
86+
"date": "2024-03-15T10:30:00Z",
87+
"entitlements": ["premium-support"],
88+
}
89+
```
90+
91+
### Design Choices
92+
93+
- Remediations link to **specific purl_status/product_status records**
94+
- Keep statuses (category) as VARCHAR. Having separate statuses table have added more complexity to queries than benefits.
95+
- Normalize only important remediation fields, while keeping the rest in JSON blob
96+
97+
### Ingestion Approach
98+
99+
During CSAF ingestion:
100+
1. `StatusCreator` resolves product_ids - creates purl_status/product_status records
101+
2. Track which records were created for each product_id
102+
3. `RemediationCreator` uses this mapping to create junction table entries
103+
104+
This follows the existing `StatusCreator` pattern and reuses the `ResolveProductIdCache`.
105+
106+
### API Exposure
107+
108+
**New API Model:**
109+
```rust
110+
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
111+
pub struct RemediationSummary {
112+
pub id: Uuid,
113+
pub category: String, // vendor_fix, workaround, mitigation, etc.
114+
pub details: Option<String>,
115+
pub url: Option<String>,
116+
pub data: Option<serde_json::Value>,
117+
}
118+
```
119+
120+
**Modified Response Structures:**
121+
122+
Add `remediations: Vec<RemediationSummary>` to:
123+
- `PurlStatus` struct
124+
- `SbomStatus` struct
125+
126+
**Affected Endpoints:**
127+
- `GET /v2/purl/{key}` - returns `PurlDetails` with `PurlStatus`
128+
- `POST /v3/vulnerability/analyze` - returns `AnalysisResponse` with `PurlStatus`
129+
- `GET /v2/sbom/{id}/advisory` - returns `SbomAdvisory` with `SbomStatus`
130+
- `GET /v2/sbom/{id}` - returns `SbomDetails` with `SbomStatus`
131+
132+
**Example Response:**
133+
```json
134+
{
135+
"vulnerability": {"id": "CVE-2024-1234", ...},
136+
"status": "affected",
137+
"remediations": [
138+
{
139+
"id": "550e8400-e29b-41d4-a716-446655440000",
140+
"category": "vendor_fix",
141+
"details": "Upgrade to version 2.0.0 or later",
142+
"url": "https://example.com/security/CVE-2024-1234",
143+
"data": {"restart_required": {"category": "none"}}
144+
},
145+
{
146+
"id": "660e8400-e29b-41d4-a716-446655440001",
147+
"category": "workaround",
148+
"details": "Disable the vulnerable feature X",
149+
"url": null,
150+
"data": null
151+
}
152+
]
153+
}
154+
```
155+
156+
**Query Strategy:**
157+
158+
Queries will use LEFT JOIN on junction tables:
159+
```sql
160+
SELECT ps.*, r.id, r.category, r.details, r.url, r.data
161+
FROM purl_status ps
162+
LEFT JOIN remediation_purl_status rps ON rps.purl_status_id = ps.id
163+
LEFT JOIN remediation r ON r.id = rps.remediation_id
164+
WHERE ps.id = ?;
165+
```
166+
167+
Most packages have no remediations, so LEFT JOIN returns NULL efficiently without performance impact.
168+
169+
## Consequences
170+
171+
### Positive
172+
173+
- Users receive actionable remediation guidance alongside vulnerability status
174+
- Follows Trustify's existing junction table pattern (e.g., `sbom_package_purl_ref`)
175+
- Maintains proper data integrity with FK constraints and CASCADE delete
176+
- Enables querying remediations independently ("all vendor_fix remediations")
177+
- Supports multiple remediations per package (vendor_fix + workaround)
178+
179+
### Trade-offs
180+
181+
- Adds 2 JOINs to status queries (remediation_purl_status + remediation tables)
182+
- Ingestion complexity: must track created status record IDs
183+
184+
### Future Optimizations
185+
186+
- Can denormalize if query performance becomes an issue
187+
- Can combine with ADR 00008 (PURL recommendations) to suggest both recommendations and remediations
188+
189+
## Related ADRs
190+
191+
- [ADR 00008](00008-purls-recommendation.md): PURL recommendations endpoint (future: combine recommendations with remediations)
192+
- [ADR 00010](00010-version-range-in-responses.md): Version range in responses
193+
194+
## References
195+
196+
- CSAF v2.0 Spec: https://docs.oasis-open.org/csaf/csaf/v2.0/os/csaf-v2.0-os.html#3212-vulnerabilities-property---remediations
197+
- Test data: [rhsa-2024-2705.json](../../etc/test-data/csaf/rhsa-2024-2705.json)

0 commit comments

Comments
 (0)