Skip to content

Commit d51aed2

Browse files
committed
refactor: updates AssessmentMethod to AssessmentProcedure
This change refactors the work in the previous commits to allow for top-level metadata to support different types of assessment procedures and move the AssessmentSteps under AssessmentProcedure struct. The concept of "method" is updated to describe the procedure categorically for tools that run automated procedures. Signed-off-by: Jennifer Power <[email protected]>
1 parent 9d8a610 commit d51aed2

File tree

11 files changed

+433
-281
lines changed

11 files changed

+433
-281
lines changed

layer4/assessment.go

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"time"
77
)
88

9-
// Assessment is a struct that contains the results of a single method within a ControlEvaluation.
9+
// Assessment is a struct that contains the results of a single Procedure within a ControlEvaluation.
1010
type Assessment struct {
1111
// RequirementID is the unique identifier for the requirement being tested
1212
RequirementId string `yaml:"requirement-id"`
@@ -18,10 +18,8 @@ type Assessment struct {
1818
Result Result `yaml:"result"`
1919
// Message is the human-readable result of the test
2020
Message string `yaml:"message"`
21-
// Methods is a slice of assessment methods that were executed during the test
22-
Methods []*AssessmentMethod `yaml:"methods"`
23-
// MethodsExecuted is the number of assessment methods that were executed during the test
24-
MethodsExecuted int `yaml:"methods-executed,omitempty"`
21+
// Procedures is a slice of assessment procedures that were executed during the test
22+
Procedures []*AssessmentProcedure `yaml:"procedures"`
2523
// RunDuration is the time it took to run the test
2624
RunDuration string `yaml:"run-duration,omitempty"`
2725
// Value is the object that was returned during the test
@@ -31,32 +29,24 @@ type Assessment struct {
3129
}
3230

3331
// NewAssessment creates a new Assessment object and returns a pointer to it.
34-
func NewAssessment(requirementId string, description string, applicability []string, methods []*AssessmentMethod) (*Assessment, error) {
32+
func NewAssessment(requirementId string, description string, applicability []string, procedures []*AssessmentProcedure) (*Assessment, error) {
3533
a := &Assessment{
3634
RequirementId: requirementId,
3735
Description: description,
3836
Applicability: applicability,
3937
Result: NotRun,
40-
Methods: methods,
38+
Procedures: procedures,
4139
}
4240
err := a.precheck()
4341
return a, err
4442
}
4543

46-
// AddMethod queues a new method in the Assessment
47-
func (a *Assessment) AddMethod(method AssessmentMethod) {
48-
a.Methods = append(a.Methods, &method)
44+
// AddProcedure queues a new Procedure in the Assessment
45+
func (a *Assessment) AddProcedure(Procedure AssessmentProcedure) {
46+
a.Procedures = append(a.Procedures, &Procedure)
4947
}
5048

51-
func (a *Assessment) runMethod(targetData interface{}, method *AssessmentMethod) Result {
52-
a.MethodsExecuted++
53-
result, message := method.RunMethod(targetData, a.Changes)
54-
a.Result = UpdateAggregateResult(a.Result, result)
55-
a.Message = message
56-
return result
57-
}
58-
59-
// Run will execute all steps, halting if any method does not return layer4.Passed.
49+
// Run executes all automated assessment procedures.
6050
func (a *Assessment) Run(targetData interface{}, changesAllowed bool) Result {
6151
if a.Result != NotRun {
6252
return a.Result
@@ -73,11 +63,15 @@ func (a *Assessment) Run(targetData interface{}, changesAllowed bool) Result {
7363
change.Allow()
7464
}
7565
}
76-
for _, method := range a.Methods {
77-
if a.runMethod(targetData, method) == Failed {
78-
return Failed
66+
67+
for _, procedure := range a.Procedures {
68+
if procedure.Method == TestMethod {
69+
result := procedure.RunProcedure(targetData, a.Changes)
70+
a.Result = UpdateAggregateResult(a.Result, result)
71+
a.Message = procedure.Message
7972
}
8073
}
74+
8175
a.RunDuration = time.Since(startTime).String()
8276
return a.Result
8377
}
@@ -118,10 +112,10 @@ func (a *Assessment) RevertChanges() (corrupted bool) {
118112
// precheck verifies that the assessment has all the required fields.
119113
// It returns an error if the assessment is not valid.
120114
func (a *Assessment) precheck() error {
121-
if a.RequirementId == "" || a.Description == "" || a.Applicability == nil || a.Methods == nil || len(a.Applicability) == 0 || len(a.Methods) == 0 {
115+
if a.RequirementId == "" || a.Description == "" || a.Applicability == nil || a.Procedures == nil || len(a.Applicability) == 0 || len(a.Procedures) == 0 {
122116
message := fmt.Sprintf(
123-
"expected all Assessment fields to have a value, but got: requirementId=len(%v), description=len=(%v), applicability=len(%v), methods=len(%v)",
124-
len(a.RequirementId), len(a.Description), len(a.Applicability), len(a.Methods),
117+
"expected all Assessment fields to have a value, but got: requirementId=len(%v), description=len=(%v), applicability=len(%v), procedures=len(%v)",
118+
len(a.RequirementId), len(a.Description), len(a.Applicability), len(a.Procedures),
125119
)
126120
a.Result = Unknown
127121
a.Message = message

layer4/assessment_method.go

Lines changed: 0 additions & 69 deletions
This file was deleted.

layer4/assessment_procedure.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package layer4
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"reflect"
8+
"runtime"
9+
)
10+
11+
// AssessmentProcedure defines a specific assessment for assessing a Layer 2 control requirement.
12+
type AssessmentProcedure struct {
13+
// Id is the unique identifier of the assessment Procedure being executed
14+
Id string `json:"id" yaml:"id"`
15+
// Name is the human-readable name of the Procedure.
16+
Name string `json:"name" yaml:"name"`
17+
// Description is a detailed explanation of the Procedure.
18+
Description string `json:"description" yaml:"description"`
19+
20+
// Method describe the high-level method used to determine the results of the procedure
21+
Method Method `yaml:"method"`
22+
// Remediation guide is a URL to remediation guidance associated with the control's assessment requirement and this specific assessment Procedure.
23+
RemediationGuide string `json:"remediation-guide,omitempty" yaml:"remediation-guide,omitempty"`
24+
// URL to documentation that describes how the assessment Procedure evaluates the control requirement.
25+
Documentation string `json:"documentation,omitempty" yaml:"documentation,omitempty"`
26+
27+
// Run is a boolean indicating whether the procedure was run or not. When run is true, result is expected to be present.
28+
Run bool `json:"run" yaml:"run"`
29+
// Message is the human-readable result of the test
30+
Message string `json:"message,omitempty" yaml:"message,omitempty"`
31+
// Result is the outcome of the assessment Procedure.
32+
// This field must be present if Run is true.
33+
Result Result `json:"result,omitempty" yaml:"result,omitempty"`
34+
// StepsExecuted is the number of steps that were executed during the assessment execution
35+
StepsExecuted int `json:"-" yaml:"-"`
36+
37+
// Steps define logical steps to inspect the provided targetData and returns a Result with a message.
38+
// The message may be an error string or other descriptive text.
39+
Steps []AssessmentStep
40+
}
41+
42+
// NewProcedure creates a new AssessmentProcedure object and returns a pointer to it.
43+
func NewProcedure(id, name, description string, steps []AssessmentStep) (*AssessmentProcedure, error) {
44+
a := &AssessmentProcedure{
45+
Id: id,
46+
Name: name,
47+
Description: description,
48+
Result: NotRun,
49+
Steps: steps,
50+
}
51+
err := a.precheck()
52+
return a, err
53+
}
54+
55+
// AssessmentStep is a function type that inspects the provided targetData and returns a Result with a message.
56+
// The message may be an error string or other descriptive text.
57+
type AssessmentStep func(payload interface{}, c map[string]*Change) (Result, string)
58+
59+
func (as AssessmentStep) String() string {
60+
// Get the function pointer correctly
61+
fn := runtime.FuncForPC(reflect.ValueOf(as).Pointer())
62+
if fn == nil {
63+
return "<unknown function>"
64+
}
65+
return fn.Name()
66+
}
67+
68+
func (as AssessmentStep) MarshalJSON() ([]byte, error) {
69+
return json.Marshal(as.String())
70+
}
71+
72+
func (as AssessmentStep) MarshalYAML() (interface{}, error) {
73+
return as.String(), nil
74+
}
75+
76+
// AddStep queues a new step in the Assessment Procedure
77+
func (a *AssessmentProcedure) AddStep(step AssessmentStep) {
78+
a.Steps = append(a.Steps, step)
79+
}
80+
81+
// RunProcedure executes all assessment steps, halting if any assessment does not return layer4.Passed.
82+
func (a *AssessmentProcedure) RunProcedure(targetData interface{}, changes map[string]*Change) Result {
83+
a.Run = true
84+
85+
err := a.precheck()
86+
if err != nil {
87+
a.Result = Unknown
88+
return a.Result
89+
}
90+
for _, steps := range a.Steps {
91+
if a.runStep(targetData, changes, steps) == Failed {
92+
return Failed
93+
}
94+
}
95+
return a.Result
96+
}
97+
98+
func (a *AssessmentProcedure) runStep(targetData interface{}, changes map[string]*Change, step AssessmentStep) Result {
99+
a.StepsExecuted++
100+
result, message := step(targetData, changes)
101+
a.Result = UpdateAggregateResult(a.Result, result)
102+
a.Message = message
103+
return result
104+
}
105+
106+
// precheck verifies that the assessment procedure has step fields.
107+
// It returns an error if the assessment is not valid.
108+
func (a *AssessmentProcedure) precheck() error {
109+
if len(a.Steps) == 0 {
110+
message := fmt.Sprintf(
111+
"expected all Assessment Procedure fields steps=len(%v)",
112+
len(a.Steps),
113+
)
114+
return errors.New(message)
115+
}
116+
return nil
117+
}

0 commit comments

Comments
 (0)