forked from kubewarden/audit-scanner
-
Notifications
You must be signed in to change notification settings - Fork 0
Suseobs store #16
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
Draft
jvanz
wants to merge
12
commits into
main
Choose a base branch
from
suseobs-monitor
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Suseobs store #16
Changes from 5 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
e63fcef
feat(store): suse obs store
jvanz ef2e50e
feat(suse obs): send request to SUSE Obs
jvanz 0b124a5
Allow define different type of store and add cli flag to enable SUSE obs
jvanz f20be44
interval configuration and cluster resource urn
jvanz 0a1856b
add a documentation comment
jvanz 691971c
chore(dep): update go.mod
jvanz 458c300
chore(dep): update go version
jvanz 7873c8a
feat: SUSE Obs stackpack
jvanz e3d84e0
fix typo
jvanz cb75f7f
clean privisioner
jvanz 8b0c563
wip
jvanz 0788f40
update .gitignore
jvanz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ package cmd | |
| import ( | ||
| "context" | ||
| "fmt" | ||
| "time" | ||
|
|
||
| "github.com/google/uuid" | ||
| "github.com/kubewarden/audit-scanner/internal/k8s" | ||
|
|
@@ -30,13 +31,24 @@ const ( | |
| //nolint:gocognit,funlen // This function is the CLI entrypoint and it's expected to be long. | ||
| func NewRootCommand() *cobra.Command { | ||
| var ( | ||
| level logconfig.Level // log level. | ||
| outputScan bool // print result of scan as JSON to stdout. | ||
| skippedNs []string // list of namespaces to be skipped from scan. | ||
| insecureSSL bool // skip SSL cert validation when connecting to PolicyServers endpoints. | ||
| disableStore bool // disable storing the results in the k8s cluster. | ||
| level logconfig.Level // log level. | ||
| outputScan bool // print result of scan as JSON to stdout. | ||
| skippedNs []string // list of namespaces to be skipped from scan. | ||
| insecureSSL bool // skip SSL cert validation when connecting to PolicyServers endpoints. | ||
| disableStore bool // disable storing the results in the k8s cluster. | ||
| suseObsURL string // URL to the SUSE OBS API. | ||
| suseObsApiKey string // API key to authenticate with the SUSE OBS API. | ||
| suseObsUrn string // API key to authenticate with the SUSE OBS API. | ||
| suseObsCluster string // API key to authenticate with the SUSE OBS API. | ||
| suseObsRepeatInterval time.Duration | ||
| suseObsExpireInterval time.Duration | ||
| ) | ||
|
|
||
| defaultInterval, err := time.ParseDuration("30m") | ||
| if err != nil { | ||
| log.Logger.Err(err).Msg("cannot parse default suseob interval value ") | ||
| } | ||
|
|
||
| // rootCmd represents the base command when called without any subcommands. | ||
| rootCmd := &cobra.Command{ | ||
| Use: "audit-scanner", | ||
|
|
@@ -112,7 +124,14 @@ There will be a ClusterPolicyReport with results for cluster-wide resources.`, | |
| if err != nil { | ||
| return err | ||
| } | ||
| policyReportStore := report.NewPolicyReportStore(client) | ||
| var policyReportStore report.ReportStore | ||
| if len(suseObsURL) > 0 && len(suseObsApiKey) > 0 && len(suseObsUrn) > 0 && len(suseObsCluster) > 0 { | ||
| log.Debug().Msg("Using SUSE Observability as report store") | ||
| policyReportStore = report.NewSuseObsStore(suseObsApiKey, suseObsURL, suseObsUrn, suseObsCluster, suseObsRepeatInterval, suseObsExpireInterval) | ||
| } else { | ||
| log.Debug().Msg("Using Kubernetes as report store") | ||
| policyReportStore = report.NewPolicyReportStore(client) | ||
| } | ||
|
|
||
| scannerConfig := scanner.Config{ | ||
| PoliciesClient: policiesClient, | ||
|
|
@@ -162,6 +181,12 @@ There will be a ClusterPolicyReport with results for cluster-wide resources.`, | |
| rootCmd.Flags().IntP("parallel-resources", "", defaultParallelResources, "number of resources to scan in parallel") | ||
| rootCmd.Flags().IntP("parallel-policies", "", defaultParallelPolicies, "number of policies to evaluate for a given resource in parallel") | ||
| rootCmd.Flags().IntP("page-size", "", defaultPageSize, "number of resources to fetch from the Kubernetes API server when paginating") | ||
| rootCmd.Flags().StringVar(&suseObsURL, "suseobs-url", "", "URL to the SUSE OBS API") | ||
| rootCmd.Flags().StringVar(&suseObsApiKey, "suseobs-apikey", "", "API key to authenticate with the SUSE OBS API") | ||
| rootCmd.Flags().StringVar(&suseObsUrn, "suseobs-urn", "", "SUSE Observability health check stream urn") | ||
| rootCmd.Flags().StringVar(&suseObsCluster, "suseobs-cluster", "", "SUSE Observability cluster name where audit scanner is running") | ||
| rootCmd.Flags().DurationVar(&suseObsRepeatInterval, "suseobs-repeat-interval", defaultInterval, "The frequency with which audit scanner will send health data to SUSE Observability. Max allowed value is 1800 (30 minutes)") | ||
| rootCmd.Flags().DurationVar(&suseObsExpireInterval, "suseobs-expire-interval", defaultInterval, "The time to wait after the last update before an audit scanner check is deleted by SUSE Observability if the check isn't observed again") | ||
|
Comment on lines
+184
to
+189
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we decide to allow multiple store types. I think we should move the store configuration to a file. |
||
|
|
||
| return rootCmd | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| package report | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "context" | ||
| "crypto/tls" | ||
| "encoding/json" | ||
| "errors" | ||
| "net/http" | ||
| "net/url" | ||
| "strconv" | ||
| "strings" | ||
| "time" | ||
|
|
||
| // "github.com/google/uuid" | ||
| "github.com/rs/zerolog" | ||
| "github.com/rs/zerolog/log" | ||
| corev1 "k8s.io/api/core/v1" | ||
| wgpolicy "sigs.k8s.io/wg-policy-prototypes/policy-report/pkg/api/wgpolicyk8s.io/v1alpha2" | ||
| ) | ||
|
|
||
| // https://docs.stackstate.com/health/health-synchronization#consistency-models | ||
| const DEFAULT_CONSISTENCY_MODEL = "REPEAT_STATES" | ||
|
|
||
| // SuseObsStore is a store for PolicyReport and ClusterPolicyReport. | ||
| type SuseObsStore struct { | ||
| client *http.Client | ||
| apiKey string | ||
| internalHostname string | ||
| urn string | ||
| cluster string | ||
| repeatInterval string | ||
| expireInterval string | ||
| } | ||
|
|
||
| type SuseObsExpireConfiguration struct { | ||
| RepeatInterval string `json:"repeat_interval_s"` | ||
| ExpireInterval string `json:"expiry_interval_s"` | ||
| } | ||
|
|
||
| type SuseObsStream struct { | ||
| Urn string `json:"urn"` | ||
| } | ||
|
|
||
| type SuseObsCheckState struct { | ||
| CheckStateId string `json:"checkStateId"` | ||
| Message string `json:"message"` | ||
| Health string `json:"health"` | ||
| TopologyElementIdentifier string `json:"topologyElementIdentifier"` | ||
| Name string `json:"name"` | ||
| } | ||
|
|
||
| type SuseObsHealthCheck struct { | ||
| ConsistencyModel string `json:"consistency_model"` | ||
| Expire SuseObsExpireConfiguration `json:"expire"` | ||
| Stream SuseObsStream `json:"stream"` | ||
| CheckStates []SuseObsCheckState `json:"check_states"` | ||
| } | ||
|
|
||
| type SuseObsJsonPayload struct { | ||
| ApiKey string `json:"apiKey"` | ||
| CollectionTimestamp int64 `json:"collection_timestamp"` | ||
| InternalHostname string `json:"internalHostname"` | ||
| Events interface{} `json:"events,omitempty"` | ||
| Metrics []interface{} `json:"metrics"` | ||
| ServiceChecks []interface{} `json:"service_checks"` | ||
| Health []SuseObsHealthCheck `json:"health"` | ||
| Topologies []interface{} `json:"topoligies"` | ||
| } | ||
|
|
||
| // NewSuseObsStore creates a new SuseObsStore. | ||
| func NewSuseObsStore(apiKey, internalHostname, urn, cluster string, repeatInterval, expireInterval time.Duration) *SuseObsStore { | ||
| repeatIntervalStr := strconv.FormatFloat(repeatInterval.Seconds(), 'f', 0, 32) | ||
| expireIntervalStr := strconv.FormatFloat(expireInterval.Seconds(), 'f', 0, 32) | ||
| return &SuseObsStore{ | ||
| client: &http.Client{ | ||
| Transport: &http.Transport{ | ||
| // FIXME - configure certicates for a secure communication | ||
| TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, | ||
| }, | ||
| }, | ||
| apiKey: apiKey, | ||
| internalHostname: internalHostname, | ||
| urn: urn, | ||
| cluster: cluster, | ||
| repeatInterval: repeatIntervalStr, | ||
| expireInterval: expireIntervalStr, | ||
| } | ||
| } | ||
|
|
||
| func (s *SuseObsStore) generateCheckStates(policyReport *wgpolicy.PolicyReport) []SuseObsCheckState { | ||
| checkStates := []SuseObsCheckState{} | ||
| for _, result := range policyReport.Results { | ||
| healthCheckStatus := "Clear" | ||
| if result.Result == "fail" { | ||
| healthCheckStatus = "Deviating" | ||
| } | ||
| checkState := SuseObsCheckState{ | ||
| CheckStateId: generateCheckStateId(result.Policy, policyReport.Scope), | ||
| Message: result.Description, | ||
| Health: healthCheckStatus, | ||
| TopologyElementIdentifier: strings.ToLower("urn:kubernetes:/" + s.cluster + ":" + policyReport.Scope.Namespace + ":" + policyReport.Scope.Kind + "/" + policyReport.Scope.Name), | ||
| Name: result.Policy, | ||
| } | ||
| checkStates = append(checkStates, checkState) | ||
| } | ||
| return checkStates | ||
| } | ||
|
|
||
| func generateCheckStateId(policy string, scope *corev1.ObjectReference) string { | ||
| return strings.ToLower(policy + "-" + scope.Namespace + "-" + scope.Kind + "-" + scope.Name + "-" + policy) | ||
| } | ||
|
|
||
| func (s *SuseObsStore) generateCheckStatesFromClusterPolicyReport(policyReport *wgpolicy.ClusterPolicyReport) []SuseObsCheckState { | ||
| checkStates := []SuseObsCheckState{} | ||
| for _, result := range policyReport.Results { | ||
| healthCheckStatus := "Clear" | ||
| if result.Result == "fail" { | ||
| healthCheckStatus = "Deviating" | ||
| } | ||
| checkState := SuseObsCheckState{ | ||
| CheckStateId: generateCheckStateId(result.Policy, policyReport.Scope), | ||
| Message: result.Description, | ||
| Health: healthCheckStatus, | ||
| TopologyElementIdentifier: strings.ToLower("urn:kubernetes:/" + s.cluster + ":" + policyReport.Scope.Kind + "/" + policyReport.Scope.Name), | ||
| Name: result.Policy, | ||
| } | ||
| checkStates = append(checkStates, checkState) | ||
| } | ||
| return checkStates | ||
| } | ||
|
|
||
| func (s *SuseObsStore) generateSuseObsJsonPayload(checkStates []SuseObsCheckState) (*SuseObsJsonPayload, error) { | ||
| url, err := url.Parse(s.internalHostname) | ||
| if err != nil { | ||
| return nil, errors.New("failed to parse SUSE OBS URL") | ||
| } | ||
| payload := &SuseObsJsonPayload{ | ||
| ApiKey: s.apiKey, | ||
| InternalHostname: url.Hostname(), | ||
| CollectionTimestamp: time.Now().Unix(), | ||
| Events: nil, | ||
| Metrics: []interface{}{}, | ||
| ServiceChecks: []interface{}{}, | ||
| Topologies: []interface{}{}, | ||
| Health: []SuseObsHealthCheck{{ | ||
| ConsistencyModel: DEFAULT_CONSISTENCY_MODEL, | ||
| Expire: SuseObsExpireConfiguration{ | ||
| RepeatInterval: s.repeatInterval, | ||
| ExpireInterval: s.expireInterval, | ||
| }, | ||
| Stream: SuseObsStream{ | ||
| Urn: s.urn, | ||
| }, | ||
| CheckStates: checkStates, | ||
| }}, | ||
| } | ||
| return payload, nil | ||
| } | ||
|
|
||
| func (s *SuseObsStore) convertPolicyReportIntoSuseObsJsonPayload(policyReport *wgpolicy.PolicyReport) (*SuseObsJsonPayload, error) { | ||
| return s.generateSuseObsJsonPayload(s.generateCheckStates(policyReport)) | ||
| } | ||
|
|
||
| func (s *SuseObsStore) convertClusterPolicyReportIntoSuseObsJsonPayload(policyReport *wgpolicy.ClusterPolicyReport) (*SuseObsJsonPayload, error) { | ||
| return s.generateSuseObsJsonPayload(s.generateCheckStatesFromClusterPolicyReport(policyReport)) | ||
| } | ||
|
|
||
| // CreateOrPatchPolicyReport creates or patches a PolicyReport. | ||
| func (s *SuseObsStore) CreateOrPatchPolicyReport(ctx context.Context, policyReport *wgpolicy.PolicyReport) error { | ||
| payload, err := s.convertPolicyReportIntoSuseObsJsonPayload(policyReport) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return s.sendRequest(payload) | ||
| } | ||
|
|
||
| func (s *SuseObsStore) DeleteOldPolicyReports(ctx context.Context, scanRunID, namespace string) error { | ||
| // No need to delete SUSE Obs will remove the check states after the expiry interval | ||
| return nil | ||
| } | ||
|
|
||
| // CreateOrPatchClusterPolicyReport creates or patches a ClusterPolicyReport. | ||
| func (s *SuseObsStore) CreateOrPatchClusterPolicyReport(ctx context.Context, clusterPolicyReport *wgpolicy.ClusterPolicyReport) error { | ||
| payload, err := s.convertClusterPolicyReportIntoSuseObsJsonPayload(clusterPolicyReport) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return s.sendRequest(payload) | ||
| } | ||
|
|
||
| func (s *SuseObsStore) DeleteOldClusterPolicyReports(ctx context.Context, scanRunID string) error { | ||
| // No need to delete SUSE Obs will remove the check states after the expiry interval | ||
| return nil | ||
| } | ||
|
|
||
| func (s *SuseObsStore) sendRequest(payload *SuseObsJsonPayload) error { | ||
| if len(payload.Health) > 0 && len(payload.Health[0].CheckStates) == 0 { | ||
| return nil | ||
| } | ||
| jsonPayload, err := json.Marshal(payload) | ||
| if err != nil { | ||
| return errors.New("failed to marshal SUSE OBS payload") | ||
| } | ||
| url := s.internalHostname + "/receiver/stsAgent/intake?api_key=" | ||
| log.Debug().Dict("dict", zerolog.Dict()). | ||
| Str("SUSE Obs URL", url). | ||
| RawJSON("payload", jsonPayload). | ||
| Msg("Sending SUSE OBS healch check request") | ||
|
|
||
| response, err := s.client.Post(url+s.apiKey, "application/json", bytes.NewReader(jsonPayload)) | ||
| if err != nil { | ||
| return errors.Join(errors.New("failed to send SUSE OBS payload"), err) | ||
| } | ||
| if response.StatusCode != http.StatusOK { | ||
| return errors.New("SUSE Obs returned an error. Status code: " + response.Status) | ||
| } | ||
| return nil | ||
|
|
||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is very ugly. And it needs to be refactored.
What do you think about having multiple stores? For example, the audit scanner could create policy reports and send the data to SUSE Obs.