Skip to content

Commit 02c0b2b

Browse files
committed
chore: cleanup OSCAL export logic for consistency
This updates the Layer 1 and Layer 2 implementations of OSCAL generation for a more cohesive approach. Assisted by: Composer(Cursor AI) Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
1 parent a8da258 commit 02c0b2b

File tree

8 files changed

+738
-634
lines changed

8 files changed

+738
-634
lines changed

cmd/oscal_export/export/export.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func Catalog(path string, args []string) error {
6464
return err
6565
}
6666

67-
oscalCatalog, err := oscal.FromCatalog(catalog, "https://example/versions/%s#%s")
67+
oscalCatalog, err := oscal.CatalogFromCatalog(catalog, oscal.WithControlHref("https://example/versions/%s#%s"))
6868
if err != nil {
6969
return err
7070
}

oscal/catalog.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package oscal
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/defenseunicorns/go-oscal/src/pkg/uuid"
8+
oscal "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-3"
9+
"github.com/ossf/gemara"
10+
)
11+
12+
// CatalogFromCatalog converts a Layer 2 Catalog to OSCAL Catalog format.
13+
// The function automatically:
14+
// - Uses the catalog's internal version from Metadata.Version (or defaultVersion if empty)
15+
// - Uses the ControlFamily.Id as the OSCAL group ID
16+
// - Generates a unique UUID for the catalog
17+
//
18+
// Options:
19+
// - WithControlHref: URL template for linking to controls. Uses format: controlHREF(version, controlID)
20+
// Example: "https://baseline.openssf.org/versions/%s#%s"
21+
// - WithVersion: Override the version (defaults to catalog.Metadata.Version or defaultVersion)
22+
// - WithCanonicalHrefFormat: Alternative canonical HREF format (if WithControlHref not provided)
23+
func CatalogFromCatalog(catalog *gemara.Catalog, opts ...GenerateOption) (oscal.Catalog, error) {
24+
options := generateOpts{}
25+
for _, opt := range opts {
26+
opt(&options)
27+
}
28+
options.completeFromCatalog(catalog)
29+
30+
metadata, err := createMetadataFromCatalog(catalog, options)
31+
if err != nil {
32+
return oscal.Catalog{}, fmt.Errorf("error creating catalog metadata: %w", err)
33+
}
34+
35+
// Determine control HREF format for control links
36+
controlHREF := options.controlHREF
37+
if controlHREF == "" {
38+
controlHREF = options.canonicalHref
39+
}
40+
41+
oscalCatalog := oscal.Catalog{
42+
UUID: uuid.NewUUID(),
43+
Groups: nil,
44+
Metadata: metadata,
45+
}
46+
47+
catalogGroups := []oscal.Group{}
48+
49+
for _, family := range catalog.ControlFamilies {
50+
group := oscal.Group{
51+
Class: "family",
52+
Controls: nil,
53+
ID: family.Id,
54+
Title: strings.ReplaceAll(family.Description, "\n", "\\n"),
55+
}
56+
57+
controls := []oscal.Control{}
58+
for _, control := range family.Controls {
59+
controlTitle := strings.TrimSpace(control.Title)
60+
61+
newCtl := oscal.Control{
62+
Class: family.Id,
63+
ID: control.Id,
64+
Title: strings.ReplaceAll(controlTitle, "\n", "\\n"),
65+
Parts: &[]oscal.Part{
66+
{
67+
Name: "statement",
68+
ID: fmt.Sprintf("%s_smt", control.Id),
69+
Prose: control.Objective,
70+
},
71+
},
72+
Links: func() *[]oscal.Link {
73+
if controlHREF != "" {
74+
return &[]oscal.Link{
75+
{
76+
Href: fmt.Sprintf(controlHREF, options.version, strings.ToLower(control.Id)),
77+
Rel: "canonical",
78+
},
79+
}
80+
}
81+
return nil
82+
}(),
83+
}
84+
85+
var subControls []oscal.Control
86+
for _, ar := range control.AssessmentRequirements {
87+
subControl := oscal.Control{
88+
ID: ar.Id,
89+
Title: ar.Id,
90+
Parts: &[]oscal.Part{
91+
{
92+
Name: "statement",
93+
ID: fmt.Sprintf("%s_smt", ar.Id),
94+
Prose: ar.Text,
95+
},
96+
},
97+
}
98+
99+
if ar.Recommendation != "" {
100+
*subControl.Parts = append(*subControl.Parts, oscal.Part{
101+
Name: "guidance",
102+
ID: fmt.Sprintf("%s_gdn", ar.Id),
103+
Prose: ar.Recommendation,
104+
})
105+
}
106+
107+
*subControl.Parts = append(*subControl.Parts, oscal.Part{
108+
Name: "assessment-objective",
109+
ID: fmt.Sprintf("%s_obj", ar.Id),
110+
Links: &[]oscal.Link{
111+
{
112+
Href: fmt.Sprintf("#%s_smt", ar.Id),
113+
Rel: "assessment-for",
114+
},
115+
},
116+
})
117+
118+
subControls = append(subControls, subControl)
119+
}
120+
121+
if len(subControls) > 0 {
122+
newCtl.Controls = &subControls
123+
}
124+
controls = append(controls, newCtl)
125+
}
126+
127+
group.Controls = &controls
128+
catalogGroups = append(catalogGroups, group)
129+
}
130+
oscalCatalog.Groups = &catalogGroups
131+
132+
return oscalCatalog, nil
133+
}

