Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(oscal): link remapper method component defn #879

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
23 changes: 11 additions & 12 deletions src/cmd/evaluate/evaluate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import (
"strings"

"github.com/defenseunicorns/go-oscal/src/pkg/files"
oscalTypes "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-3"
"github.com/spf13/cobra"

"github.com/defenseunicorns/lula/src/cmd/common"
pkgCommon "github.com/defenseunicorns/lula/src/pkg/common"
"github.com/defenseunicorns/lula/src/pkg/common/oscal"
"github.com/defenseunicorns/lula/src/pkg/common/result"
"github.com/defenseunicorns/lula/src/pkg/message"
"github.com/spf13/cobra"
)

var evaluateHelp = `
Expand Down Expand Up @@ -81,7 +81,7 @@ func EvaluateCommand() *cobra.Command {
return evaluateCmd
}

func EvaluateAssessments(assessmentMap map[string]*oscalTypes.AssessmentResults, target string, summary, machine bool) error {
func EvaluateAssessments(assessmentMap map[string]*oscal.AssessmentResults, target string, summary, machine bool) error {
// Identify the threshold & latest for comparison
resultMap := oscal.FilterResults(assessmentMap)

Expand All @@ -103,11 +103,7 @@ func EvaluateAssessments(assessmentMap map[string]*oscalTypes.AssessmentResults,

// Write each file back in the case of modification
for filePath, assessment := range assessmentMap {
model := oscalTypes.OscalCompleteSchema{
AssessmentResults: assessment,
}

err := oscal.WriteOscalModel(filePath, &model)
err := oscal.WriteOscalModelNew(filePath, assessment)
if err != nil {
return err
}
Expand Down Expand Up @@ -241,12 +237,12 @@ func evaluateTarget(target oscal.EvalResult, source string, summary, machine boo

// Read many filepaths into a map[filepath]*AssessmentResults
// Placing here until otherwise decided on value elsewhere
func readManyAssessmentResults(fileArray []string) (map[string]*oscalTypes.AssessmentResults, error) {
func readManyAssessmentResults(fileArray []string) (map[string]*oscal.AssessmentResults, error) {
if len(fileArray) == 0 {
return nil, fmt.Errorf("no files provided for evaluation")
}

assessmentMap := make(map[string]*oscalTypes.AssessmentResults)
assessmentMap := make(map[string]*oscal.AssessmentResults)
for _, fileString := range fileArray {
err := files.IsJsonOrYaml(fileString)
if err != nil {
Expand All @@ -257,11 +253,14 @@ func readManyAssessmentResults(fileArray []string) (map[string]*oscalTypes.Asses
if err != nil {
return nil, err
}
assessment, err := oscal.NewAssessmentResults(data)

var assessment oscal.AssessmentResults
err = assessment.NewModel(data)
if err != nil {
return nil, err
}
assessmentMap[fileString] = assessment

assessmentMap[fileString] = &assessment
}

return assessmentMap, nil
Expand Down
7 changes: 1 addition & 6 deletions src/cmd/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"strings"

oscalTypes "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-3"
"github.com/spf13/cobra"

"github.com/defenseunicorns/lula/src/cmd/common"
Expand Down Expand Up @@ -122,12 +121,8 @@ var generateComponentCmd = &cobra.Command{
message.Fatalf(err, "error creating component - %s\n", err.Error())
}

var model = oscalTypes.OscalModels{
ComponentDefinition: comp,
}

// Write the component definition to file
err = oscal.WriteOscalModel(opts.OutputFile, &model)
err = oscal.WriteOscalModelNew(opts.OutputFile, comp)
if err != nil {
message.Fatalf(err, "error writing component to file")
}
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/generate/system-security-plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func GenerateSSPCommand() *cobra.Command {
if err != nil {
return err
}
if modelType != "profile" {
if modelType != oscal.OSCAL_PROFILE {
return fmt.Errorf("profile must be a valid OSCAL profile")
}

Expand Down
7 changes: 4 additions & 3 deletions src/cmd/tools/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,15 @@ func PrintCommand() *cobra.Command {
return fmt.Errorf("error getting assessment directory: %v", err)
}

oscalAssessment, err := oscal.NewAssessmentResults(assessmentData)
var assessment oscal.AssessmentResults
err = assessment.NewModel(assessmentData)
if err != nil {
return fmt.Errorf("error creating oscal assessment results model: %v", err)
}

// Print the resources or validation
if resources {
err = PrintResources(oscalAssessment, observationUuid, assessmentDir, outputFile)
err = PrintResources(assessment.Model, observationUuid, assessmentDir, outputFile)
if err != nil {
return fmt.Errorf("error printing resources: %v", err)
}
Expand All @@ -83,7 +84,7 @@ func PrintCommand() *cobra.Command {
}

// Print the validation
err = PrintValidation(oscalModel.ComponentDefinition, oscalAssessment, observationUuid, outputFile)
err = PrintValidation(oscalModel.ComponentDefinition, assessment.Model, observationUuid, outputFile)
if err != nil {
return fmt.Errorf("error printing validation: %v", err)
}
Expand Down
7 changes: 1 addition & 6 deletions src/cmd/validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"path/filepath"

oscalTypes "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-3"
"github.com/spf13/cobra"

"github.com/defenseunicorns/lula/src/cmd/common"
Expand Down Expand Up @@ -104,12 +103,8 @@ func ValidateCommand() *cobra.Command {
return fmt.Errorf("assessment results are nil")
}

var model = oscalTypes.OscalModels{
AssessmentResults: assessmentResults,
}

// Write the assessment results to file
err = oscal.WriteOscalModel(outputFile, &model)
err = oscal.WriteOscalModelNew(outputFile, assessmentResults)
if err != nil {
return fmt.Errorf("error writing component to file: %v", err)
}
Expand Down
7 changes: 4 additions & 3 deletions src/internal/reporting/reporting.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import (
"fmt"

oscalTypes "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-3"
"gopkg.in/yaml.v3"

"github.com/defenseunicorns/lula/src/cmd/common"
"github.com/defenseunicorns/lula/src/pkg/common/composition"
"github.com/defenseunicorns/lula/src/pkg/common/network"
"github.com/defenseunicorns/lula/src/pkg/common/oscal"
"github.com/defenseunicorns/lula/src/pkg/message"
"gopkg.in/yaml.v3"
)

type ReportData struct {
Expand Down Expand Up @@ -69,11 +70,11 @@ func handleOSCALModel(oscalModel *oscalTypes.OscalModels, format string, compose
}

switch modelType {
case "catalog", "profile", "assessment-plan", "assessment-results", "system-security-plan", "poam":
case oscal.OSCAL_CATALOG, oscal.OSCAL_PROFILE, oscal.OSCAL_ASSESSMENT_PLAN, oscal.OSCAL_ASSESSMENT_RESULTS, oscal.OSCAL_SYSTEM_SECURITY_PLAN, oscal.OSCAL_POAM:
// If the model type is not supported, stop the spinner with a warning
return fmt.Errorf("reporting does not create reports for %s at this time", modelType)

case "component":
case oscal.OSCAL_COMPONENT:
spinner.Updatef("Composing Component Definition")
err := composer.ComposeComponentDefinitions(context.Background(), oscalModel.ComponentDefinition, "")
if err != nil {
Expand Down
8 changes: 5 additions & 3 deletions src/internal/tui/component/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
oscalTypes "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-3"

"github.com/defenseunicorns/lula/src/internal/tui/common"
"github.com/defenseunicorns/lula/src/pkg/common/oscal"
requirementstore "github.com/defenseunicorns/lula/src/pkg/common/requirement-store"
Expand Down Expand Up @@ -73,7 +74,6 @@ func (m ValidateModel) Init() tea.Cmd {

func (m ValidateModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
var err error

switch msg := msg.(type) {
case tea.WindowSizeMsg:
Expand Down Expand Up @@ -106,10 +106,12 @@ func (m ValidateModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

case ValidateStartMsg:
validationStart := time.Now()
m.assessmentResults, err = m.RunValidations(m.runExecutable, m.target)
assessmentResults, err := m.RunValidations(m.runExecutable, m.target)
if err != nil {
common.PrintToLog("error running validations: %v", err)
}
m.assessmentResults = assessmentResults.Model

validationDuration := time.Since(validationStart)
// just adding a minimum of 2 seconds to the "validating" popup
if validationDuration < time.Second*2 {
Expand Down Expand Up @@ -208,7 +210,7 @@ func (m *ValidateModel) updateSizing(height, width int) {
m.width = common.Max(width, minimumWidth)
}

func (m *ValidateModel) RunValidations(runExecutable bool, target string) (*oscalTypes.AssessmentResults, error) {
func (m *ValidateModel) RunValidations(runExecutable bool, target string) (*oscal.AssessmentResults, error) {
validator, err := validation.New(
validation.WithAllowExecution(runExecutable, true),
)
Expand Down
97 changes: 97 additions & 0 deletions src/pkg/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import (
"io"
"os"
"path/filepath"
"reflect"
"regexp"
"strings"

oscalTypes "github.com/defenseunicorns/go-oscal/src/types/oscal-1-1-3"
goversion "github.com/hashicorp/go-version"
"k8s.io/apimachinery/pkg/util/yaml"

"github.com/defenseunicorns/lula/src/pkg/common/network"
"github.com/defenseunicorns/lula/src/pkg/domains/api"
"github.com/defenseunicorns/lula/src/pkg/domains/files"
kube "github.com/defenseunicorns/lula/src/pkg/domains/kubernetes"
Expand Down Expand Up @@ -211,3 +213,98 @@ func CleanMultilineString(str string) string {
formatted := re.ReplaceAllString(str, "\n")
return formatted
}

// RemapPath takes an input path, relative to the baseDir, and remaps it to be relative to the newDir
// Example: path = "folder/file.txt", baseDir = "/home/user/dir", newDir = "/home/user/newDir"
// output path = "../dir/folder/file.txt"
func RemapPath(path string, baseDir string, newDir string) (string, error) {
// Do nothing if the path is a UUID reference
if isUUIDReference(path) {
return path, nil
}

// Return if the path is a URL or absolute link
localDir := network.GetLocalFileDir(path, baseDir)
if localDir == "" {
return path, nil
}

// Trim file://, if present
path = strings.TrimPrefix(path, "file://")

// Find the relative path from newDir to baseDir
relativePath, err := filepath.Rel(newDir, baseDir)
if err != nil {
return "", err
}

// Append the original relative path to the computed relative path
remappedPath := filepath.Join(relativePath, path)
remappedPath = filepath.Clean(remappedPath)

return remappedPath, nil
}

func isUUIDReference(path string) bool {
path = strings.TrimPrefix(path, UUID_PREFIX)
return checkValidUuid(path)
}

// TraverseAndUpdatePaths uses reflection to traverse the obj based on the path and update file path references
func TraverseAndUpdatePaths(obj interface{}, path string, baseDir string, newDir string) error {
// Split the path into components
components := splitPath(path)

// Start reflection traversal
return reflectTraverseAndUpdate(reflect.ValueOf(obj), components, baseDir, newDir)
}

func reflectTraverseAndUpdate(val reflect.Value, components []string, baseDir string, newDir string) error {
if val.Kind() == reflect.Ptr {
val = val.Elem() // Dereference pointer
}

if val.Kind() == reflect.Slice || val.Kind() == reflect.Array {
// Handle slices/arrays
for i := 0; i < val.Len(); i++ {
err := reflectTraverseAndUpdate(val.Index(i), components, baseDir, newDir)
if err != nil {
return err
}
}
return nil
}

if val.Kind() == reflect.Struct && len(components) > 0 {
// Handle structs
field := val.FieldByName(components[0])
if !field.IsValid() {
return fmt.Errorf("field %s not found", components[0])
}
return reflectTraverseAndUpdate(field, components[1:], baseDir, newDir)
}

if len(components) == 0 {
if val.Kind() == reflect.String {
// Update the final field (assumed to be a string)
newValue, err := RemapPath(val.String(), baseDir, newDir)
if err != nil {
return fmt.Errorf("error remapping path %s: %v", val.String(), err)
}
if val.CanSet() {
val.SetString(newValue)
return nil
}
return fmt.Errorf("unable to set string value")
}
// if val.Kind() is not a string, we can't update it
return fmt.Errorf("cannot update type %s", val.Kind())
}

return nil
}

func splitPath(path string) []string {
components := strings.Split(path, ".")
return components
}
Loading
Loading