Files
kubescape/core/cautils/getter/loadpolicy.go
Matthias Bertschy 57addd493f fix all linter errors
Signed-off-by: Matthias Bertschy <matthias.bertschy@gmail.com>
2026-02-02 17:32:08 +01:00

272 lines
7.3 KiB
Go

package getter
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/armosec/armoapi-go/armotypes"
"github.com/kubescape/opa-utils/reporthandling"
"github.com/kubescape/opa-utils/reporthandling/attacktrack/v1alpha1"
)
// =======================================================================================================================
// ============================================== LoadPolicy =============================================================
// =======================================================================================================================
var (
DefaultLocalStore = getCacheDir()
ErrNotImplemented = errors.New("feature is currently not supported")
ErrNotFound = errors.New("name not found")
ErrNameRequired = errors.New("missing required input framework name")
ErrIDRequired = errors.New("missing required input control ID")
ErrFrameworkNotMatching = errors.New("framework from file not matching")
ErrControlNotMatching = errors.New("control from file not matching")
)
var (
_ IPolicyGetter = &LoadPolicy{}
_ IExceptionsGetter = &LoadPolicy{}
_ IAttackTracksGetter = &LoadPolicy{}
_ IControlsInputsGetter = &LoadPolicy{}
)
func getCacheDir() string {
defaultDirPath := ".kubescape"
if homeDir, err := os.UserHomeDir(); err == nil {
defaultDirPath = filepath.Join(homeDir, defaultDirPath)
}
return defaultDirPath
}
// LoadPolicy loads policies from a local repository.
type LoadPolicy struct {
filePaths []string
}
// NewLoadPolicy builds a LoadPolicy.
func NewLoadPolicy(filePaths []string) *LoadPolicy {
return &LoadPolicy{
filePaths: filePaths,
}
}
// GetControl returns a control from the policy file.
func (lp *LoadPolicy) GetControl(controlID string) (*reporthandling.Control, error) {
if controlID == "" {
return nil, ErrIDRequired
}
// NOTE: this assumes that only the first path contains either a valid control descriptor or a framework descriptor
filePath := lp.filePath()
buf, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
// check if the file is a control descriptor: a ControlID field is populated.
var control reporthandling.Control
if err = json.Unmarshal(buf, &control); err == nil && control.ControlID != "" {
if strings.EqualFold(controlID, control.ControlID) {
return &control, nil
}
return nil, fmt.Errorf("controlID: %s: %w", controlID, ErrControlNotMatching)
}
// check if the file is a framework descriptor
var framework reporthandling.Framework
if err = json.Unmarshal(buf, &framework); err != nil {
return nil, err
}
for _, toPin := range framework.Controls {
ctrl := toPin
if strings.EqualFold(ctrl.ControlID, controlID) {
return &ctrl, nil
}
}
return nil, fmt.Errorf("controlID: %s: %w", controlID, ErrControlNotMatching)
}
// GetFramework retrieves a framework configuration from the policy paths.
func (lp *LoadPolicy) GetFramework(frameworkName string) (*reporthandling.Framework, error) {
if frameworkName == "" {
return nil, ErrNameRequired
}
for _, filePath := range lp.filePaths {
buf, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
var framework reporthandling.Framework
if err = json.Unmarshal(buf, &framework); err != nil {
return nil, err
}
if strings.EqualFold(frameworkName, framework.Name) {
return &framework, nil
}
}
return nil, fmt.Errorf("framework: %s: %w", frameworkName, ErrFrameworkNotMatching)
}
// GetFrameworks returns all configured framework descriptors.
func (lp *LoadPolicy) GetFrameworks() ([]reporthandling.Framework, error) {
frameworks := make([]reporthandling.Framework, 0, 10)
seenFws := make(map[string]struct{})
for _, f := range lp.filePaths {
buf, err := os.ReadFile(f)
if err != nil {
return nil, err
}
var framework reporthandling.Framework
if err = json.Unmarshal(buf, &framework); err != nil {
// ignore invalid framework files
continue
}
// dedupe
_, alreadyLoaded := seenFws[framework.Name]
if alreadyLoaded {
continue
}
seenFws[framework.Name] = struct{}{}
frameworks = append(frameworks, framework)
}
return frameworks, nil
}
// ListFrameworks lists the names of all configured frameworks in this policy.
func (lp *LoadPolicy) ListFrameworks() ([]string, error) {
frameworkNames := make([]string, 0, 10)
for _, f := range lp.filePaths {
buf, err := os.ReadFile(f)
if err != nil {
return nil, err
}
var framework reporthandling.Framework
if err := json.Unmarshal(buf, &framework); err != nil {
continue
}
if framework.Name == "" || contains(frameworkNames, framework.Name) {
continue
}
frameworkNames = append(frameworkNames, framework.Name)
}
return frameworkNames, nil
}
// ListControls returns the list of controls for this framework.
//
// At this moment, controls are listed for one single configured framework.
func (lp *LoadPolicy) ListControls() ([]string, error) {
controlIDs := make([]string, 0, 100)
filePath := lp.filePath()
buf, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
var framework reporthandling.Framework
if err = json.Unmarshal(buf, &framework); err != nil {
return nil, err
}
for _, ctrl := range framework.Controls {
controlIDs = append(controlIDs, ctrl.ControlID)
}
return controlIDs, nil
}
// GetExceptions retrieves configured exceptions.
//
// NOTE: the cluster parameter is not used at this moment.
func (lp *LoadPolicy) GetExceptions(_ /* clusterName */ string) ([]armotypes.PostureExceptionPolicy, error) {
// NOTE: this assumes that the first path contains a valid exceptions descriptor
filePath := lp.filePath()
buf, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
exception := make([]armotypes.PostureExceptionPolicy, 0, 300)
err = json.Unmarshal(buf, &exception)
return exception, err
}
// GetControlsInputs retrieves the map of control configs.
//
// NOTE: the cluster parameter is not used at this moment.
func (lp *LoadPolicy) GetControlsInputs(_ /* clusterName */ string) (map[string][]string, error) {
// NOTE: this assumes that only the first path contains a valid control inputs descriptor
filePath := lp.filePath()
fileName := filepath.Base(filePath)
buf, err := os.ReadFile(filePath)
if err != nil {
formattedError := fmt.Errorf(
`error opening %s file, "controls-config" will be downloaded from ARMO management portal`,
fileName,
)
return nil, formattedError
}
controlInputs := make(map[string][]string, 100) // from armotypes.Settings.PostureControlInputs
if err = json.Unmarshal(buf, &controlInputs); err != nil {
formattedError := fmt.Errorf(
`error reading %s file, %v, "controls-config" will be downloaded from ARMO management portal`,
fileName, err,
)
return nil, formattedError
}
return controlInputs, nil
}
// GetAttackTracks yields the attack tracks from a config file.
func (lp *LoadPolicy) GetAttackTracks() ([]v1alpha1.AttackTrack, error) {
attackTracks := make([]v1alpha1.AttackTrack, 0, 20)
buf, err := os.ReadFile(lp.filePath())
if err != nil {
return nil, err
}
if err = json.Unmarshal(buf, &attackTracks); err != nil {
return nil, err
}
return attackTracks, nil
}
// temporary support for a list of files
func (lp *LoadPolicy) filePath() string {
if len(lp.filePaths) > 0 {
return lp.filePaths[0]
}
return ""
}