oscal/catalog_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package oscal
2+
3+
import (
4+
"testing"
5+
6+
oscalTypes "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-3"
7+
"github.com/ossf/gemara"
8+
"github.com/stretchr/testify/assert"
9+
10+
oscalUtils "github.com/ossf/gemara/internal/oscal"
11+
)
12+
13+
var testCases = []struct {
14+
name string
15+
catalog *gemara.Catalog
16+
controlHREF string
17+
wantErr bool
18+
expectedTitle string
19+
}{
20+
{
21+
name: "Valid catalog with single control family",
22+
catalog: &gemara.Catalog{
23+
Metadata: gemara.Metadata{
24+
Id: "test-catalog",
25+
Version: "devel",
26+
},
27+
Title: "Test Catalog",
28+
ControlFamilies: []gemara.ControlFamily{
29+
{
30+
Id: "AC",
31+
Title: "access-control",
32+
Description: "Controls for access management",
33+
Controls: []gemara.Control{
34+
{
35+
Id: "AC-01",
36+
Title: "Access Control Policy",
37+
AssessmentRequirements: []gemara.AssessmentRequirement{
38+
{
39+
Id: "AC-01.1",
40+
Text: "Develop and document access control policy",
41+
},
42+
},
43+
},
44+
},
45+
},
46+
},
47+
},
48+
controlHREF: "https://baseline.openssf.org/versions/%s#%s",
49+
wantErr: false,
50+
expectedTitle: "Test Catalog",
51+
},
52+
{
53+
name: "Valid catalog with multiple control families",
54+
catalog: &gemara.Catalog{
55+
Metadata: gemara.Metadata{
56+
Id: "test-catalog-multi",
57+
Version: "devel",
58+
},
59+
Title: "Test Catalog Multiple",
60+
ControlFamilies: []gemara.ControlFamily{
61+
{
62+
Id: "AC",
63+
Title: "access-control",
64+
Description: "Controls for access management",
65+
Controls: []gemara.Control{
66+
{
67+
Id: "AC-01",
68+
Title: "Access Control Policy",
69+
AssessmentRequirements: []gemara.AssessmentRequirement{
70+
{
71+
Id: "AC-01.1",
72+
Text: "Develop and document access control policy",
73+
},
74+
},
75+
},
76+
},
77+
},
78+
{
79+
Id: "BR",
80+
Title: "business-requirements",
81+
Description: "Controls for business requirements",
82+
Controls: []gemara.Control{
83+
{
84+
Id: "BR-01",
85+
Title: "Business Requirements Policy",
86+
AssessmentRequirements: []gemara.AssessmentRequirement{
87+
{
88+
Id: "BR-01.1",
89+
Text: "Define business requirements",
90+
},
91+
},
92+
},
93+
},
94+
},
95+
},
96+
},
97+
controlHREF: "https://baseline.openssf.org/versions/%s#%s",
98+
wantErr: false,
99+
expectedTitle: "Test Catalog Multiple",
100+
},
101+
}
102+
103+
func TestFromCatalog(t *testing.T) {
104+
for _, tt := range testCases {
105+
t.Run(tt.name, func(t *testing.T) {
106+
oscalCatalog, err := CatalogFromCatalog(tt.catalog, WithControlHref(tt.controlHREF))
107+
108+
if (err == nil) == tt.wantErr {
109+
t.Errorf("ToOSCAL() error = %v, wantErr %v", err, tt.wantErr)
110+
return
111+
}
112+
113+
if tt.wantErr {
114+
return
115+
}
116+
117+
// Wrap oscal catalog
118+
// Create the proper OSCAL document structure
119+
oscalDocument := oscalTypes.OscalModels{
120+
Catalog: &oscalCatalog,
121+
}
122+
123+
// Create validation for the OSCAL catalog
124+
assert.NoError(t, oscalUtils.Validate(oscalDocument))
125+
126+
// Compare each field
127+
assert.NotEmpty(t, oscalCatalog.UUID)
128+
assert.Equal(t, tt.expectedTitle, oscalCatalog.Metadata.Title)
129+
assert.Equal(t, tt.catalog.Metadata.Version, oscalCatalog.Metadata.Version)
130+
assert.Equal(t, len(tt.catalog.ControlFamilies), len(*oscalCatalog.Groups))
131+
132+
// Compare each control family
133+
for i, family := range tt.catalog.ControlFamilies {
134+
groups := (*oscalCatalog.Groups)
135+
group := groups[i]
136+
assert.Equal(t, family.Id, group.ID)
137+
}
138+
})
139+
}
140+
}

0 commit comments

Comments
 (0)