Skip to content

Commit

Permalink
Boostrap oscal generator and subcommand
Browse files Browse the repository at this point in the history
This command initializes the first oscal output and
generator for the baseline data.

Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]>
  • Loading branch information
puerco committed Feb 19, 2025
1 parent 04f9f53 commit 6767dce
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/ossf/security-baseline
go 1.23

require (
github.com/defenseunicorns/go-oscal v0.6.2
github.com/spf13/cobra v1.8.1
gopkg.in/yaml.v3 v3.0.1
)
Expand Down
2 changes: 2 additions & 0 deletions cmd/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/defenseunicorns/go-oscal v0.6.2 h1:oLkMAJYVMq73Rm+9efcyaKq5SLMditjB6wv7o3XXpq8=
github.com/defenseunicorns/go-oscal v0.6.2/go.mod h1:UHp2yK9ty2mYJDun7oNhbstCq6SAAwP4YGbw9n7uG6o=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
Expand Down
94 changes: 94 additions & 0 deletions cmd/internal/cmd/oscal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-FileCopyrightText: Copyright 2025 The OSPS Authors
// SPDX-License-Identifier: Apache-2.0

package cmd

import (
"errors"
"fmt"
"os"

"github.com/ossf/security-baseline/pkg/baseline"
"github.com/spf13/cobra"
)

var appname = "baseline"

type oscalOptions struct {
outPath string
baselinePath string
}

// Validate the options in context with arguments
func (o *oscalOptions) Validate() error {
errs := []error{}

if o.baselinePath == "" {
errs = append(errs, errors.New("baseline path not specified"))
}
return errors.Join(errs...)
}

func (o *oscalOptions) AddFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVarP(
&o.baselinePath, "baseline", "b", "", "path to directory containing the baseline YAML data",
)

cmd.PersistentFlags().StringVarP(
&o.outPath, "out", "o", "", "path to output file (defaults to STDOUT)",
)
}

func addOscal(parentCmd *cobra.Command) {
opts := oscalOptions{}
packCmd := &cobra.Command{
Short: "writes the baseline definition to an oscal json catalog",
Long: fmt.Sprintf(`
%s oscal: Write the OSPS Baseline to oscal definitions.
This subcommand exports the OSPS Baseline data to OSCAL (Open Security Controls
Assessment Language). This lets automated tools understand the criteria set as
OSCAL controls.
`, appname),
Use: "oscal -o osps.oscal.json",
SilenceUsage: false,
SilenceErrors: true,
PreRunE: func(_ *cobra.Command, args []string) error {
if opts.outPath != "" && len(args) > 1 && opts.outPath != args[1] {
return fmt.Errorf("out path specified twice")
}

if len(args) > 1 {
opts.outPath = args[1]
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
if err := opts.Validate(); err != nil {
return err
}

cmd.SilenceUsage = true

loader := baseline.NewLoader()
loader.DataPath = opts.baselinePath

bline, err := loader.Load()
if err != nil {
return err
}

// TODO: Open the output file

gen := baseline.NewGenerator()
if err := gen.ExportOSCAL(bline, os.Stdout); err != nil {
return err
}

return nil
},
}
opts.AddFlags(packCmd)
parentCmd.AddCommand(packCmd)
}
1 change: 1 addition & 0 deletions cmd/internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func Execute() error {
// Add the subcommands
addCompile(rootCmd)
addValidate(rootCmd)
addOscal(rootCmd)

return rootCmd.Execute()
}
107 changes: 107 additions & 0 deletions cmd/pkg/baseline/generator_oscal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-FileCopyrightText: Copyright 2025 The OSPS Authors
// SPDX-License-Identifier: Apache-2.0

package baseline

import (
"encoding/json"
"fmt"
"io"
"strings"
"time"

oscal "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-3"
"github.com/ossf/security-baseline/pkg/types"
)

const (
VersionOSPS = "devel"
controlHREF = "https://baseline.openssf.org/versions/%s#%s"
catalogUUID = "8c222a23-fc7e-4ad8-b6dd-289014f07a9f"
)

func (g *Generator) ExportOSCAL(b *types.Baseline, w io.Writer) error {
n := time.Now()
catalog := oscal.Catalog{
UUID: catalogUUID,
Groups: nil,
Metadata: oscal.Metadata{
LastModified: n,
Links: &[]oscal.Link{
{
Href: fmt.Sprintf(controlHREF, VersionOSPS, ""),
},
},
OscalVersion: "1.1.3",
Published: &n,
Title: "Open Source Project Security Baseline",
Version: VersionOSPS,
},
}

catalogGroups := []oscal.Group{}

for code, cat := range b.Categories {
group := oscal.Group{
Class: "OSPS",
Controls: nil,
ID: code,
Title: cat.Description,
}

controls := []oscal.Control{}

for _, criteria := range cat.Criteria {
newCtl := oscal.Control{
Class: criteria.Category,
ID: strings.TrimPrefix(criteria.ID, "OSPS-"),
Links: &[]oscal.Link{
{
Href: fmt.Sprintf(controlHREF, VersionOSPS, strings.ToLower(criteria.ID)),
Rel: "reference",
},
},
Parts: &[]oscal.Part{
{
ID: strings.TrimPrefix(criteria.ID, "OSPS-") + "_level",
Name: "maturity-level",
Prose: fmt.Sprintf("%d", criteria.MaturityLevel),
},
{
ID: strings.TrimPrefix(criteria.ID, "OSPS-") + "_details",
Name: "details",
Prose: strings.TrimSpace(criteria.Details),
},
{
ID: strings.TrimPrefix(criteria.ID, "OSPS-") + "_rationale",
Name: "rationale",
Prose: strings.TrimSpace(criteria.Rationale),
},
},
Title: strings.TrimSpace(criteria.CriterionText),
}

if criteria.Implementation != "" {
pts := append(*newCtl.Parts, oscal.Part{
ID: strings.TrimPrefix(criteria.ID, "OSPS-") + "_implementation",
Name: "implementation",
Prose: strings.TrimSpace(criteria.Implementation),
})
newCtl.Parts = &pts
}

controls = append(controls, newCtl)
}

group.Controls = &controls
catalogGroups = append(catalogGroups, group)
}
catalog.Groups = &catalogGroups

enc := json.NewEncoder(w)
enc.SetIndent("", " ")
if err := enc.Encode(catalog); err != nil {
return fmt.Errorf("encoding oscal json data: %w", err)
}
return nil
}

0 comments on commit 6767dce

Please sign in to comment.