diff --git a/go.mod b/go.mod index 3904599..73d8703 100644 --- a/go.mod +++ b/go.mod @@ -5,19 +5,17 @@ go 1.23.6 toolchain go1.24.1 require ( - github.com/compliance-framework/agent v0.1.1 - github.com/compliance-framework/configuration-service v0.1.1 - github.com/google/uuid v1.6.0 + github.com/compliance-framework/agent v0.1.7-0.20250407053328-1983bae6f0e8 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-plugin v1.6.3 github.com/stretchr/testify v1.10.0 - google.golang.org/protobuf v1.36.6 ) require ( github.com/agnivade/levenshtein v1.2.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/compliance-framework/configuration-service v0.1.2-0.20250327060646-625c895cd99c // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fatih/color v1.18.0 // indirect github.com/go-ini/ini v1.67.0 // indirect @@ -26,6 +24,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect @@ -54,6 +53,7 @@ require ( golang.org/x/text v0.23.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/grpc v1.71.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 2a0594b..668ccec 100644 --- a/go.sum +++ b/go.sum @@ -24,10 +24,10 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/compliance-framework/agent v0.1.1 h1:uQ4idgwOMqrgM0JeYCtBv20HZoMymsH2nownrkl457w= -github.com/compliance-framework/agent v0.1.1/go.mod h1:jy/26xgTx9+at64ipTV1oo80pTVyhtlZaSMViQ3cVVQ= -github.com/compliance-framework/configuration-service v0.1.1 h1:p/r5vq1FLe0S8j/kLhth4Dvda8xajVPOBjnO9QauMjM= -github.com/compliance-framework/configuration-service v0.1.1/go.mod h1:tLKJKXbQbY9Pg/e3BJtJVkqxaejXJMHoE8Yp0NW4lDE= +github.com/compliance-framework/agent v0.1.7-0.20250407053328-1983bae6f0e8 h1:ODThayKjBR9gRiP49yZM2PCrzDk+yFczag219KWWoX8= +github.com/compliance-framework/agent v0.1.7-0.20250407053328-1983bae6f0e8/go.mod h1:dAjHJecMEMdKzylOatT54tXmbjgx5d2Wsq4mGnRksZk= +github.com/compliance-framework/configuration-service v0.1.2-0.20250327060646-625c895cd99c h1:7RIDZy+OcF/9O5EquFchd0W9TY+mlz2zSCVCsReG8Bw= +github.com/compliance-framework/configuration-service v0.1.2-0.20250327060646-625c895cd99c/go.mod h1:tLKJKXbQbY9Pg/e3BJtJVkqxaejXJMHoE8Yp0NW4lDE= github.com/containerd/containerd v1.7.26 h1:3cs8K2RHlMQaPifLqgRyI4VBkoldNdEw62cb7qQga7k= github.com/containerd/containerd v1.7.26/go.mod h1:m4JU0E+h0ebbo9yXD7Hyt+sWnc8tChm7MudCjj4jRvQ= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -314,5 +314,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/main.go b/main.go index 3c35833..a4d26e4 100644 --- a/main.go +++ b/main.go @@ -4,18 +4,14 @@ import ( "context" "errors" "fmt" - "os" - "time" - policyManager "github.com/compliance-framework/agent/policy-manager" "github.com/compliance-framework/agent/runner" "github.com/compliance-framework/agent/runner/proto" - "github.com/compliance-framework/configuration-service/sdk" "github.com/compliance-framework/plugin-apt-versions/internal" - "github.com/google/uuid" "github.com/hashicorp/go-hclog" goplugin "github.com/hashicorp/go-plugin" - "google.golang.org/protobuf/types/known/timestamppb" + "os" + "slices" ) type AptVersion struct { @@ -101,181 +97,88 @@ func (l *AptVersion) Eval(request *proto.EvalRequest, apiHelper runner.ApiHelper } func (l *AptVersion) evaluatePolicies(ctx context.Context, activities []*proto.Activity, packageData map[string]interface{}, req *proto.EvalRequest) ([]*proto.Observation, []*proto.Finding, error) { - startTime := time.Now() var accumulatedErrors error findings := make([]*proto.Finding, 0) observations := make([]*proto.Observation, 0) - l.logger.Debug("config", l.config) - - for _, policyPath := range req.GetPolicyPaths() { - steps := make([]*proto.Step, 0) - steps = append(steps, &proto.Step{ - Title: "Compile policy bundle", - Description: "Using a locally addressable policy path, compile the policy files to an in memory executable.", - }) - steps = append(steps, &proto.Step{ - Title: "Execute policy bundle", - Description: "Using previously collected JSON-formatted installed OS package data, execute the compiled policies", - }) - results, err := policyManager.New(ctx, l.logger, policyPath).Execute(ctx, "apt_version", packageData) - if err != nil { - l.logger.Error("Failed to evaluate against policy bundle", "error", err) - accumulatedErrors = errors.Join(accumulatedErrors, err) - return observations, findings, accumulatedErrors - } + l.logger.Trace("config", l.config) - hostname := os.Getenv("HOSTNAME") - subjectAttributeMap := map[string]string{ - "type": "machine-instance", - "hostname": hostname, - } - subjects := []*proto.SubjectReference{ - { - Type: "machine-instance", - Attributes: subjectAttributeMap, - Title: internal.StringAddressed("Machine Instance"), - Remarks: internal.StringAddressed("A machine instance where we've retrieved the installed packages."), - Props: []*proto.Property{ - { - Name: "hostname", - Value: hostname, - Remarks: internal.StringAddressed("The local hostname of the machine where the plugin has been executed"), - }, - }, + hostname := os.Getenv("HOSTNAME") + labels := map[string]string{ + "type": "machine-instance", + "hostname": hostname, + } + subjects := []*proto.SubjectReference{ + { + Type: "machine-instance", + Attributes: map[string]string{ + "type": "machine-instance", + "hostname": hostname, }, - } - actors := []*proto.OriginActor{ - { - Title: "The Continuous Compliance Framework", - Type: "assessment-platform", - Links: []*proto.Link{ - { - Href: "https://compliance-framework.github.io/docs/", - Rel: internal.StringAddressed("reference"), - Text: internal.StringAddressed("The Continuous Compliance Framework"), - }, + Title: internal.StringAddressed("Machine Instance"), + Remarks: internal.StringAddressed("A machine instance where we've retrieved the installed packages."), + Props: []*proto.Property{ + { + Name: "hostname", + Value: hostname, + Remarks: internal.StringAddressed("The local hostname of the machine where the plugin has been executed"), }, - Props: nil, }, - { - Title: "Continuous Compliance Framework - Local APT Installed Packages Plugin", - Type: "tool", - Links: []*proto.Link{ - { - Href: "https://github.com/compliance-framework/plugin-apt-versions", - Rel: internal.StringAddressed("reference"), - Text: internal.StringAddressed("The Continuous Compliance Framework' Local APT Installed Packages Plugin"), - }, + }, + } + actors := []*proto.OriginActor{ + { + Title: "The Continuous Compliance Framework", + Type: "assessment-platform", + Links: []*proto.Link{ + { + Href: "https://compliance-framework.github.io/docs/", + Rel: internal.StringAddressed("reference"), + Text: internal.StringAddressed("The Continuous Compliance Framework"), }, - Props: nil, }, - } - components := []*proto.ComponentReference{ - { - Identifier: "common-components/package", + Props: nil, + }, + { + Title: "Continuous Compliance Framework - Local APT Installed Packages Plugin", + Type: "tool", + Links: []*proto.Link{ + { + Href: "https://github.com/compliance-framework/plugin-apt-versions", + Rel: internal.StringAddressed("reference"), + Text: internal.StringAddressed("The Continuous Compliance Framework' Local APT Installed Packages Plugin"), + }, }, - } - - activities = append(activities, &proto.Activity{ - Title: "Compile Results", - Description: "Using the output from policy execution, compile the resulting output to Observations and Findings, marking any violations, risks, and other OSCAL-familiar data", - Steps: steps, - }) - - for _, result := range results { - // Observation UUID should differ for each individual subject, but remain consistent when validating the same policy for the same subject. - // This acts as an identifier to show the history of an observation. - observationUUIDMap := internal.MergeMaps(subjectAttributeMap, map[string]string{ - "policy": result.Policy.Package.PurePackage(), - "policy_file": result.Policy.File, - "policy_path": policyPath, - }) - observationUUID, err := sdk.SeededUUID(observationUUIDMap) - if err != nil { - accumulatedErrors = errors.Join(accumulatedErrors, err) - // We've been unable to do much here, but let's try the next one regardless. - continue - } - - // Finding UUID should differ for each individual subject, but remain consistent when validating the same policy for the same subject. - // This acts as an identifier to show the history of a finding. - findingUUIDMap := internal.MergeMaps(subjectAttributeMap, map[string]string{ - "policy": result.Policy.Package.PurePackage(), - "policy_file": result.Policy.File, - "policy_path": policyPath, - }) - findingUUID, err := sdk.SeededUUID(findingUUIDMap) - if err != nil { - accumulatedErrors = errors.Join(accumulatedErrors, err) - // We've been unable to do much here, but let's try the next one regardless. - continue - } + Props: nil, + }, + } + components := []*proto.ComponentReference{ + { + Identifier: "common-components/package", + }, + } - observation := proto.Observation{ - ID: uuid.New().String(), - UUID: observationUUID.String(), - Collected: timestamppb.New(startTime), - Expires: timestamppb.New(startTime.Add(24 * time.Hour)), - Origins: []*proto.Origin{{Actors: actors}}, - Subjects: subjects, - Activities: activities, - Components: components, - RelevantEvidence: []*proto.RelevantEvidence{ - { - Description: fmt.Sprintf("Policy %v was executed against the local APT installed packages, using the Local APT Packages Compliance Plugin", result.Policy.Package.PurePackage()), - }, + for _, policyPath := range req.GetPolicyPaths() { + // Explicitly reset steps to make things readable + processor := policyManager.NewPolicyProcessor( + l.logger, + internal.MergeMaps( + labels, + map[string]string{ + "_policy_path": policyPath, }, - } - - newFinding := func() *proto.Finding { - return &proto.Finding{ - ID: uuid.New().String(), - UUID: findingUUID.String(), - Collected: timestamppb.New(time.Now()), - Labels: map[string]string{ - "type": "package", - "host": hostname, - "_policy": result.Policy.Package.PurePackage(), - "_policy_path": result.Policy.File, - }, - Origins: []*proto.Origin{{Actors: actors}}, - Subjects: subjects, - Components: components, - RelatedObservations: []*proto.RelatedObservation{{ObservationUUID: observation.ID}}, - Controls: nil, - } - } - - if len(result.Violations) == 0 { - observation.Title = internal.StringAddressed(fmt.Sprintf("Local APT package validation on %s passed.", result.Policy.Package.PurePackage())) - observation.Description = fmt.Sprintf("Observed no violations on the %s policy within the Local APT Installed Package Compliance Plugin.", result.Policy.Package.PurePackage()) - observations = append(observations, &observation) - - finding := newFinding() - finding.Title = fmt.Sprintf("No violations found on %s", result.Policy.Package.PurePackage()) - finding.Description = fmt.Sprintf("No violations found on the %s policy within the Local APT Packages Compliance Plugin.", result.Policy.Package.PurePackage()) - finding.Status = &proto.FindingStatus{ - State: runner.FindingTargetStatusSatisfied, - } - findings = append(findings, finding) - } else { - observation.Title = internal.StringAddressed(fmt.Sprintf("Validation on %s failed.", result.Policy.Package.PurePackage())) - observation.Description = fmt.Sprintf("Observed %d violation(s) on the %s policy within the Local APT Packaged Compliance Plugin.", len(result.Violations), result.Policy.Package.PurePackage()) - observations = append(observations, &observation) - - for _, violation := range result.Violations { - finding := newFinding() - finding.Title = violation.Title - finding.Description = violation.Description - finding.Remarks = internal.StringAddressed(violation.Remarks) - finding.Status = &proto.FindingStatus{ - State: runner.FindingTargetStatusNotSatisfied, - } - findings = append(findings, finding) - } - } + ), + subjects, + components, + actors, + activities, + ) + obs, finds, err := processor.GenerateResults(ctx, policyPath, packageData) + observations = slices.Concat(observations, obs) + findings = slices.Concat(findings, finds) + if err != nil { + accumulatedErrors = errors.Join(accumulatedErrors, err) } }