Skip to content

Commit fdb55ff

Browse files
vinayada1Vinaya Damle
andauthored
feat: support multiple control catalog versions (#269)
Signed-off-by: Vinaya Damle <vinayada@mac.lan> Signed-off-by: Vinaya Damle <vinayada1@users.noreply.github.com> Co-authored-by: Vinaya Damle <vinayada@mac.lan>
1 parent df3fcff commit fdb55ff

9 files changed

Lines changed: 3332 additions & 9 deletions

File tree

.github/scripts/ci.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ services:
9494
plugin: github-repo
9595
policy:
9696
catalogs:
97-
- osps-baseline
97+
- osps-baseline-2026-02
9898
applicability:
9999
- Maturity Level 1
100100
vars:

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ jobs:
3333
./.github/scripts/ci.sh 2>&1 | tee integration_output.txt
3434
- name: Verify test output
3535
run: |
36-
grep -E 'privateer_osps-baseline.*Passed.*Warnings.*Failed.*Possible' integration_output.txt
36+
grep -E 'privateer_osps-baseline-2026-02.*Passed.*Warnings.*Failed.*Possible' integration_output.txt

data/catalogs/OSPS_Baseline_2025_10.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
metadata:
2-
id: osps-baseline
2+
id: osps-baseline-2025-10
33
title: Open Source Project Security Baseline
4-
version: ""
4+
version: "2025.10"
55
description: |
66
The Open Source Project Security (OSPS) Baseline is a set of security criteria
77
that projects should meet to demonstrate a strong security posture.

data/catalogs/OSPS_Baseline_2026_02.yaml

Lines changed: 3222 additions & 0 deletions
Large diffs are not rendered by default.

evaluation_plans/evaluation-plans.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import (
1515
)
1616

1717
var (
18-
// Open Source Project Security Baseline
18+
// OSPS contains assessment step implementations for OSPS Baseline controls.
19+
// Each catalog YAML defines which IDs are active for that version,
20+
// so the SDK only runs the relevant subset.
1921
OSPS = map[string][]gemara.AssessmentStep{
2022
"OSPS-AC-01.01": {
2123
reusable_steps.GithubBuiltIn,
@@ -243,3 +245,23 @@ var (
243245
},
244246
}
245247
)
248+
249+
// AllSteps merges all step maps into a single map for registration with the SDK.
250+
// Assessment IDs are unique across catalogs (e.g., OSPS-* vs CRA-*), so the
251+
// catalog YAML naturally filters to the correct subset at evaluation time.
252+
// To add a new catalog family, define its step map and include it here.
253+
//
254+
// A single shared map is safe across catalog versions because the OSPS
255+
// maintenance policy (https://github.com/ossf/security-baseline/blob/main/docs/maintenance.md#identifiers)
256+
// guarantees that substantive changes to a control result in a new identifier.
257+
// This means implementations for a given assessment ID will not diverge between
258+
// versions, so all versions can share the same step function for the same key.
259+
func AllSteps() map[string][]gemara.AssessmentStep {
260+
merged := make(map[string][]gemara.AssessmentStep, len(OSPS))
261+
for id, steps := range OSPS {
262+
merged[id] = steps
263+
}
264+
// Add additional catalog step maps here, e.g.:
265+
// for id, steps := range CRA { merged[id] = steps }
266+
return merged
267+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package evaluation_plans
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/gemaraproj/go-gemara"
9+
"github.com/goccy/go-yaml"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestAllSteps(t *testing.T) {
14+
t.Run("Returns non-empty map", func(t *testing.T) {
15+
result := AllSteps()
16+
assert.NotEmpty(t, result)
17+
})
18+
19+
t.Run("Contains all OSPS entries", func(t *testing.T) {
20+
result := AllSteps()
21+
for id := range OSPS {
22+
assert.Contains(t, result, id, "AllSteps() missing OSPS key %s", id)
23+
}
24+
assert.Equal(t, len(OSPS), len(result))
25+
})
26+
27+
t.Run("Every entry has non-empty steps", func(t *testing.T) {
28+
result := AllSteps()
29+
for id, steps := range result {
30+
assert.NotEmpty(t, steps, "AllSteps() entry %s has no steps", id)
31+
}
32+
})
33+
34+
t.Run("Returns a copy not the original", func(t *testing.T) {
35+
result := AllSteps()
36+
result["fake-id"] = nil
37+
_, exists := OSPS["fake-id"]
38+
assert.False(t, exists, "mutating AllSteps() result should not affect OSPS")
39+
})
40+
}
41+
42+
// TestAllCatalogAssessmentIDsHaveSteps ensures every assessment requirement ID
43+
// defined in every catalog YAML has a corresponding entry in the combined step map.
44+
// This prevents silently producing "Unknown" results when a new catalog
45+
// introduces assessment IDs without adding step implementations.
46+
func TestAllCatalogAssessmentIDsHaveSteps(t *testing.T) {
47+
allSteps := AllSteps()
48+
catalogDir := filepath.Join("..", "data", "catalogs")
49+
entries, err := os.ReadDir(catalogDir)
50+
if err != nil {
51+
t.Fatalf("failed to read catalog directory: %v", err)
52+
}
53+
54+
for _, entry := range entries {
55+
if entry.IsDir() {
56+
continue
57+
}
58+
catalogPath := filepath.Join(catalogDir, entry.Name())
59+
t.Run(entry.Name(), func(t *testing.T) {
60+
data, err := os.ReadFile(catalogPath)
61+
if err != nil {
62+
t.Fatalf("failed to read catalog %s: %v", entry.Name(), err)
63+
}
64+
var catalog gemara.ControlCatalog
65+
if err := yaml.Unmarshal(data, &catalog); err != nil {
66+
t.Fatalf("failed to parse catalog %s: %v", entry.Name(), err)
67+
}
68+
69+
for _, control := range catalog.Controls {
70+
for _, req := range control.AssessmentRequirements {
71+
if _, ok := allSteps[req.Id]; !ok {
72+
t.Errorf("catalog %s has assessment requirement %s but no step implementation exists", entry.Name(), req.Id)
73+
}
74+
}
75+
}
76+
})
77+
}
78+
}

example-config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ services:
1313
# The policy section may be set at the top level if assessing multiple services via privateer
1414
policy:
1515
catalogs:
16-
- osps-baseline # currently this is the only available catalog to assess against
16+
- osps-baseline-2026-02 # use osps-baseline-2025-10 for the previous version
1717
applicability:
1818
- Maturity Level 1
1919
# - Maturity Level 2

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.25.1
44

55
require (
66
github.com/gemaraproj/go-gemara v0.0.2
7+
github.com/goccy/go-yaml v1.19.2
78
github.com/google/go-github/v74 v74.0.0
89
github.com/migueleliasweb/go-github-mock v1.5.0
910
github.com/ossf/si-tooling/v2 v2.2.0
@@ -26,7 +27,6 @@ require (
2627
github.com/go-git/go-billy/v5 v5.8.0 // indirect
2728
github.com/go-git/go-git/v5 v5.17.0 // indirect
2829
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
29-
github.com/goccy/go-yaml v1.19.2 // indirect
3030
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
3131
github.com/google/go-github/v71 v71.0.0 // indirect
3232
github.com/google/go-github/v73 v73.0.0 // indirect

main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ func main() {
5454
}
5555

5656
orchestrator.AddRequiredVars(RequiredVars)
57-
err = orchestrator.AddEvaluationSuite("osps-baseline", nil, evaluation_plans.OSPS)
57+
58+
err = orchestrator.AddEvaluationSuiteForAllCatalogs(nil, evaluation_plans.AllSteps())
5859
if err != nil {
59-
fmt.Printf("Error adding evaluation suite: %v\n", err)
60+
fmt.Printf("Error adding evaluation suites: %v\n", err)
6061
os.Exit(1)
6162
}
6263

0 commit comments

Comments
 (0)