Skip to content

Commit

Permalink
CNS-2791: Add a remote configurator that pulls changes from CB Consol…
Browse files Browse the repository at this point in the history
…e and applies them to the CR (#184)

* Update README.md

* Update README.md

* Fix charts for main to be idetical to latest release v6.0.1

* Update README.md

* Adding labels field to charts

* Adding company code secret template to charts

* Adding company code secret template to charts fix

* Add documentation for secrets with helm

* config applier skeleton

* More work on the configuration applier loop.

* Happy path test for config_applier

* Pass context to API functions as they'll do IO

* More tests

* Send Failed status to backend when failing to list or update CR

* Treat missing CR when a change is pending as error

* Move "scheduling" logic to a separate struct for config applier

* Use a DummyAPI implementation

* Small refactoring

* Add test for feature toggles

* Add tests for the version field. Split the modification logic in a separate helper function to make testing easier

* Sort the change slice in case there are multiple pending changes

* Clearing TODOs, small fixes

* Removing more TODOs

* Turn off configurator by default and use env var to enable

* Renaming things

* Refactor tests a bit and remove more TODOs

* Change the configurator to read the CR first , so it can extract the cluster name

* Validate change against sensor capabilities

* Validate operator and agent version compatibility

* Tests for positive validation path

* Bring the Validate and Apply change funcs under 1 struct and validate before applying

* Adding change validation to the configuration

* Change validator interface and add fetcher to download metadata from the API

* Add auth_provider and adjust test to match

* Create AccessTokenProvider in main.go

* Extract the ApiGateway from processors.* and use it in configurator as well.

* Go back to the previous version as it looked simpler

* Fixing some tests and main.go

* Change the agent_processor tests to match the new func. Remove a test that was not deterministic

* Add clusterIdentifier parameter to GetConfigurationChanges

* Add clusterID to the status update model call

* Add secret detection to the changer

* Add secret detection to the validation

* Minor TODOs

* Some missing test cases for validation

* More minor changes

* Add sensor metadata implementation. Add dummy config changes implementation.

* Move data classes around

* Removing TODOs

* Change the feature toggle to be part of the CR

* Change errors to have system info and user info for debugging vs user context in the UI

* Small refactor

* Change dummy struct to just a func

* Bump timeout a bit

* Remove controller_test.go

* Add back the env var during development until the feature is completed (so it is not ON by default when testing)

* Add remote_configuration to docker build

* Fix not wrapped error

* Fix missing generate run

* Fix sensors path and response

* Increase iteration sleep time

* Fix helm indentation

* Change the version reset to be more readable

* Move the remote_configuration under cbcontainers and controllers

* Added godoc

---------

Co-authored-by: BenRub <[email protected]>
Co-authored-by: benrub <[email protected]>
  • Loading branch information
3 people authored Sep 20, 2023
1 parent 5de2343 commit 82c2bff
Show file tree
Hide file tree
Showing 34 changed files with 2,142 additions and 218 deletions.
11 changes: 11 additions & 0 deletions api/v1/cbcontainersagent_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ type CBContainersComponentsSettings struct {
// care of determining the necessary `NO_PROXY` settings.
//
Proxy *CBContainersProxySettings `json:"proxy,omitempty"`

// RemoteConfiguration holds settings for the operator/agent's feature to apply configuration changes via the Carbon black console
RemoteConfiguration *CBContainersRemoteConfigurationSettings `json:"remoteConfiguration,omitempty"`
}

func (s CBContainersComponentsSettings) ShouldCreateDefaultImagePullSecrets() bool {
Expand Down Expand Up @@ -126,6 +129,14 @@ type CBContainersProxySettings struct {
NoProxySuffix *string `json:"noProxySuffix,omitempty"`
}

// CBContainersRemoteConfigurationSettings holds settings for the operator/agent's feature to apply configuration changes via the Carbon black console
type CBContainersRemoteConfigurationSettings struct {
// EnabledForAgent turns the feature to change agent configuration remotely (as opposed to operator configuration)
//
// +kubebuilder:default:=true
EnabledForAgent *bool `json:"enabledForAgent,omitempty"`
}

// CBContainersAgentStatus defines the observed state of CBContainersAgent
type CBContainersAgentStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
Expand Down
25 changes: 25 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions cbcontainers/communication/gateway/api_gateway.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gateway

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
Expand All @@ -16,6 +17,8 @@ var (
ErrGettingOperatorCompatibility = errors.New("error while getting the operator compatibility")
)

// TODO: Extract the cluster group + name + ID as separate struct identifying a cluster and used together

type ApiGateway struct {
account string
cluster string
Expand Down Expand Up @@ -165,3 +168,45 @@ func (gateway *ApiGateway) GetCompatibilityMatrixEntryFor(operatorVersion string

return r, nil
}

func (gateway *ApiGateway) GetSensorMetadata() ([]models.SensorMetadata, error) {
type getSensorsResponse struct {
Sensors []models.SensorMetadata `json:"sensors"`
}

url := gateway.baseUrl("setup/sensors")
resp, err := gateway.baseRequest().
SetResult(getSensorsResponse{}).
Get(url)

if err != nil {
return nil, err
}
if !resp.IsSuccess() {
return nil, fmt.Errorf("failed to get sensor metadata with status code (%d)", resp.StatusCode())
}

r, ok := resp.Result().(*getSensorsResponse)
if !ok || r == nil {
return nil, fmt.Errorf("malformed sensor metadata response")
}
return r.Sensors, nil
}

// GetConfigurationChanges returns a list of configuration changes for the cluster
func (gateway *ApiGateway) GetConfigurationChanges(ctx context.Context, clusterIdentifier string) ([]models.ConfigurationChange, error) {
// TODO: Real implementation with CNS-2790
c := randomRemoteConfigChange()
if c != nil {
return []models.ConfigurationChange{*c}, nil

}
return nil, nil
}

// UpdateConfigurationChangeStatus either acknowledges a remote configuration change applied to the cluster or marks the attempt as a failure
func (gateway *ApiGateway) UpdateConfigurationChangeStatus(context.Context, models.ConfigurationChangeStatusUpdate) error {
// TODO: Real implementation with CNS-2790

return nil
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package processors
package gateway

import (
cbcontainersv1 "github.com/vmware/cbcontainers-operator/api/v1"
"github.com/vmware/cbcontainers-operator/cbcontainers/communication/gateway"
)

type DefaultGatewayCreator struct {
Expand All @@ -12,9 +11,9 @@ func NewDefaultGatewayCreator() *DefaultGatewayCreator {
return &DefaultGatewayCreator{}
}

func (creator *DefaultGatewayCreator) CreateGateway(cbContainersAgent *cbcontainersv1.CBContainersAgent, accessToken string) (APIGateway, error) {
func (creator *DefaultGatewayCreator) CreateGateway(cbContainersAgent *cbcontainersv1.CBContainersAgent, accessToken string) (*ApiGateway, error) {
spec := cbContainersAgent.Spec
builder := gateway.NewBuilder(spec.Account, spec.ClusterName, accessToken, spec.Gateways.ApiGateway.Host, cbContainersAgent.ObjectMeta.Labels).
builder := NewBuilder(spec.Account, spec.ClusterName, accessToken, spec.Gateways.ApiGateway.Host, cbContainersAgent.ObjectMeta.Labels).
SetURLComponents(spec.Gateways.ApiGateway.Scheme, spec.Gateways.ApiGateway.Port, spec.Gateways.ApiGateway.Adapter).
SetTLSInsecureSkipVerify(spec.Gateways.GatewayTLS.InsecureSkipVerify).
SetTLSRootCAsBundle(spec.Gateways.GatewayTLS.RootCAsBundle)
Expand Down
63 changes: 63 additions & 0 deletions cbcontainers/communication/gateway/dummy_configuration_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package gateway

import (
"github.com/vmware/cbcontainers-operator/cbcontainers/models"
"math/rand"
"strconv"
)

// TODO: This will be removed once real APIs are implemented for this but it helps try the feature while in development
// API task - CNS-2790

var (
tr = true
fal = false
dummyAgentVersions = []string{"2.12.1", "2.10.0", "2.12.0", "2.11.0", "3.0.0"}
)

func randomRemoteConfigChange() *models.ConfigurationChange {
csRand, runtimeRand, cndrRand, versionRand, nilRand := rand.Int(), rand.Int(), rand.Int(), rand.Intn(len(dummyAgentVersions)), rand.Int()

if nilRand%5 == 1 {
return nil
}

changeVersion := &dummyAgentVersions[versionRand]

var changeClusterScanning *bool
var changeRuntime *bool
var changeCNDR *bool

switch csRand % 5 {
case 1, 3:
changeClusterScanning = &tr
case 2, 4:
changeClusterScanning = &fal
default:
changeClusterScanning = nil
}

switch runtimeRand % 5 {
case 1, 3:
changeRuntime = &tr
case 2, 4:
changeRuntime = &fal
default:
changeRuntime = nil
}

if changeVersion != nil && *changeVersion == "3.0.0" && cndrRand%2 == 0 {
changeCNDR = &tr
} else {
changeCNDR = &fal
}

return &models.ConfigurationChange{
ID: strconv.Itoa(rand.Int()),
AgentVersion: changeVersion,
EnableClusterScanning: changeClusterScanning,
EnableRuntime: changeRuntime,
EnableCNDR: changeCNDR,
Status: models.ChangeStatusPending,
}
}
2 changes: 1 addition & 1 deletion cbcontainers/models/operator_compatibility.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ func (c OperatorCompatibility) CheckCompatibility(agentVersion string) error {
return fmt.Errorf("agent version too low, downgrade the operator to use that agent version: min is [%s], desired is [%s]", c.MinAgent, agentVersion)
}

// if we are here it means the operator and the agent version are compatibile
// if we are here it means the operator and the agent version are compatible
return nil
}
40 changes: 40 additions & 0 deletions cbcontainers/models/remote_configuration_changes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package models

type RemoteChangeStatus string

var (
ChangeStatusPending RemoteChangeStatus = "PENDING"
ChangeStatusAcked RemoteChangeStatus = "ACKNOWLEDGED"
ChangeStatusFailed RemoteChangeStatus = "FAILED"
)

type ConfigurationChange struct {
ID string `json:"id"`
Status RemoteChangeStatus `json:"status"`
AgentVersion *string `json:"agent_version"`
EnableClusterScanning *bool `json:"enable_cluster_scanning"`
EnableRuntime *bool `json:"enable_runtime"`
EnableCNDR *bool `json:"enable_cndr"`
EnableClusterScanningSecretDetection *bool `json:"enable_cluster_scanning_secret_detection"`
Timestamp string `json:"timestamp"`
}

type ConfigurationChangeStatusUpdate struct {
ID string `json:"id"`
ClusterIdentifier string `json:"cluster_identifier"`
ClusterGroup string `json:"cluster_group"`
ClusterName string `json:"cluster_name"`
Status RemoteChangeStatus `json:"status"`

// AppliedGeneration tracks the generation of the Custom resource where the change was applied
AppliedGeneration int64 `json:"applied_generation"`
// AppliedTimestamp records when the change was applied in RFC3339 format
AppliedTimestamp string `json:"applied_timestamp"`

// Error should hold information about encountered errors when the change application failed.
// For system usage only, not meant for end-users.
Error string `json:"encountered_error"`
// ErrorReason should be populated if some additional information can be shown to the user (e.g. why a change was invalid)
// It should not be used to store system information
ErrorReason string `json:"error_reason"`
}
10 changes: 10 additions & 0 deletions cbcontainers/models/sensor_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package models

type SensorMetadata struct {
Version string `json:"version"`
IsLatest bool `json:"is_latest" `
SupportsRuntime bool `json:"supports_runtime"`
SupportsClusterScanning bool `json:"supports_cluster_scanning"`
SupportsClusterScanningSecrets bool `json:"supports_cluster_scanning_secrets"`
SupportsCndr bool `json:"supports_cndr"`
}
8 changes: 3 additions & 5 deletions cbcontainers/processors/agent_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ type APIGateway interface {
GetCompatibilityMatrixEntryFor(operatorVersion string) (*models.OperatorCompatibility, error)
}

type APIGatewayCreator interface {
CreateGateway(cbContainersCluster *cbcontainersv1.CBContainersAgent, accessToken string) (APIGateway, error)
}
type APIGatewayCreator func(cbContainersCluster *cbcontainersv1.CBContainersAgent, accessToken string) (APIGateway, error)

type OperatorVersionProvider interface {
GetOperatorVersion() (string, error)
Expand Down Expand Up @@ -79,7 +77,7 @@ func (processor *AgentProcessor) initializeIfNeeded(cbContainersCluster *cbconta
}

processor.log.Info("Initializing AgentProcessor components")
gateway, err := processor.gatewayCreator.CreateGateway(cbContainersCluster, accessToken)
gateway, err := processor.gatewayCreator(cbContainersCluster, accessToken)
if err != nil {
return err
}
Expand Down Expand Up @@ -118,7 +116,7 @@ func (processor *AgentProcessor) checkCompatibility(cbContainersAgent *cbcontain
}
return err
}
gateway, err := processor.gatewayCreator.CreateGateway(cbContainersAgent, accessToken)
gateway, err := processor.gatewayCreator(cbContainersAgent, accessToken)
if err != nil {
processor.log.Error(err, "skipping compatibility check, error while building API gateway")
// if there is an error while building the gateway log it and skip the check
Expand Down
Loading

0 comments on commit 82c2bff

Please sign in to comment.