From 727c183bf6371efa463c8ff1ed29648e5264f99c Mon Sep 17 00:00:00 2001 From: RTann Date: Fri, 14 Jan 2022 08:19:36 -0800 Subject: [PATCH 01/30] for now --- .../listener/resources/registry_store.go | 46 ++++++++++++++++++ .../kubernetes/listener/resources/secrets.go | 48 +++++++++++++------ .../listener/resources/serviceaccount.go | 3 +- .../listener/resources/singleton.go | 11 +++++ 4 files changed, 91 insertions(+), 17 deletions(-) create mode 100644 sensor/kubernetes/listener/resources/registry_store.go diff --git a/sensor/kubernetes/listener/resources/registry_store.go b/sensor/kubernetes/listener/resources/registry_store.go new file mode 100644 index 0000000000000..3d362e0954fae --- /dev/null +++ b/sensor/kubernetes/listener/resources/registry_store.go @@ -0,0 +1,46 @@ +package resources + +import "github.com/stackrox/rox/pkg/sync" + +// RegistryStore stores cluster-internal registries by namespace. +type RegistryStore struct { + // store maps a namespace to the names of registries accessible from within the namespace. + // The registry maps to its credentials. + store map[string]map[string]dockerConfigEntry + + mutex sync.RWMutex +} + +// newRegistryStore creates a new registryStore. +func newRegistryStore() *RegistryStore { + return &RegistryStore{ + store: make(map[string]map[string]dockerConfigEntry), + } +} + +func (rs *RegistryStore) addOrUpdateRegistry(namespace, registry string, dce dockerConfigEntry) { + rs.mutex.Lock() + defer rs.mutex.Unlock() + + nsMap := rs.store[namespace] + if nsMap == nil { + nsMap = make(map[string]dockerConfigEntry) + rs.store[namespace] = nsMap + } + + nsMap[registry] = dce +} + +// getAllInNamespace returns all the registries+credentials within a given namespace. +func (rs *RegistryStore) getAllInNamespace(namespace string) map[string]dockerConfigEntry { + regs := make(map[string]dockerConfigEntry) + + rs.mutex.RLock() + rs.mutex.RUnlock() + + for reg, dce := range rs.store[namespace] { + regs[reg] = dce + } + + return regs +} diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index 2452b46081ead..b4315e3b75512 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -11,6 +11,7 @@ import ( "github.com/cloudflare/cfssl/certinfo" "github.com/stackrox/rox/generated/internalapi/central" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/protoconv" "github.com/stackrox/rox/pkg/registries/docker" "github.com/stackrox/rox/pkg/registries/rhel" @@ -20,7 +21,12 @@ import ( v1 "k8s.io/api/core/v1" ) -const redhatRegistryEndpoint = "registry.redhat.io" +const ( + redhatRegistryEndpoint = "registry.redhat.io" + + saAnnotation = "kubernetes.io/service-account.name" + defaultSA = "default" +) // The following types are copied from the Kubernetes codebase, // since it is not placed in any of the officially supported client @@ -187,11 +193,15 @@ func populateTypeData(secret *storage.Secret, dataFiles map[string][]byte) { } // secretDispatcher handles secret resource events. -type secretDispatcher struct{} +type secretDispatcher struct{ + regStore *RegistryStore +} // newSecretDispatcher creates and returns a new secret handler. -func newSecretDispatcher() *secretDispatcher { - return &secretDispatcher{} +func newSecretDispatcher(regStore *RegistryStore) *secretDispatcher { + return &secretDispatcher{ + regStore: regStore, + } } func dockerConfigToImageIntegration(registry string, dce dockerConfigEntry) *storage.ImageIntegration { @@ -200,11 +210,6 @@ func dockerConfigToImageIntegration(registry string, dce dockerConfigEntry) *sto registryType = rhel.RedHatRegistryType } - username, password := dce.Username, dce.Password - // TODO(ROX-8465): Determine which Service Account's token to use to replace the credentials. - // if features.LocalImageScanning.Enabled() { - // } - return &storage.ImageIntegration{ Id: uuid.NewV4().String(), Type: registryType, @@ -212,15 +217,15 @@ func dockerConfigToImageIntegration(registry string, dce dockerConfigEntry) *sto IntegrationConfig: &storage.ImageIntegration_Docker{ Docker: &storage.DockerConfig{ Endpoint: registry, - Username: username, - Password: password, + Username: dce.Username, + Password: dce.Password, }, }, Autogenerated: true, } } -func processDockerConfigEvent(secret *v1.Secret, action central.ResourceAction) []*central.SensorEvent { +func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action central.ResourceAction) []*central.SensorEvent { var dockerConfig dockerConfig switch secret.Type { case v1.SecretTypeDockercfg: @@ -248,9 +253,22 @@ func processDockerConfigEvent(secret *v1.Secret, action central.ResourceAction) return nil } + // The default service account comes with credentials to the internal registry. + // Check the service account annotation to see if this configuration is from the + // default SA. + var fromDefaultSA bool + if secret.GetAnnotations()[saAnnotation] == defaultSA { + fromDefaultSA = true + } + sensorEvents := make([]*central.SensorEvent, 0, len(dockerConfig)+1) registries := make([]*storage.ImagePullSecret_Registry, 0, len(dockerConfig)) for registry, dce := range dockerConfig { + if features.LocalImageScanning.Enabled() { + if fromDefaultSA { + s.regStore.addOrUpdateRegistry(secret.GetNamespace(), registry, dce) + } + } ii := dockerConfigToImageIntegration(registry, dce) sensorEvents = append(sensorEvents, ¢ral.SensorEvent{ // Only update is supported at this time. @@ -302,14 +320,14 @@ func secretToSensorEvent(action central.ResourceAction, secret *storage.Secret) } // ProcessEvent processes a secret resource event, and returns the sensor events to emit in response. -func (*secretDispatcher) ProcessEvent(obj, _ interface{}, action central.ResourceAction) []*central.SensorEvent { +func (s *secretDispatcher) ProcessEvent(obj, _ interface{}, action central.ResourceAction) []*central.SensorEvent { secret := obj.(*v1.Secret) switch secret.Type { case v1.SecretTypeDockerConfigJson, v1.SecretTypeDockercfg: - return processDockerConfigEvent(secret, action) + return s.processDockerConfigEvent(secret, action) case v1.SecretTypeServiceAccountToken: - // Filter out service account tokens because we have a service account field. + // Filter out service account tokens because we have a service account processor. return nil } diff --git a/sensor/kubernetes/listener/resources/serviceaccount.go b/sensor/kubernetes/listener/resources/serviceaccount.go index a0643eeb71f7f..b85da097d45ba 100644 --- a/sensor/kubernetes/listener/resources/serviceaccount.go +++ b/sensor/kubernetes/listener/resources/serviceaccount.go @@ -15,9 +15,8 @@ func newServiceAccountDispatcher() *serviceAccountDispatcher { return &serviceAccountDispatcher{} } -// Process processes a service account resource event, and returns the sensor events to emit in response. +// ProcessEvent processes a service account resource event, and returns the sensor events to emit in response. func (*serviceAccountDispatcher) ProcessEvent(obj, _ interface{}, action central.ResourceAction) []*central.SensorEvent { - serviceAccount := obj.(*v1.ServiceAccount) var serviceAccountSecrets []string diff --git a/sensor/kubernetes/listener/resources/singleton.go b/sensor/kubernetes/listener/resources/singleton.go index f04348a28e0f9..484069cb0e42c 100644 --- a/sensor/kubernetes/listener/resources/singleton.go +++ b/sensor/kubernetes/listener/resources/singleton.go @@ -8,6 +8,9 @@ var ( psInit sync.Once podStore *PodStore + + rsInit sync.Once + regStore *RegistryStore ) // DeploymentStoreSingleton returns a singleton of the DeploymentStore @@ -25,3 +28,11 @@ func PodStoreSingleton() *PodStore { }) return podStore } + +// RegistryStoreSingleton returns a singleton of the RegistryStore. +func RegistryStoreSingleton() *RegistryStore { + rsInit.Do(func() { + regStore = newRegistryStore() + }) + return regStore +} From aea8089d0708aaa1016a5af6fad66175a491b446 Mon Sep 17 00:00:00 2001 From: RTann Date: Mon, 17 Jan 2022 16:39:01 -0800 Subject: [PATCH 02/30] updates --- .../listener/resources/registry_store.go | 1 + sensor/kubernetes/listener/resources/secrets.go | 16 +++++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/sensor/kubernetes/listener/resources/registry_store.go b/sensor/kubernetes/listener/resources/registry_store.go index 3d362e0954fae..1b565193b7b3c 100644 --- a/sensor/kubernetes/listener/resources/registry_store.go +++ b/sensor/kubernetes/listener/resources/registry_store.go @@ -38,6 +38,7 @@ func (rs *RegistryStore) getAllInNamespace(namespace string) map[string]dockerCo rs.mutex.RLock() rs.mutex.RUnlock() + // Copy the registry to configuration map. for reg, dce := range rs.store[namespace] { regs[reg] = dce } diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index b4315e3b75512..dd5b852ea9fdc 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -249,23 +249,21 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce } dockerConfig = dockerConfigJSON.Auths default: - utils.Should(errors.New("only Docker Config secrets are allowed")) + _ = utils.Should(errors.New("only Docker Config secrets are allowed")) return nil } - // The default service account comes with credentials to the internal registry. - // Check the service account annotation to see if this configuration is from the - // default SA. - var fromDefaultSA bool - if secret.GetAnnotations()[saAnnotation] == defaultSA { - fromDefaultSA = true - } - sensorEvents := make([]*central.SensorEvent, 0, len(dockerConfig)+1) registries := make([]*storage.ImagePullSecret_Registry, 0, len(dockerConfig)) + // In Kubernetes, the `default` service account always exists in each namespace (it is recreated upon deletion). + // The default service account always contains an API token. + // In OpenShift, the default service account also contains credentials for the + // OpenShift Container Registry, which is an internal image registry. + fromDefaultSA := secret.GetAnnotations()[saAnnotation] == defaultSA for registry, dce := range dockerConfig { if features.LocalImageScanning.Enabled() { if fromDefaultSA { + // Store the registry credentials so Sensor can reach it. s.regStore.addOrUpdateRegistry(secret.GetNamespace(), registry, dce) } } From d0df329cbd2a47fd09f7be52dd081413d80ee1f3 Mon Sep 17 00:00:00 2001 From: RTann Date: Tue, 18 Jan 2022 09:22:09 -0800 Subject: [PATCH 03/30] simple test --- .../listener/resources/dispatcher.go | 3 +- .../listener/resources/registry_store_test.go | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 sensor/kubernetes/listener/resources/registry_store_test.go diff --git a/sensor/kubernetes/listener/resources/dispatcher.go b/sensor/kubernetes/listener/resources/dispatcher.go index 6ca07a0672f2b..092f5e32948c0 100644 --- a/sensor/kubernetes/listener/resources/dispatcher.go +++ b/sensor/kubernetes/listener/resources/dispatcher.go @@ -61,6 +61,7 @@ func NewDispatcherRegistry(clusterID string, podLister v1Listers.PodLister, prof endpointManager := newEndpointManager(serviceStore, deploymentStore, podStore, nodeStore, entityStore) rbacUpdater := rbac.NewStore() portExposureReconciler := newPortExposureReconciler(deploymentStore, serviceStore) + registryStore := newRegistryStore() return ®istryImpl{ deploymentHandler: newDeploymentHandler(clusterID, serviceStore, deploymentStore, podStore, endpointManager, nsStore, @@ -70,7 +71,7 @@ func NewDispatcherRegistry(clusterID string, podLister v1Listers.PodLister, prof namespaceDispatcher: newNamespaceDispatcher(nsStore, serviceStore, deploymentStore, podStore), serviceDispatcher: newServiceDispatcher(serviceStore, deploymentStore, endpointManager, portExposureReconciler), osRouteDispatcher: newRouteDispatcher(serviceStore, portExposureReconciler), - secretDispatcher: newSecretDispatcher(), + secretDispatcher: newSecretDispatcher(registryStore), networkPolicyDispatcher: newNetworkPolicyDispatcher(), nodeDispatcher: newNodeDispatcher(serviceStore, deploymentStore, nodeStore, endpointManager), serviceAccountDispatcher: newServiceAccountDispatcher(), diff --git a/sensor/kubernetes/listener/resources/registry_store_test.go b/sensor/kubernetes/listener/resources/registry_store_test.go new file mode 100644 index 0000000000000..3f72399d92382 --- /dev/null +++ b/sensor/kubernetes/listener/resources/registry_store_test.go @@ -0,0 +1,35 @@ +package resources + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRegistryStore(t *testing.T) { + rs := newRegistryStore() + rs.addOrUpdateRegistry("a", "reg1", dockerConfigEntry{ + Username: "test1", + Password: "test1pass", + Email: "test1@test.com", + }) + rs.addOrUpdateRegistry("a", "reg2", dockerConfigEntry{ + Username: "test2", + Password: "test2pass", + Email: "test2@test.com", + }) + rs.addOrUpdateRegistry("b", "reg3", dockerConfigEntry{ + Username: "test3", + Password: "test2pass", + Email: "test3@test.com", + }) + + regs := rs.getAllInNamespace("a") + assert.Len(t, regs, 2) + + regs = rs.getAllInNamespace("b") + assert.Len(t, regs, 1) + + regs = rs.getAllInNamespace("c") + assert.Empty(t, regs) +} From 8397df49eb6def304bfabf13585e961fe564b0c9 Mon Sep 17 00:00:00 2001 From: RTann Date: Tue, 18 Jan 2022 09:41:24 -0800 Subject: [PATCH 04/30] style --- sensor/kubernetes/listener/resources/secrets.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index dd5b852ea9fdc..7af54b9cffda6 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -193,7 +193,7 @@ func populateTypeData(secret *storage.Secret, dataFiles map[string][]byte) { } // secretDispatcher handles secret resource events. -type secretDispatcher struct{ +type secretDispatcher struct { regStore *RegistryStore } From 35633225a2916a7016c78562a5f1d44a020a2841 Mon Sep 17 00:00:00 2001 From: RTann Date: Tue, 18 Jan 2022 15:15:41 -0800 Subject: [PATCH 05/30] updates --- pkg/docker/types/config.go | 84 +++++++++++++++++++ .../listener/resources/registry_store.go | 17 ++-- .../listener/resources/registry_store_test.go | 7 +- .../kubernetes/listener/resources/secrets.go | 80 +----------------- 4 files changed, 102 insertions(+), 86 deletions(-) create mode 100644 pkg/docker/types/config.go diff --git a/pkg/docker/types/config.go b/pkg/docker/types/config.go new file mode 100644 index 0000000000000..cbb814e869130 --- /dev/null +++ b/pkg/docker/types/config.go @@ -0,0 +1,84 @@ +package types + +import ( + "encoding/base64" + "encoding/json" + "strings" + + "github.com/pkg/errors" +) + +// The following types are copied from the Kubernetes codebase, +// since it is not placed in any of the officially supported client +// libraries. + +// DockerConfigJSON represents ~/.docker/config.json file info +// see https://github.com/docker/docker/pull/12009. +type DockerConfigJSON struct { + Auths DockerConfig `json:"auths"` +} + +// DockerConfig represents the config file used by the docker CLI. +// This config that represents the credentials that should be used +// when pulling images from specific image repositories. +type DockerConfig map[string]DockerConfigEntry + +// DockerConfigEntry is an entry in the DockerConfig. +type DockerConfigEntry struct { + Username string + Password string + Email string +} + +// DockerConfigEntryWithAuth is used solely for deserializing the Auth field +// into a DockerConfigEntry during JSON deserialization. +type DockerConfigEntryWithAuth struct { + // +optional + Username string `json:"username,omitempty"` + // +optional + Password string `json:"password,omitempty"` + // +optional + Email string `json:"email,omitempty"` + // +optional + Auth string `json:"auth,omitempty"` +} + +// decodeDockerConfigFieldAuth deserializes the "auth" field from dockercfg into a +// username and a password. The format of the auth field is base64(:). +func decodeDockerConfigFieldAuth(field string) (username, password string, err error) { + decoded, err := base64.StdEncoding.DecodeString(field) + if err != nil { + return + } + + parts := strings.SplitN(string(decoded), ":", 2) + if len(parts) != 2 { + err = errors.New("unable to parse auth field") + return + } + + username = parts[0] + password = parts[1] + + return +} + +// UnmarshalJSON unmarshals the given JSON data into a *DockerConfigEntry. +func (d *DockerConfigEntry) UnmarshalJSON(data []byte) error { + var tmp DockerConfigEntryWithAuth + err := json.Unmarshal(data, &tmp) + if err != nil { + return err + } + + d.Username = tmp.Username + d.Password = tmp.Password + d.Email = tmp.Email + + if len(tmp.Auth) == 0 { + return nil + } + + d.Username, d.Password, err = decodeDockerConfigFieldAuth(tmp.Auth) + return err +} diff --git a/sensor/kubernetes/listener/resources/registry_store.go b/sensor/kubernetes/listener/resources/registry_store.go index 1b565193b7b3c..5d0baffad2032 100644 --- a/sensor/kubernetes/listener/resources/registry_store.go +++ b/sensor/kubernetes/listener/resources/registry_store.go @@ -1,12 +1,15 @@ package resources -import "github.com/stackrox/rox/pkg/sync" +import ( + "github.com/stackrox/rox/pkg/docker/types" + "github.com/stackrox/rox/pkg/sync" +) // RegistryStore stores cluster-internal registries by namespace. type RegistryStore struct { // store maps a namespace to the names of registries accessible from within the namespace. // The registry maps to its credentials. - store map[string]map[string]dockerConfigEntry + store map[string]map[string]types.DockerConfigEntry mutex sync.RWMutex } @@ -14,17 +17,17 @@ type RegistryStore struct { // newRegistryStore creates a new registryStore. func newRegistryStore() *RegistryStore { return &RegistryStore{ - store: make(map[string]map[string]dockerConfigEntry), + store: make(map[string]map[string]types.DockerConfigEntry), } } -func (rs *RegistryStore) addOrUpdateRegistry(namespace, registry string, dce dockerConfigEntry) { +func (rs *RegistryStore) addOrUpdateRegistry(namespace, registry string, dce types.DockerConfigEntry) { rs.mutex.Lock() defer rs.mutex.Unlock() nsMap := rs.store[namespace] if nsMap == nil { - nsMap = make(map[string]dockerConfigEntry) + nsMap = make(map[string]types.DockerConfigEntry) rs.store[namespace] = nsMap } @@ -32,8 +35,8 @@ func (rs *RegistryStore) addOrUpdateRegistry(namespace, registry string, dce doc } // getAllInNamespace returns all the registries+credentials within a given namespace. -func (rs *RegistryStore) getAllInNamespace(namespace string) map[string]dockerConfigEntry { - regs := make(map[string]dockerConfigEntry) +func (rs *RegistryStore) getAllInNamespace(namespace string) map[string]types.DockerConfigEntry { + regs := make(map[string]types.DockerConfigEntry) rs.mutex.RLock() rs.mutex.RUnlock() diff --git a/sensor/kubernetes/listener/resources/registry_store_test.go b/sensor/kubernetes/listener/resources/registry_store_test.go index 3f72399d92382..2de11c6e715c3 100644 --- a/sensor/kubernetes/listener/resources/registry_store_test.go +++ b/sensor/kubernetes/listener/resources/registry_store_test.go @@ -3,22 +3,23 @@ package resources import ( "testing" + "github.com/stackrox/rox/pkg/docker/types" "github.com/stretchr/testify/assert" ) func TestRegistryStore(t *testing.T) { rs := newRegistryStore() - rs.addOrUpdateRegistry("a", "reg1", dockerConfigEntry{ + rs.addOrUpdateRegistry("a", "reg1", types.DockerConfigEntry{ Username: "test1", Password: "test1pass", Email: "test1@test.com", }) - rs.addOrUpdateRegistry("a", "reg2", dockerConfigEntry{ + rs.addOrUpdateRegistry("a", "reg2", types.DockerConfigEntry{ Username: "test2", Password: "test2pass", Email: "test2@test.com", }) - rs.addOrUpdateRegistry("b", "reg3", dockerConfigEntry{ + rs.addOrUpdateRegistry("b", "reg3", types.DockerConfigEntry{ Username: "test3", Password: "test2pass", Email: "test3@test.com", diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index 7af54b9cffda6..7a5658463aa3a 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -11,6 +11,7 @@ import ( "github.com/cloudflare/cfssl/certinfo" "github.com/stackrox/rox/generated/internalapi/central" "github.com/stackrox/rox/generated/storage" + "github.com/stackrox/rox/pkg/docker/types" "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/protoconv" "github.com/stackrox/rox/pkg/registries/docker" @@ -28,79 +29,6 @@ const ( defaultSA = "default" ) -// The following types are copied from the Kubernetes codebase, -// since it is not placed in any of the officially supported client -// libraries. -// dockerConfigJSON represents ~/.docker/config.json file info -// see https://github.com/docker/docker/pull/12009 -type dockerConfigJSON struct { - Auths dockerConfig `json:"auths"` -} - -// dockerConfig represents the config file used by the docker CLI. -// This config that represents the credentials that should be used -// when pulling images from specific image repositories. -type dockerConfig map[string]dockerConfigEntry - -// dockerConfigEntry is an entry in the dockerConfig. -type dockerConfigEntry struct { - Username string - Password string - Email string -} - -// dockerConfigEntryWithAuth is used solely for deserializing the Auth field -// into a dockerConfigEntry during JSON deserialization. -type dockerConfigEntryWithAuth struct { - // +optional - Username string `json:"username,omitempty"` - // +optional - Password string `json:"password,omitempty"` - // +optional - Email string `json:"email,omitempty"` - // +optional - Auth string `json:"auth,omitempty"` -} - -// decodeDockerConfigFieldAuth deserializes the "auth" field from dockercfg into a -// username and a password. The format of the auth field is base64(:). -func decodeDockerConfigFieldAuth(field string) (username, password string, err error) { - decoded, err := base64.StdEncoding.DecodeString(field) - if err != nil { - return - } - - parts := strings.SplitN(string(decoded), ":", 2) - if len(parts) != 2 { - err = errors.New("unable to parse auth field") - return - } - - username = parts[0] - password = parts[1] - - return -} - -func (d *dockerConfigEntry) UnmarshalJSON(data []byte) error { - var tmp dockerConfigEntryWithAuth - err := json.Unmarshal(data, &tmp) - if err != nil { - return err - } - - d.Username = tmp.Username - d.Password = tmp.Password - d.Email = tmp.Email - - if len(tmp.Auth) == 0 { - return nil - } - - d.Username, d.Password, err = decodeDockerConfigFieldAuth(tmp.Auth) - return err -} - var dataTypeMap = map[string]storage.SecretType{ "-----BEGIN CERTIFICATE-----": storage.SecretType_PUBLIC_CERTIFICATE, "-----BEGIN NEW CERTIFICATE REQUEST-----": storage.SecretType_CERTIFICATE_REQUEST, @@ -204,7 +132,7 @@ func newSecretDispatcher(regStore *RegistryStore) *secretDispatcher { } } -func dockerConfigToImageIntegration(registry string, dce dockerConfigEntry) *storage.ImageIntegration { +func dockerConfigToImageIntegration(registry string, dce types.DockerConfigEntry) *storage.ImageIntegration { registryType := docker.GenericDockerRegistryType if urlfmt.TrimHTTPPrefixes(registry) == redhatRegistryEndpoint { registryType = rhel.RedHatRegistryType @@ -226,7 +154,7 @@ func dockerConfigToImageIntegration(registry string, dce dockerConfigEntry) *sto } func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action central.ResourceAction) []*central.SensorEvent { - var dockerConfig dockerConfig + var dockerConfig types.DockerConfig switch secret.Type { case v1.SecretTypeDockercfg: data, ok := secret.Data[v1.DockerConfigKey] @@ -242,7 +170,7 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce if !ok { return nil } - var dockerConfigJSON dockerConfigJSON + var dockerConfigJSON types.DockerConfigJSON if err := json.Unmarshal(data, &dockerConfigJSON); err != nil { log.Error(err) return nil From 196eb1c0e0e6977933aa7b06c953d336242f6406 Mon Sep 17 00:00:00 2001 From: RTann Date: Wed, 19 Jan 2022 12:27:48 -0800 Subject: [PATCH 06/30] for now --- pkg/registries/factory.go | 40 +++++++++++-------- pkg/registries/factory_options.go | 22 ++++++++++ .../listener/resources/registry_store.go | 33 +++++++++++---- 3 files changed, 72 insertions(+), 23 deletions(-) create mode 100644 pkg/registries/factory_options.go diff --git a/pkg/registries/factory.go b/pkg/registries/factory.go index 7e01362fd9501..fe0eb2988f170 100644 --- a/pkg/registries/factory.go +++ b/pkg/registries/factory.go @@ -28,27 +28,35 @@ type Factory interface { type creatorWrapper func() (string, func(integration *storage.ImageIntegration) (types.Registry, error)) +var allCreatorFuncs = []creatorWrapper{ + artifactRegistryFactory.Creator, + artifactoryFactory.Creator, + dockerFactory.Creator, + dtrFactory.Creator, + ecrFactory.Creator, + googleFactory.Creator, + quayFactory.Creator, + tenableFactory.Creator, + nexusFactory.Creator, + azureFactory.Creator, + rhelFactory.Creator, + ibmFactory.Creator, +} + // NewFactory creates a new scanner factory. -func NewFactory() Factory { +func NewFactory(opts ...FactoryOption) Factory { + var o factoryOption + for _, opt := range opts { + opt.apply(&o) + } + reg := &factoryImpl{ creators: make(map[string]Creator), } - // Add registries to factory. - ////////////////////////////// - creatorFuncs := []creatorWrapper{ - artifactRegistryFactory.Creator, - artifactoryFactory.Creator, - dockerFactory.Creator, - dtrFactory.Creator, - ecrFactory.Creator, - googleFactory.Creator, - quayFactory.Creator, - tenableFactory.Creator, - nexusFactory.Creator, - azureFactory.Creator, - rhelFactory.Creator, - ibmFactory.Creator, + creatorFuncs := allCreatorFuncs + if len(o.creatorFuncs) > 0 { + creatorFuncs = o.creatorFuncs } for _, creatorFunc := range creatorFuncs { diff --git a/pkg/registries/factory_options.go b/pkg/registries/factory_options.go new file mode 100644 index 0000000000000..63cc952aa6c8a --- /dev/null +++ b/pkg/registries/factory_options.go @@ -0,0 +1,22 @@ +package registries + +type factoryOption struct { + creatorFuncs []creatorWrapper +} + +type FactoryOption interface { + apply(*factoryOption) +} + +type factoryOptionFunc func(*factoryOption) + +func (o factoryOptionFunc) apply(opt *factoryOption) { + o(opt) +} + +// WithRegistryCreators specifies which registries to add to the factory. +func WithRegistryCreators(creatorFuncs ...creatorWrapper) FactoryOption { + return factoryOptionFunc(func(o *factoryOption) { + o.creatorFuncs = creatorFuncs + }) +} diff --git a/sensor/kubernetes/listener/resources/registry_store.go b/sensor/kubernetes/listener/resources/registry_store.go index 5d0baffad2032..206f223534302 100644 --- a/sensor/kubernetes/listener/resources/registry_store.go +++ b/sensor/kubernetes/listener/resources/registry_store.go @@ -1,15 +1,20 @@ package resources import ( + "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/docker/types" + "github.com/stackrox/rox/pkg/registries" + dockerFactory "github.com/stackrox/rox/pkg/registries/docker" "github.com/stackrox/rox/pkg/sync" + "github.com/stackrox/rox/pkg/tlscheck" ) // RegistryStore stores cluster-internal registries by namespace. type RegistryStore struct { + factory registries.Factory // store maps a namespace to the names of registries accessible from within the namespace. // The registry maps to its credentials. - store map[string]map[string]types.DockerConfigEntry + store map[string]registries.Set mutex sync.RWMutex } @@ -17,7 +22,8 @@ type RegistryStore struct { // newRegistryStore creates a new registryStore. func newRegistryStore() *RegistryStore { return &RegistryStore{ - store: make(map[string]map[string]types.DockerConfigEntry), + factory: registries.NewFactory(registries.WithRegistryCreators(dockerFactory.Creator)), + store: make(map[string]registries.Set), } } @@ -25,13 +31,26 @@ func (rs *RegistryStore) addOrUpdateRegistry(namespace, registry string, dce typ rs.mutex.Lock() defer rs.mutex.Unlock() - nsMap := rs.store[namespace] - if nsMap == nil { - nsMap = make(map[string]types.DockerConfigEntry) - rs.store[namespace] = nsMap + regs := rs.store[namespace] + if regs == nil { + regs = registries.NewSet(rs.factory) + rs.store[namespace] = regs } - nsMap[registry] = dce + tlscheck.CheckTLS(registry) + regs.UpdateImageIntegration(&storage.ImageIntegration{ + Name: registry, + Type: "docker", + Categories: []storage.ImageIntegrationCategory{storage.ImageIntegrationCategory_REGISTRY}, + IntegrationConfig: &storage.ImageIntegration_Docker{ + Docker: &storage.DockerConfig{ + Endpoint: registry, + Username: dce.Username, + Password: dce.Password, + Insecure: false, + }, + }, + }) } // getAllInNamespace returns all the registries+credentials within a given namespace. From 2d53f131ef90b0dc9ec42d8aa0fafd1d4881d07e Mon Sep 17 00:00:00 2001 From: RTann Date: Wed, 19 Jan 2022 13:20:54 -0800 Subject: [PATCH 07/30] updates --- .../listener/resources/registry_store.go | 39 ++++++++++++------- .../listener/resources/registry_store_test.go | 36 ----------------- .../kubernetes/listener/resources/secrets.go | 5 ++- 3 files changed, 29 insertions(+), 51 deletions(-) delete mode 100644 sensor/kubernetes/listener/resources/registry_store_test.go diff --git a/sensor/kubernetes/listener/resources/registry_store.go b/sensor/kubernetes/listener/resources/registry_store.go index 206f223534302..8f4343e778db8 100644 --- a/sensor/kubernetes/listener/resources/registry_store.go +++ b/sensor/kubernetes/listener/resources/registry_store.go @@ -1,6 +1,7 @@ package resources import ( + "github.com/pkg/errors" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/docker/types" "github.com/stackrox/rox/pkg/registries" @@ -10,6 +11,7 @@ import ( ) // RegistryStore stores cluster-internal registries by namespace. +// It is assumed all the registries are Docker registries. type RegistryStore struct { factory registries.Factory // store maps a namespace to the names of registries accessible from within the namespace. @@ -27,7 +29,7 @@ func newRegistryStore() *RegistryStore { } } -func (rs *RegistryStore) addOrUpdateRegistry(namespace, registry string, dce types.DockerConfigEntry) { +func (rs *RegistryStore) getRegistries(namespace string) registries.Set { rs.mutex.Lock() defer rs.mutex.Unlock() @@ -37,8 +39,19 @@ func (rs *RegistryStore) addOrUpdateRegistry(namespace, registry string, dce typ rs.store[namespace] = regs } - tlscheck.CheckTLS(registry) - regs.UpdateImageIntegration(&storage.ImageIntegration{ + return regs +} + +// upsertRegistry upserts the given registry with the given credentials in the given namespace into the store. +func (rs *RegistryStore) upsertRegistry(namespace, registry string, dce types.DockerConfigEntry) error { + regs := rs.getRegistries(namespace) + + secure, err := tlscheck.CheckTLS(registry) + if err != nil { + return errors.Wrapf(err, "unable to check TLS for registry %q", registry) + } + + err = regs.UpdateImageIntegration(&storage.ImageIntegration{ Name: registry, Type: "docker", Categories: []storage.ImageIntegrationCategory{storage.ImageIntegrationCategory_REGISTRY}, @@ -47,23 +60,21 @@ func (rs *RegistryStore) addOrUpdateRegistry(namespace, registry string, dce typ Endpoint: registry, Username: dce.Username, Password: dce.Password, - Insecure: false, + Insecure: !secure, }, }, }) + if err != nil { + return errors.Wrapf(err, "updating registry store with registry %q", registry) + } + + return nil } // getAllInNamespace returns all the registries+credentials within a given namespace. -func (rs *RegistryStore) getAllInNamespace(namespace string) map[string]types.DockerConfigEntry { - regs := make(map[string]types.DockerConfigEntry) - +func (rs *RegistryStore) getAllInNamespace(namespace string) registries.Set { rs.mutex.RLock() - rs.mutex.RUnlock() + defer rs.mutex.RUnlock() - // Copy the registry to configuration map. - for reg, dce := range rs.store[namespace] { - regs[reg] = dce - } - - return regs + return rs.store[namespace] } diff --git a/sensor/kubernetes/listener/resources/registry_store_test.go b/sensor/kubernetes/listener/resources/registry_store_test.go deleted file mode 100644 index 2de11c6e715c3..0000000000000 --- a/sensor/kubernetes/listener/resources/registry_store_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package resources - -import ( - "testing" - - "github.com/stackrox/rox/pkg/docker/types" - "github.com/stretchr/testify/assert" -) - -func TestRegistryStore(t *testing.T) { - rs := newRegistryStore() - rs.addOrUpdateRegistry("a", "reg1", types.DockerConfigEntry{ - Username: "test1", - Password: "test1pass", - Email: "test1@test.com", - }) - rs.addOrUpdateRegistry("a", "reg2", types.DockerConfigEntry{ - Username: "test2", - Password: "test2pass", - Email: "test2@test.com", - }) - rs.addOrUpdateRegistry("b", "reg3", types.DockerConfigEntry{ - Username: "test3", - Password: "test2pass", - Email: "test3@test.com", - }) - - regs := rs.getAllInNamespace("a") - assert.Len(t, regs, 2) - - regs = rs.getAllInNamespace("b") - assert.Len(t, regs, 1) - - regs = rs.getAllInNamespace("c") - assert.Empty(t, regs) -} diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index 7a5658463aa3a..0c1ac2465f675 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -192,7 +192,10 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce if features.LocalImageScanning.Enabled() { if fromDefaultSA { // Store the registry credentials so Sensor can reach it. - s.regStore.addOrUpdateRegistry(secret.GetNamespace(), registry, dce) + err := s.regStore.upsertRegistry(secret.GetNamespace(), registry, dce) + if err != nil { + log.Errorf("Unable to upsert registry %q into store: %v", registry, err) + } } } ii := dockerConfigToImageIntegration(registry, dce) From aa30729593e1bccdb12fcbc573afa29531ca4016 Mon Sep 17 00:00:00 2001 From: RTann Date: Mon, 24 Jan 2022 11:30:10 -0800 Subject: [PATCH 08/30] update factory opts --- pkg/registries/factory_options.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/registries/factory_options.go b/pkg/registries/factory_options.go index 63cc952aa6c8a..78456b215b07c 100644 --- a/pkg/registries/factory_options.go +++ b/pkg/registries/factory_options.go @@ -10,13 +10,13 @@ type FactoryOption interface { type factoryOptionFunc func(*factoryOption) -func (o factoryOptionFunc) apply(opt *factoryOption) { - o(opt) +func (f factoryOptionFunc) apply(opt *factoryOption) { + f(opt) } // WithRegistryCreators specifies which registries to add to the factory. func WithRegistryCreators(creatorFuncs ...creatorWrapper) FactoryOption { return factoryOptionFunc(func(o *factoryOption) { - o.creatorFuncs = creatorFuncs + o.creatorFuncs = append(o.creatorFuncs, creatorFuncs...) }) } From 2b53677341ffe4c323d88c1f95072f420e5011e6 Mon Sep 17 00:00:00 2001 From: RTann Date: Mon, 24 Jan 2022 13:40:44 -0800 Subject: [PATCH 09/30] debug --- sensor/kubernetes/listener/resources/registry_store.go | 2 ++ sensor/kubernetes/listener/resources/secrets.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/sensor/kubernetes/listener/resources/registry_store.go b/sensor/kubernetes/listener/resources/registry_store.go index 8f4343e778db8..739f977b1011a 100644 --- a/sensor/kubernetes/listener/resources/registry_store.go +++ b/sensor/kubernetes/listener/resources/registry_store.go @@ -66,6 +66,8 @@ func (rs *RegistryStore) upsertRegistry(namespace, registry string, dce types.Do }) if err != nil { return errors.Wrapf(err, "updating registry store with registry %q", registry) + } else { + log.Infof("Updated registry store with %q (Secure: %v)", registry, secure) } return nil diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index 0c1ac2465f675..17ee5051a1bb3 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -195,6 +195,8 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce err := s.regStore.upsertRegistry(secret.GetNamespace(), registry, dce) if err != nil { log.Errorf("Unable to upsert registry %q into store: %v", registry, err) + } else { + log.Info("Upserted registry %q", registry) } } } From 93550ab80b90b63994b408c022a347b97b98b531 Mon Sep 17 00:00:00 2001 From: RTann Date: Mon, 24 Jan 2022 13:58:05 -0800 Subject: [PATCH 10/30] style --- pkg/registries/factory_options.go | 1 + .../listener/resources/registry_store.go | 20 +++++++++---------- .../kubernetes/listener/resources/secrets.go | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pkg/registries/factory_options.go b/pkg/registries/factory_options.go index 78456b215b07c..259d9d676b81c 100644 --- a/pkg/registries/factory_options.go +++ b/pkg/registries/factory_options.go @@ -4,6 +4,7 @@ type factoryOption struct { creatorFuncs []creatorWrapper } +// FactoryOption specifies optional configuration parameters for a registry factory. type FactoryOption interface { apply(*factoryOption) } diff --git a/sensor/kubernetes/listener/resources/registry_store.go b/sensor/kubernetes/listener/resources/registry_store.go index 739f977b1011a..b705cdadb6ba4 100644 --- a/sensor/kubernetes/listener/resources/registry_store.go +++ b/sensor/kubernetes/listener/resources/registry_store.go @@ -52,24 +52,24 @@ func (rs *RegistryStore) upsertRegistry(namespace, registry string, dce types.Do } err = regs.UpdateImageIntegration(&storage.ImageIntegration{ - Name: registry, - Type: "docker", - Categories: []storage.ImageIntegrationCategory{storage.ImageIntegrationCategory_REGISTRY}, - IntegrationConfig: &storage.ImageIntegration_Docker{ + Name: registry, + Type: "docker", + Categories: []storage.ImageIntegrationCategory{storage.ImageIntegrationCategory_REGISTRY}, + IntegrationConfig: &storage.ImageIntegration_Docker{ Docker: &storage.DockerConfig{ - Endpoint: registry, - Username: dce.Username, - Password: dce.Password, - Insecure: !secure, + Endpoint: registry, + Username: dce.Username, + Password: dce.Password, + Insecure: !secure, }, }, }) if err != nil { return errors.Wrapf(err, "updating registry store with registry %q", registry) - } else { - log.Infof("Updated registry store with %q (Secure: %v)", registry, secure) } + log.Infof("Updated registry store with %q (Secure: %v)", registry, secure) + return nil } diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index 17ee5051a1bb3..decb8b2be4f4e 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -196,7 +196,7 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce if err != nil { log.Errorf("Unable to upsert registry %q into store: %v", registry, err) } else { - log.Info("Upserted registry %q", registry) + log.Infof("Upserted registry %q", registry) } } } From 325beeb9b48d6e6e25f13f20c030d7461f94608c Mon Sep 17 00:00:00 2001 From: RTann Date: Tue, 25 Jan 2022 11:31:03 -0800 Subject: [PATCH 11/30] update --- sensor/kubernetes/listener/resources/dispatcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensor/kubernetes/listener/resources/dispatcher.go b/sensor/kubernetes/listener/resources/dispatcher.go index 092f5e32948c0..85544a9e5f606 100644 --- a/sensor/kubernetes/listener/resources/dispatcher.go +++ b/sensor/kubernetes/listener/resources/dispatcher.go @@ -61,7 +61,7 @@ func NewDispatcherRegistry(clusterID string, podLister v1Listers.PodLister, prof endpointManager := newEndpointManager(serviceStore, deploymentStore, podStore, nodeStore, entityStore) rbacUpdater := rbac.NewStore() portExposureReconciler := newPortExposureReconciler(deploymentStore, serviceStore) - registryStore := newRegistryStore() + registryStore := RegistryStoreSingleton() return ®istryImpl{ deploymentHandler: newDeploymentHandler(clusterID, serviceStore, deploymentStore, podStore, endpointManager, nsStore, From b12da60afc043e6eb39641829725e6f0c60437a2 Mon Sep 17 00:00:00 2001 From: RTann Date: Tue, 25 Jan 2022 14:25:06 -0800 Subject: [PATCH 12/30] add more logs --- sensor/kubernetes/listener/resources/secrets.go | 1 + 1 file changed, 1 insertion(+) diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index decb8b2be4f4e..22792fdca5e83 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -188,6 +188,7 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce // In OpenShift, the default service account also contains credentials for the // OpenShift Container Registry, which is an internal image registry. fromDefaultSA := secret.GetAnnotations()[saAnnotation] == defaultSA + log.Infof("Secret %s:%s from default SA? %v", secret.GetName(), secret.GetNamespace(), secret.GetAnnotations()[saAnnotation] == defaultSA) for registry, dce := range dockerConfig { if features.LocalImageScanning.Enabled() { if fromDefaultSA { From 1d6baf9eae4e4b9570db4b0170b383d92f5820a7 Mon Sep 17 00:00:00 2001 From: RTann Date: Tue, 25 Jan 2022 15:14:37 -0800 Subject: [PATCH 13/30] remove log --- sensor/kubernetes/listener/resources/secrets.go | 1 - 1 file changed, 1 deletion(-) diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index 22792fdca5e83..decb8b2be4f4e 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -188,7 +188,6 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce // In OpenShift, the default service account also contains credentials for the // OpenShift Container Registry, which is an internal image registry. fromDefaultSA := secret.GetAnnotations()[saAnnotation] == defaultSA - log.Infof("Secret %s:%s from default SA? %v", secret.GetName(), secret.GetNamespace(), secret.GetAnnotations()[saAnnotation] == defaultSA) for registry, dce := range dockerConfig { if features.LocalImageScanning.Enabled() { if fromDefaultSA { From e6b48cd508b77fd87be669668fc6f6b8e22f9586 Mon Sep 17 00:00:00 2001 From: RTann Date: Tue, 25 Jan 2022 15:28:41 -0800 Subject: [PATCH 14/30] remove logs --- sensor/kubernetes/listener/resources/registry_store.go | 4 +--- sensor/kubernetes/listener/resources/secrets.go | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/sensor/kubernetes/listener/resources/registry_store.go b/sensor/kubernetes/listener/resources/registry_store.go index b705cdadb6ba4..7f1d5ddabc779 100644 --- a/sensor/kubernetes/listener/resources/registry_store.go +++ b/sensor/kubernetes/listener/resources/registry_store.go @@ -68,12 +68,10 @@ func (rs *RegistryStore) upsertRegistry(namespace, registry string, dce types.Do return errors.Wrapf(err, "updating registry store with registry %q", registry) } - log.Infof("Updated registry store with %q (Secure: %v)", registry, secure) - return nil } -// getAllInNamespace returns all the registries+credentials within a given namespace. +// getAllInNamespace returns all the registries within a given namespace. func (rs *RegistryStore) getAllInNamespace(namespace string) registries.Set { rs.mutex.RLock() defer rs.mutex.RUnlock() diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index decb8b2be4f4e..0c1ac2465f675 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -195,8 +195,6 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce err := s.regStore.upsertRegistry(secret.GetNamespace(), registry, dce) if err != nil { log.Errorf("Unable to upsert registry %q into store: %v", registry, err) - } else { - log.Infof("Upserted registry %q", registry) } } } From 9733eeb59df55e278720bc5a90b36d24daaab57a Mon Sep 17 00:00:00 2001 From: RTann Date: Wed, 26 Jan 2022 09:09:31 -0800 Subject: [PATCH 15/30] updates --- .../registry}/registry_store.go | 20 +++++++++---------- sensor/common/registry/singleton.go | 16 +++++++++++++++ .../listener/resources/dispatcher.go | 3 ++- .../kubernetes/listener/resources/secrets.go | 7 ++++--- .../listener/resources/singleton.go | 11 ---------- 5 files changed, 32 insertions(+), 25 deletions(-) rename sensor/{kubernetes/listener/resources => common/registry}/registry_store.go (76%) create mode 100644 sensor/common/registry/singleton.go diff --git a/sensor/kubernetes/listener/resources/registry_store.go b/sensor/common/registry/registry_store.go similarity index 76% rename from sensor/kubernetes/listener/resources/registry_store.go rename to sensor/common/registry/registry_store.go index 7f1d5ddabc779..6a3442c43de98 100644 --- a/sensor/kubernetes/listener/resources/registry_store.go +++ b/sensor/common/registry/registry_store.go @@ -1,4 +1,4 @@ -package resources +package registry import ( "github.com/pkg/errors" @@ -10,9 +10,9 @@ import ( "github.com/stackrox/rox/pkg/tlscheck" ) -// RegistryStore stores cluster-internal registries by namespace. +// Store stores cluster-internal registries by namespace. // It is assumed all the registries are Docker registries. -type RegistryStore struct { +type Store struct { factory registries.Factory // store maps a namespace to the names of registries accessible from within the namespace. // The registry maps to its credentials. @@ -22,14 +22,14 @@ type RegistryStore struct { } // newRegistryStore creates a new registryStore. -func newRegistryStore() *RegistryStore { - return &RegistryStore{ +func newRegistryStore() *Store { + return &Store{ factory: registries.NewFactory(registries.WithRegistryCreators(dockerFactory.Creator)), store: make(map[string]registries.Set), } } -func (rs *RegistryStore) getRegistries(namespace string) registries.Set { +func (rs *Store) getRegistries(namespace string) registries.Set { rs.mutex.Lock() defer rs.mutex.Unlock() @@ -42,8 +42,8 @@ func (rs *RegistryStore) getRegistries(namespace string) registries.Set { return regs } -// upsertRegistry upserts the given registry with the given credentials in the given namespace into the store. -func (rs *RegistryStore) upsertRegistry(namespace, registry string, dce types.DockerConfigEntry) error { +// UpsertRegistry upserts the given registry with the given credentials in the given namespace into the store. +func (rs *Store) UpsertRegistry(namespace, registry string, dce types.DockerConfigEntry) error { regs := rs.getRegistries(namespace) secure, err := tlscheck.CheckTLS(registry) @@ -71,8 +71,8 @@ func (rs *RegistryStore) upsertRegistry(namespace, registry string, dce types.Do return nil } -// getAllInNamespace returns all the registries within a given namespace. -func (rs *RegistryStore) getAllInNamespace(namespace string) registries.Set { +// GetAllInNamespace returns all the registries within a given namespace. +func (rs *Store) GetAllInNamespace(namespace string) registries.Set { rs.mutex.RLock() defer rs.mutex.RUnlock() diff --git a/sensor/common/registry/singleton.go b/sensor/common/registry/singleton.go new file mode 100644 index 0000000000000..402eb291f447c --- /dev/null +++ b/sensor/common/registry/singleton.go @@ -0,0 +1,16 @@ +package registry + +import "github.com/stackrox/rox/pkg/sync" + +var ( + once sync.Once + rStore *Store +) + +// Singleton returns a singleton of the registry storage. +func Singleton() *Store { + once.Do(func() { + rStore = newRegistryStore() + }) + return rStore +} diff --git a/sensor/kubernetes/listener/resources/dispatcher.go b/sensor/kubernetes/listener/resources/dispatcher.go index 85544a9e5f606..717c7d114a460 100644 --- a/sensor/kubernetes/listener/resources/dispatcher.go +++ b/sensor/kubernetes/listener/resources/dispatcher.go @@ -12,6 +12,7 @@ import ( "github.com/stackrox/rox/sensor/common/config" "github.com/stackrox/rox/sensor/common/detector" "github.com/stackrox/rox/sensor/common/metrics" + "github.com/stackrox/rox/sensor/common/registry" complianceOperatorDispatchers "github.com/stackrox/rox/sensor/kubernetes/listener/resources/complianceoperator/dispatchers" "github.com/stackrox/rox/sensor/kubernetes/listener/resources/rbac" "github.com/stackrox/rox/sensor/kubernetes/orchestratornamespaces" @@ -61,7 +62,7 @@ func NewDispatcherRegistry(clusterID string, podLister v1Listers.PodLister, prof endpointManager := newEndpointManager(serviceStore, deploymentStore, podStore, nodeStore, entityStore) rbacUpdater := rbac.NewStore() portExposureReconciler := newPortExposureReconciler(deploymentStore, serviceStore) - registryStore := RegistryStoreSingleton() + registryStore := registry.Singleton() return ®istryImpl{ deploymentHandler: newDeploymentHandler(clusterID, serviceStore, deploymentStore, podStore, endpointManager, nsStore, diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index 0c1ac2465f675..4b61626c13408 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -19,6 +19,7 @@ import ( "github.com/stackrox/rox/pkg/urlfmt" "github.com/stackrox/rox/pkg/utils" "github.com/stackrox/rox/pkg/uuid" + "github.com/stackrox/rox/sensor/common/registry" v1 "k8s.io/api/core/v1" ) @@ -122,11 +123,11 @@ func populateTypeData(secret *storage.Secret, dataFiles map[string][]byte) { // secretDispatcher handles secret resource events. type secretDispatcher struct { - regStore *RegistryStore + regStore *registry.Store } // newSecretDispatcher creates and returns a new secret handler. -func newSecretDispatcher(regStore *RegistryStore) *secretDispatcher { +func newSecretDispatcher(regStore *registry.Store) *secretDispatcher { return &secretDispatcher{ regStore: regStore, } @@ -192,7 +193,7 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce if features.LocalImageScanning.Enabled() { if fromDefaultSA { // Store the registry credentials so Sensor can reach it. - err := s.regStore.upsertRegistry(secret.GetNamespace(), registry, dce) + err := s.regStore.UpsertRegistry(secret.GetNamespace(), registry, dce) if err != nil { log.Errorf("Unable to upsert registry %q into store: %v", registry, err) } diff --git a/sensor/kubernetes/listener/resources/singleton.go b/sensor/kubernetes/listener/resources/singleton.go index 484069cb0e42c..f04348a28e0f9 100644 --- a/sensor/kubernetes/listener/resources/singleton.go +++ b/sensor/kubernetes/listener/resources/singleton.go @@ -8,9 +8,6 @@ var ( psInit sync.Once podStore *PodStore - - rsInit sync.Once - regStore *RegistryStore ) // DeploymentStoreSingleton returns a singleton of the DeploymentStore @@ -28,11 +25,3 @@ func PodStoreSingleton() *PodStore { }) return podStore } - -// RegistryStoreSingleton returns a singleton of the RegistryStore. -func RegistryStoreSingleton() *RegistryStore { - rsInit.Do(func() { - regStore = newRegistryStore() - }) - return regStore -} From bf2c6bf9bf920b6e4913fe513e44afd376491dd6 Mon Sep 17 00:00:00 2001 From: RTann Date: Wed, 2 Feb 2022 12:24:14 -0800 Subject: [PATCH 16/30] update error --- pkg/images/integration/set_impl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/images/integration/set_impl.go b/pkg/images/integration/set_impl.go index 48fac9d7a57f9..29fab83639ac6 100644 --- a/pkg/images/integration/set_impl.go +++ b/pkg/images/integration/set_impl.go @@ -64,7 +64,7 @@ func (e *setImpl) UpdateImageIntegration(integration *storage.ImageIntegration) err = e.scannerSet.UpdateImageIntegration(integration) case storage.ImageIntegrationCategory_NODE_SCANNER: // This is because node scanners are implemented into image integrations default: - err = fmt.Errorf("Source category '%s' has not been implemented", category) + err = fmt.Errorf("source category %q has not been implemented", category) } } From 63588ab9da66805ad7ad4c91d99182e8d6e50638 Mon Sep 17 00:00:00 2001 From: RTann Date: Wed, 2 Feb 2022 14:48:09 -0800 Subject: [PATCH 17/30] unit test --- sensor/common/registry/registry_store.go | 25 +++- .../listener/resources/secrets_test.go | 122 ++++++++++++++++++ 2 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 sensor/kubernetes/listener/resources/secrets_test.go diff --git a/sensor/common/registry/registry_store.go b/sensor/common/registry/registry_store.go index 6a3442c43de98..7ba7af492a12f 100644 --- a/sensor/common/registry/registry_store.go +++ b/sensor/common/registry/registry_store.go @@ -19,9 +19,11 @@ type Store struct { store map[string]registries.Set mutex sync.RWMutex + + test bool } -// newRegistryStore creates a new registryStore. +// newRegistryStore creates a new registry store. func newRegistryStore() *Store { return &Store{ factory: registries.NewFactory(registries.WithRegistryCreators(dockerFactory.Creator)), @@ -29,6 +31,15 @@ func newRegistryStore() *Store { } } +// NewTestRegistryStore creates a new registry store for testing purposes. +// The main difference between this and a non-test registry store +// is that this one does not attempt to reach out to the registry to check TLS. +func NewTestRegistryStore() *Store { + rs := newRegistryStore() + rs.test = true + return rs +} + func (rs *Store) getRegistries(namespace string) registries.Set { rs.mutex.Lock() defer rs.mutex.Unlock() @@ -46,12 +57,16 @@ func (rs *Store) getRegistries(namespace string) registries.Set { func (rs *Store) UpsertRegistry(namespace, registry string, dce types.DockerConfigEntry) error { regs := rs.getRegistries(namespace) - secure, err := tlscheck.CheckTLS(registry) - if err != nil { - return errors.Wrapf(err, "unable to check TLS for registry %q", registry) + var secure bool + if !rs.test { + var err error + secure, err = tlscheck.CheckTLS(registry) + if err != nil { + return errors.Wrapf(err, "unable to check TLS for registry %q", registry) + } } - err = regs.UpdateImageIntegration(&storage.ImageIntegration{ + err := regs.UpdateImageIntegration(&storage.ImageIntegration{ Name: registry, Type: "docker", Categories: []storage.ImageIntegrationCategory{storage.ImageIntegrationCategory_REGISTRY}, diff --git a/sensor/kubernetes/listener/resources/secrets_test.go b/sensor/kubernetes/listener/resources/secrets_test.go new file mode 100644 index 0000000000000..87b69ec795def --- /dev/null +++ b/sensor/kubernetes/listener/resources/secrets_test.go @@ -0,0 +1,122 @@ +package resources + +import ( + "testing" + + "github.com/stackrox/rox/generated/internalapi/central" + "github.com/stackrox/rox/pkg/features" + "github.com/stackrox/rox/pkg/registries/types" + "github.com/stackrox/rox/pkg/testutils/envisolator" + "github.com/stackrox/rox/sensor/common/registry" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ( + openshift311DockerConfigSecret = &v1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "default-dockercfg-6167c", + Namespace: "test-ns", + Annotations: map[string]string{ + "kubernetes.io/service-account.name": "default", + }, + }, + Data: map[string][]byte{ + ".dockercfg": []byte(` +{ + "docker-registry.default.svc.cluster.local:5000": { + "username": "serviceaccount", + "password": "password", + "email": "serviceaccount@example.org" + } +}`), + }, + Type: "kubernetes.io/dockercfg", + } + + openshift4xDockerConfigSecret = &v1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "default-dockercfg-9w5gn", + Namespace: "test-ns", + Annotations: map[string]string{ + "kubernetes.io/service-account.name": "default", + }, + }, + Data: map[string][]byte{ + ".dockercfg": []byte(` +{ + "image-registry.openshift-image-registry.svc:5000": { + "username": "serviceaccount", + "password": "password", + "email": "serviceaccount@example.org" + } +}`), + }, + Type: "kubernetes.io/dockercfg", + } +) + +func TestOpenShiftRegistrySecret_311(t *testing.T) { + envIsolator := envisolator.NewEnvIsolator(t) + envIsolator.Setenv(features.LocalImageScanning.EnvVar(), "true") + defer envIsolator.RestoreAll() + + regStore := registry.NewTestRegistryStore() + d := newSecretDispatcher(regStore) + + _ = d.ProcessEvent(openshift311DockerConfigSecret, nil, central.ResourceAction_CREATE_RESOURCE) + + assert.Nil(t, regStore.GetAllInNamespace("random-ns")) + + regs := regStore.GetAllInNamespace(openshift311DockerConfigSecret.GetNamespace()) + assert.NotNil(t, regs) + assert.Len(t, regs.GetAll(), 1) + + expectedRegConfig := &types.Config{ + Username: "serviceaccount", + Password: "password", + Insecure: true, + URL: "https://docker-registry.default.svc.cluster.local:5000", + RegistryHostname: "docker-registry.default.svc.cluster.local:5000", + Autogenerated: false, + } + + assert.Equal(t, expectedRegConfig, regs.GetAll()[0].Config()) +} + +func TestOpenShiftRegistrySecret_4x(t *testing.T) { + envIsolator := envisolator.NewEnvIsolator(t) + envIsolator.Setenv(features.LocalImageScanning.EnvVar(), "true") + defer envIsolator.RestoreAll() + + regStore := registry.NewTestRegistryStore() + d := newSecretDispatcher(regStore) + + _ = d.ProcessEvent(openshift4xDockerConfigSecret, nil, central.ResourceAction_CREATE_RESOURCE) + + assert.Nil(t, regStore.GetAllInNamespace("random-ns")) + + regs := regStore.GetAllInNamespace(openshift4xDockerConfigSecret.GetNamespace()) + assert.NotNil(t, regs) + assert.Len(t, regs.GetAll(), 1) + + expectedRegConfig := &types.Config{ + Username: "serviceaccount", + Password: "password", + Insecure: true, + URL: "https://image-registry.openshift-image-registry.svc:5000", + RegistryHostname: "image-registry.openshift-image-registry.svc:5000", + Autogenerated: false, + } + + assert.Equal(t, expectedRegConfig, regs.GetAll()[0].Config()) +} From a3c78c0b6fa4903797a73ab22de4c6a273c9d102 Mon Sep 17 00:00:00 2001 From: RTann Date: Wed, 2 Feb 2022 16:10:33 -0800 Subject: [PATCH 18/30] update unit tests --- .../kubernetes/listener/resources/secrets_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sensor/kubernetes/listener/resources/secrets_test.go b/sensor/kubernetes/listener/resources/secrets_test.go index 87b69ec795def..cfa85d36bb79e 100644 --- a/sensor/kubernetes/listener/resources/secrets_test.go +++ b/sensor/kubernetes/listener/resources/secrets_test.go @@ -6,7 +6,7 @@ import ( "github.com/stackrox/rox/generated/internalapi/central" "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/registries/types" - "github.com/stackrox/rox/pkg/testutils/envisolator" + "github.com/stackrox/rox/pkg/testutils" "github.com/stackrox/rox/sensor/common/registry" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" @@ -66,10 +66,10 @@ var ( ) func TestOpenShiftRegistrySecret_311(t *testing.T) { - envIsolator := envisolator.NewEnvIsolator(t) - envIsolator.Setenv(features.LocalImageScanning.EnvVar(), "true") - defer envIsolator.RestoreAll() + testutils.RunWithFeatureFlagEnabled(t, features.LocalImageScanning, testOpenShiftRegistrySecret311) +} +func testOpenShiftRegistrySecret311(t *testing.T) { regStore := registry.NewTestRegistryStore() d := newSecretDispatcher(regStore) @@ -94,10 +94,10 @@ func TestOpenShiftRegistrySecret_311(t *testing.T) { } func TestOpenShiftRegistrySecret_4x(t *testing.T) { - envIsolator := envisolator.NewEnvIsolator(t) - envIsolator.Setenv(features.LocalImageScanning.EnvVar(), "true") - defer envIsolator.RestoreAll() + testutils.RunWithFeatureFlagEnabled(t, features.LocalImageScanning, testOpenShiftRegistrySecret4x) +} +func testOpenShiftRegistrySecret4x(t *testing.T) { regStore := registry.NewTestRegistryStore() d := newSecretDispatcher(regStore) From 3c1e2a7a43a35b08d9b13e5bede912aaf8517f27 Mon Sep 17 00:00:00 2001 From: RTann Date: Wed, 2 Feb 2022 16:12:53 -0800 Subject: [PATCH 19/30] comments --- sensor/common/registry/registry_store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensor/common/registry/registry_store.go b/sensor/common/registry/registry_store.go index 7ba7af492a12f..ce99785f73b1c 100644 --- a/sensor/common/registry/registry_store.go +++ b/sensor/common/registry/registry_store.go @@ -15,11 +15,11 @@ import ( type Store struct { factory registries.Factory // store maps a namespace to the names of registries accessible from within the namespace. - // The registry maps to its credentials. store map[string]registries.Set mutex sync.RWMutex + // test indicates if this is a test store or not. test bool } From a21592d672935e1390a7d473808b960946ae5b7b Mon Sep 17 00:00:00 2001 From: RTann Date: Wed, 2 Feb 2022 17:50:00 -0800 Subject: [PATCH 20/30] add debug log --- sensor/common/registry/registry_store.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sensor/common/registry/registry_store.go b/sensor/common/registry/registry_store.go index ce99785f73b1c..a174348d60c3c 100644 --- a/sensor/common/registry/registry_store.go +++ b/sensor/common/registry/registry_store.go @@ -4,12 +4,17 @@ import ( "github.com/pkg/errors" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/docker/types" + "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/registries" dockerFactory "github.com/stackrox/rox/pkg/registries/docker" "github.com/stackrox/rox/pkg/sync" "github.com/stackrox/rox/pkg/tlscheck" ) +var ( + log = logging.LoggerForModule() +) + // Store stores cluster-internal registries by namespace. // It is assumed all the registries are Docker registries. type Store struct { @@ -83,6 +88,8 @@ func (rs *Store) UpsertRegistry(namespace, registry string, dce types.DockerConf return errors.Wrapf(err, "updating registry store with registry %q", registry) } + log.Debugf("Upserted registry %q into store", registry) + return nil } From 7f8290af980cc34bce9635e5077537689db8995e Mon Sep 17 00:00:00 2001 From: RTann Date: Thu, 3 Feb 2022 12:23:29 -0800 Subject: [PATCH 21/30] remove annoying log --- sensor/kubernetes/listener/resources/deployments.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/sensor/kubernetes/listener/resources/deployments.go b/sensor/kubernetes/listener/resources/deployments.go index 28f227ae6a131..08ef8d24df229 100644 --- a/sensor/kubernetes/listener/resources/deployments.go +++ b/sensor/kubernetes/listener/resources/deployments.go @@ -249,8 +249,6 @@ func (d *deploymentHandler) processPodEvent(owningDeploymentID string, k8sPod *v d.podStore.addOrUpdatePod(p) d.processFilter.UpdateByGivenContainers(p.DeploymentId, d.podStore.getContainersForDeployment(p.Namespace, p.DeploymentId)) - log.Debugf("Action: %+v Pod: %+v", action, p) - return ¢ral.SensorEvent{ Id: p.GetId(), Action: action, From 59049ee1b72ee3fc3a633c29ccb24c689816d00d Mon Sep 17 00:00:00 2001 From: RTann Date: Thu, 3 Feb 2022 13:18:49 -0800 Subject: [PATCH 22/30] update log --- sensor/common/registry/registry_store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensor/common/registry/registry_store.go b/sensor/common/registry/registry_store.go index a174348d60c3c..92dc07335327a 100644 --- a/sensor/common/registry/registry_store.go +++ b/sensor/common/registry/registry_store.go @@ -88,7 +88,7 @@ func (rs *Store) UpsertRegistry(namespace, registry string, dce types.DockerConf return errors.Wrapf(err, "updating registry store with registry %q", registry) } - log.Debugf("Upserted registry %q into store", registry) + log.Debugf("Upserted registry %q for namespace %q into store", registry, namespace) return nil } From 6ee9f65c6a234ef12bc87738b778df2a787c084b Mon Sep 17 00:00:00 2001 From: RTann Date: Mon, 7 Feb 2022 16:16:51 -0800 Subject: [PATCH 23/30] PR udpates --- pkg/docker/{types => config}/config.go | 2 +- pkg/images/integration/set.go | 2 +- pkg/registries/factory.go | 17 +++--- pkg/registries/factory_options.go | 25 ++------- pkg/scanners/anchore/anchore_test.go | 2 +- sensor/common/registry/registry_store.go | 53 ++++++++++--------- sensor/common/registry/singleton.go | 2 +- .../kubernetes/listener/resources/secrets.go | 8 +-- 8 files changed, 48 insertions(+), 63 deletions(-) rename pkg/docker/{types => config}/config.go (99%) diff --git a/pkg/docker/types/config.go b/pkg/docker/config/config.go similarity index 99% rename from pkg/docker/types/config.go rename to pkg/docker/config/config.go index cbb814e869130..a29ad9f2218ec 100644 --- a/pkg/docker/types/config.go +++ b/pkg/docker/config/config.go @@ -1,4 +1,4 @@ -package types +package config import ( "encoding/base64" diff --git a/pkg/images/integration/set.go b/pkg/images/integration/set.go index 1ab3f8737629b..b312fc2c8d779 100644 --- a/pkg/images/integration/set.go +++ b/pkg/images/integration/set.go @@ -23,7 +23,7 @@ type Set interface { // NewSet returns a new Set instance. func NewSet(reporter integrationhealth.Reporter) Set { - registryFactory := registries.NewFactory() + registryFactory := registries.NewFactory(registries.FactoryOptions{}) registrySet := registries.NewSet(registryFactory) scannerFactory := scanners.NewFactory(registrySet) diff --git a/pkg/registries/factory.go b/pkg/registries/factory.go index fe0eb2988f170..790aba61d3aac 100644 --- a/pkg/registries/factory.go +++ b/pkg/registries/factory.go @@ -26,9 +26,9 @@ type Factory interface { CreateRegistry(source *storage.ImageIntegration) (types.ImageRegistry, error) } -type creatorWrapper func() (string, func(integration *storage.ImageIntegration) (types.Registry, error)) +type CreatorWrapper func() (string, func(integration *storage.ImageIntegration) (types.Registry, error)) -var allCreatorFuncs = []creatorWrapper{ +var AllCreatorFuncs = []CreatorWrapper{ artifactRegistryFactory.Creator, artifactoryFactory.Creator, dockerFactory.Creator, @@ -44,19 +44,14 @@ var allCreatorFuncs = []creatorWrapper{ } // NewFactory creates a new scanner factory. -func NewFactory(opts ...FactoryOption) Factory { - var o factoryOption - for _, opt := range opts { - opt.apply(&o) - } - +func NewFactory(opts FactoryOptions) Factory { reg := &factoryImpl{ creators: make(map[string]Creator), } - creatorFuncs := allCreatorFuncs - if len(o.creatorFuncs) > 0 { - creatorFuncs = o.creatorFuncs + creatorFuncs := AllCreatorFuncs + if len(opts.CreatorFuncs) > 0 { + creatorFuncs = opts.CreatorFuncs } for _, creatorFunc := range creatorFuncs { diff --git a/pkg/registries/factory_options.go b/pkg/registries/factory_options.go index 259d9d676b81c..0252487ce4ee9 100644 --- a/pkg/registries/factory_options.go +++ b/pkg/registries/factory_options.go @@ -1,23 +1,8 @@ package registries -type factoryOption struct { - creatorFuncs []creatorWrapper -} - -// FactoryOption specifies optional configuration parameters for a registry factory. -type FactoryOption interface { - apply(*factoryOption) -} - -type factoryOptionFunc func(*factoryOption) - -func (f factoryOptionFunc) apply(opt *factoryOption) { - f(opt) -} - -// WithRegistryCreators specifies which registries to add to the factory. -func WithRegistryCreators(creatorFuncs ...creatorWrapper) FactoryOption { - return factoryOptionFunc(func(o *factoryOption) { - o.creatorFuncs = append(o.creatorFuncs, creatorFuncs...) - }) +// FactoryOptions specifies optional configuration parameters for a registry factory. +type FactoryOptions struct { + // CreatorFuncs specifies which registries to add to the factory. + // By default, AllCreatorFuncs is used. + CreatorFuncs []CreatorWrapper } diff --git a/pkg/scanners/anchore/anchore_test.go b/pkg/scanners/anchore/anchore_test.go index 0eb560dc47458..6c8aabddfb244 100644 --- a/pkg/scanners/anchore/anchore_test.go +++ b/pkg/scanners/anchore/anchore_test.go @@ -21,7 +21,7 @@ func TestAnchore(t *testing.T) { t.Skipf("Skipping Anchore integration test") } - registryFactory := registries.NewFactory() + registryFactory := registries.NewFactory(registries.FactoryOptions{}) registrySet := registries.NewSet(registryFactory) err := registrySet.UpdateImageIntegration(&storage.ImageIntegration{ diff --git a/sensor/common/registry/registry_store.go b/sensor/common/registry/registry_store.go index 92dc07335327a..22596cd6f7b3a 100644 --- a/sensor/common/registry/registry_store.go +++ b/sensor/common/registry/registry_store.go @@ -1,9 +1,11 @@ package registry import ( + "context" + "github.com/pkg/errors" "github.com/stackrox/rox/generated/storage" - "github.com/stackrox/rox/pkg/docker/types" + "github.com/stackrox/rox/pkg/docker/config" "github.com/stackrox/rox/pkg/logging" "github.com/stackrox/rox/pkg/registries" dockerFactory "github.com/stackrox/rox/pkg/registries/docker" @@ -24,25 +26,31 @@ type Store struct { mutex sync.RWMutex - // test indicates if this is a test store or not. - test bool + checkTLS CheckTLS } -// newRegistryStore creates a new registry store. -func newRegistryStore() *Store { - return &Store{ - factory: registries.NewFactory(registries.WithRegistryCreators(dockerFactory.Creator)), +// CheckTLS defines a function which checks if the given address is using TLS. +// An example implementation of this is tlscheck.CheckTLS. +type CheckTLS func(origAddr string) (bool, error) + +// NewRegistryStore creates a new registry store. +// The passed-in TLSChecker is used to check if a registry uses TLS. +// If no TLSChecker is passed in, tlscheck.CheckTLS is used by default. +func NewRegistryStore(checkTLS CheckTLS) *Store { + store := &Store{ + factory: registries.NewFactory(registries.FactoryOptions{ + CreatorFuncs: []registries.CreatorWrapper{dockerFactory.Creator}, + }), store: make(map[string]registries.Set), + + checkTLS: tlscheck.CheckTLS, } -} -// NewTestRegistryStore creates a new registry store for testing purposes. -// The main difference between this and a non-test registry store -// is that this one does not attempt to reach out to the registry to check TLS. -func NewTestRegistryStore() *Store { - rs := newRegistryStore() - rs.test = true - return rs + if checkTLS != nil { + store.checkTLS = checkTLS + } + + return store } func (rs *Store) getRegistries(namespace string) registries.Set { @@ -59,19 +67,16 @@ func (rs *Store) getRegistries(namespace string) registries.Set { } // UpsertRegistry upserts the given registry with the given credentials in the given namespace into the store. -func (rs *Store) UpsertRegistry(namespace, registry string, dce types.DockerConfigEntry) error { +func (rs *Store) UpsertRegistry(ctx context.Context, namespace, registry string, dce config.DockerConfigEntry) error { regs := rs.getRegistries(namespace) - var secure bool - if !rs.test { - var err error - secure, err = tlscheck.CheckTLS(registry) - if err != nil { - return errors.Wrapf(err, "unable to check TLS for registry %q", registry) - } + // TODO: pass a context here, as this can take time. + secure, err := rs.checkTLS(registry) + if err != nil { + return errors.Wrapf(err, "unable to check TLS for registry %q", registry) } - err := regs.UpdateImageIntegration(&storage.ImageIntegration{ + err = regs.UpdateImageIntegration(&storage.ImageIntegration{ Name: registry, Type: "docker", Categories: []storage.ImageIntegrationCategory{storage.ImageIntegrationCategory_REGISTRY}, diff --git a/sensor/common/registry/singleton.go b/sensor/common/registry/singleton.go index 402eb291f447c..5e807f7cb4ff4 100644 --- a/sensor/common/registry/singleton.go +++ b/sensor/common/registry/singleton.go @@ -10,7 +10,7 @@ var ( // Singleton returns a singleton of the registry storage. func Singleton() *Store { once.Do(func() { - rStore = newRegistryStore() + rStore = NewRegistryStore(nil) }) return rStore } diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index 4b61626c13408..207e4ce288d6e 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -11,7 +11,7 @@ import ( "github.com/cloudflare/cfssl/certinfo" "github.com/stackrox/rox/generated/internalapi/central" "github.com/stackrox/rox/generated/storage" - "github.com/stackrox/rox/pkg/docker/types" + "github.com/stackrox/rox/pkg/docker/config" "github.com/stackrox/rox/pkg/features" "github.com/stackrox/rox/pkg/protoconv" "github.com/stackrox/rox/pkg/registries/docker" @@ -133,7 +133,7 @@ func newSecretDispatcher(regStore *registry.Store) *secretDispatcher { } } -func dockerConfigToImageIntegration(registry string, dce types.DockerConfigEntry) *storage.ImageIntegration { +func dockerConfigToImageIntegration(registry string, dce config.DockerConfigEntry) *storage.ImageIntegration { registryType := docker.GenericDockerRegistryType if urlfmt.TrimHTTPPrefixes(registry) == redhatRegistryEndpoint { registryType = rhel.RedHatRegistryType @@ -155,7 +155,7 @@ func dockerConfigToImageIntegration(registry string, dce types.DockerConfigEntry } func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action central.ResourceAction) []*central.SensorEvent { - var dockerConfig types.DockerConfig + var dockerConfig config.DockerConfig switch secret.Type { case v1.SecretTypeDockercfg: data, ok := secret.Data[v1.DockerConfigKey] @@ -171,7 +171,7 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce if !ok { return nil } - var dockerConfigJSON types.DockerConfigJSON + var dockerConfigJSON config.DockerConfigJSON if err := json.Unmarshal(data, &dockerConfigJSON); err != nil { log.Error(err) return nil From 15e362fcadd203d72faaf4e002363db1bbc67388 Mon Sep 17 00:00:00 2001 From: RTann Date: Mon, 7 Feb 2022 16:43:26 -0800 Subject: [PATCH 24/30] PR udpates --- sensor/common/registry/registry_store.go | 4 +--- sensor/kubernetes/listener/resources/secrets.go | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sensor/common/registry/registry_store.go b/sensor/common/registry/registry_store.go index 22596cd6f7b3a..b25866a77cc0b 100644 --- a/sensor/common/registry/registry_store.go +++ b/sensor/common/registry/registry_store.go @@ -1,8 +1,6 @@ package registry import ( - "context" - "github.com/pkg/errors" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/docker/config" @@ -67,7 +65,7 @@ func (rs *Store) getRegistries(namespace string) registries.Set { } // UpsertRegistry upserts the given registry with the given credentials in the given namespace into the store. -func (rs *Store) UpsertRegistry(ctx context.Context, namespace, registry string, dce config.DockerConfigEntry) error { +func (rs *Store) UpsertRegistry(namespace, registry string, dce config.DockerConfigEntry) error { regs := rs.getRegistries(namespace) // TODO: pass a context here, as this can take time. diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index 207e4ce288d6e..d179f1a4fec97 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -197,6 +197,8 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce if err != nil { log.Errorf("Unable to upsert registry %q into store: %v", registry, err) } + + continue } } ii := dockerConfigToImageIntegration(registry, dce) From d2687bdea3eddd21a5ca0319172e0bd1b17d4cfc Mon Sep 17 00:00:00 2001 From: RTann Date: Mon, 7 Feb 2022 17:12:02 -0800 Subject: [PATCH 25/30] style --- pkg/registries/factory.go | 2 ++ sensor/common/registry/registry_store.go | 2 +- sensor/kubernetes/listener/resources/secrets_test.go | 9 +++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/registries/factory.go b/pkg/registries/factory.go index 790aba61d3aac..13f99902435dc 100644 --- a/pkg/registries/factory.go +++ b/pkg/registries/factory.go @@ -26,8 +26,10 @@ type Factory interface { CreateRegistry(source *storage.ImageIntegration) (types.ImageRegistry, error) } +// CreatorWrapper is a wrapper around a Creator which also returns the registry's name. type CreatorWrapper func() (string, func(integration *storage.ImageIntegration) (types.Registry, error)) +// AllCreatorFuncs defines all known registry creators. var AllCreatorFuncs = []CreatorWrapper{ artifactRegistryFactory.Creator, artifactoryFactory.Creator, diff --git a/sensor/common/registry/registry_store.go b/sensor/common/registry/registry_store.go index b25866a77cc0b..c4e9b7339156c 100644 --- a/sensor/common/registry/registry_store.go +++ b/sensor/common/registry/registry_store.go @@ -39,7 +39,7 @@ func NewRegistryStore(checkTLS CheckTLS) *Store { factory: registries.NewFactory(registries.FactoryOptions{ CreatorFuncs: []registries.CreatorWrapper{dockerFactory.Creator}, }), - store: make(map[string]registries.Set), + store: make(map[string]registries.Set), checkTLS: tlscheck.CheckTLS, } diff --git a/sensor/kubernetes/listener/resources/secrets_test.go b/sensor/kubernetes/listener/resources/secrets_test.go index cfa85d36bb79e..e60031a8033ed 100644 --- a/sensor/kubernetes/listener/resources/secrets_test.go +++ b/sensor/kubernetes/listener/resources/secrets_test.go @@ -65,12 +65,17 @@ var ( } ) +// checkTLS is a dummy implementation of registry.CheckTLS +func checkTLS(_ string) (bool, error) { + return false, nil +} + func TestOpenShiftRegistrySecret_311(t *testing.T) { testutils.RunWithFeatureFlagEnabled(t, features.LocalImageScanning, testOpenShiftRegistrySecret311) } func testOpenShiftRegistrySecret311(t *testing.T) { - regStore := registry.NewTestRegistryStore() + regStore := registry.NewRegistryStore(checkTLS) d := newSecretDispatcher(regStore) _ = d.ProcessEvent(openshift311DockerConfigSecret, nil, central.ResourceAction_CREATE_RESOURCE) @@ -98,7 +103,7 @@ func TestOpenShiftRegistrySecret_4x(t *testing.T) { } func testOpenShiftRegistrySecret4x(t *testing.T) { - regStore := registry.NewTestRegistryStore() + regStore := registry.NewRegistryStore(checkTLS) d := newSecretDispatcher(regStore) _ = d.ProcessEvent(openshift4xDockerConfigSecret, nil, central.ResourceAction_CREATE_RESOURCE) From 9900b2d062b0156371da4030d8bc03b85c40cee4 Mon Sep 17 00:00:00 2001 From: RTann Date: Mon, 7 Feb 2022 17:19:20 -0800 Subject: [PATCH 26/30] add ctx and comment fixes --- sensor/common/registry/registry_store.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/sensor/common/registry/registry_store.go b/sensor/common/registry/registry_store.go index c4e9b7339156c..a4853820a4cf1 100644 --- a/sensor/common/registry/registry_store.go +++ b/sensor/common/registry/registry_store.go @@ -1,6 +1,8 @@ package registry import ( + "context" + "github.com/pkg/errors" "github.com/stackrox/rox/generated/storage" "github.com/stackrox/rox/pkg/docker/config" @@ -29,11 +31,11 @@ type Store struct { // CheckTLS defines a function which checks if the given address is using TLS. // An example implementation of this is tlscheck.CheckTLS. -type CheckTLS func(origAddr string) (bool, error) +type CheckTLS func(ctx context.Context, origAddr string) (bool, error) // NewRegistryStore creates a new registry store. -// The passed-in TLSChecker is used to check if a registry uses TLS. -// If no TLSChecker is passed in, tlscheck.CheckTLS is used by default. +// The passed-in CheckTLS is used to check if a registry uses TLS. +// If checkTLS is nil, tlscheck.CheckTLS is used by default. func NewRegistryStore(checkTLS CheckTLS) *Store { store := &Store{ factory: registries.NewFactory(registries.FactoryOptions{ @@ -65,11 +67,10 @@ func (rs *Store) getRegistries(namespace string) registries.Set { } // UpsertRegistry upserts the given registry with the given credentials in the given namespace into the store. -func (rs *Store) UpsertRegistry(namespace, registry string, dce config.DockerConfigEntry) error { +func (rs *Store) UpsertRegistry(ctx context.Context, namespace, registry string, dce config.DockerConfigEntry) error { regs := rs.getRegistries(namespace) - // TODO: pass a context here, as this can take time. - secure, err := rs.checkTLS(registry) + secure, err := rs.checkTLS(ctx, registry) if err != nil { return errors.Wrapf(err, "unable to check TLS for registry %q", registry) } From 05fa6c62f6e54abc53138e596ebc27f2219d59a8 Mon Sep 17 00:00:00 2001 From: RTann Date: Mon, 7 Feb 2022 17:28:21 -0800 Subject: [PATCH 27/30] style --- sensor/kubernetes/listener/resources/secrets.go | 3 ++- sensor/kubernetes/listener/resources/secrets_test.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index d179f1a4fec97..57a8ef3ba9179 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -1,6 +1,7 @@ package resources import ( + "context" "encoding/base64" "encoding/json" "errors" @@ -193,7 +194,7 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce if features.LocalImageScanning.Enabled() { if fromDefaultSA { // Store the registry credentials so Sensor can reach it. - err := s.regStore.UpsertRegistry(secret.GetNamespace(), registry, dce) + err := s.regStore.UpsertRegistry(context.Background(), secret.GetNamespace(), registry, dce) if err != nil { log.Errorf("Unable to upsert registry %q into store: %v", registry, err) } diff --git a/sensor/kubernetes/listener/resources/secrets_test.go b/sensor/kubernetes/listener/resources/secrets_test.go index e60031a8033ed..b4c67ce6bf76a 100644 --- a/sensor/kubernetes/listener/resources/secrets_test.go +++ b/sensor/kubernetes/listener/resources/secrets_test.go @@ -1,6 +1,7 @@ package resources import ( + "context" "testing" "github.com/stackrox/rox/generated/internalapi/central" @@ -66,7 +67,7 @@ var ( ) // checkTLS is a dummy implementation of registry.CheckTLS -func checkTLS(_ string) (bool, error) { +func checkTLS(_ context.Context, _ string) (bool, error) { return false, nil } From b06b40222636ce0a1ff7ada757d2e3a8ef54737c Mon Sep 17 00:00:00 2001 From: RTann Date: Mon, 7 Feb 2022 19:47:05 -0800 Subject: [PATCH 28/30] rename checkTLS for clarity --- sensor/kubernetes/listener/resources/secrets_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sensor/kubernetes/listener/resources/secrets_test.go b/sensor/kubernetes/listener/resources/secrets_test.go index b4c67ce6bf76a..14a4f3b4d66b5 100644 --- a/sensor/kubernetes/listener/resources/secrets_test.go +++ b/sensor/kubernetes/listener/resources/secrets_test.go @@ -66,8 +66,9 @@ var ( } ) -// checkTLS is a dummy implementation of registry.CheckTLS -func checkTLS(_ context.Context, _ string) (bool, error) { +// alwaysInsecureCheckTLS is an implementation of registry.CheckTLS +// which always says the given address is insecure. +func alwaysInsecureCheckTLS(_ context.Context, _ string) (bool, error) { return false, nil } @@ -76,7 +77,7 @@ func TestOpenShiftRegistrySecret_311(t *testing.T) { } func testOpenShiftRegistrySecret311(t *testing.T) { - regStore := registry.NewRegistryStore(checkTLS) + regStore := registry.NewRegistryStore(alwaysInsecureCheckTLS) d := newSecretDispatcher(regStore) _ = d.ProcessEvent(openshift311DockerConfigSecret, nil, central.ResourceAction_CREATE_RESOURCE) @@ -104,7 +105,7 @@ func TestOpenShiftRegistrySecret_4x(t *testing.T) { } func testOpenShiftRegistrySecret4x(t *testing.T) { - regStore := registry.NewRegistryStore(checkTLS) + regStore := registry.NewRegistryStore(alwaysInsecureCheckTLS) d := newSecretDispatcher(regStore) _ = d.ProcessEvent(openshift4xDockerConfigSecret, nil, central.ResourceAction_CREATE_RESOURCE) From ec8592cb9a26d040ae8e81fe443e104d300f498d Mon Sep 17 00:00:00 2001 From: RTann Date: Tue, 8 Feb 2022 12:57:51 -0800 Subject: [PATCH 29/30] update secret --- .../kubernetes/listener/resources/secrets.go | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/sensor/kubernetes/listener/resources/secrets.go b/sensor/kubernetes/listener/resources/secrets.go index 57a8ef3ba9179..f9d9cbae957a6 100644 --- a/sensor/kubernetes/listener/resources/secrets.go +++ b/sensor/kubernetes/listener/resources/secrets.go @@ -198,18 +198,19 @@ func (s *secretDispatcher) processDockerConfigEvent(secret *v1.Secret, action ce if err != nil { log.Errorf("Unable to upsert registry %q into store: %v", registry, err) } - - continue } } - ii := dockerConfigToImageIntegration(registry, dce) - sensorEvents = append(sensorEvents, ¢ral.SensorEvent{ - // Only update is supported at this time. - Action: central.ResourceAction_UPDATE_RESOURCE, - Resource: ¢ral.SensorEvent_ImageIntegration{ - ImageIntegration: ii, - }, - }) + + if !features.LocalImageScanning.Enabled() || !fromDefaultSA { + ii := dockerConfigToImageIntegration(registry, dce) + sensorEvents = append(sensorEvents, ¢ral.SensorEvent{ + // Only update is supported at this time. + Action: central.ResourceAction_UPDATE_RESOURCE, + Resource: ¢ral.SensorEvent_ImageIntegration{ + ImageIntegration: ii, + }, + }) + } registries = append(registries, &storage.ImagePullSecret_Registry{ Name: registry, From 2501cef536f0692283ce7dae0037072d172ad020 Mon Sep 17 00:00:00 2001 From: RTann Date: Tue, 8 Feb 2022 15:01:04 -0800 Subject: [PATCH 30/30] move closer to usage --- sensor/common/registry/registry_store.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sensor/common/registry/registry_store.go b/sensor/common/registry/registry_store.go index a4853820a4cf1..1fc9496b118a7 100644 --- a/sensor/common/registry/registry_store.go +++ b/sensor/common/registry/registry_store.go @@ -68,13 +68,12 @@ func (rs *Store) getRegistries(namespace string) registries.Set { // UpsertRegistry upserts the given registry with the given credentials in the given namespace into the store. func (rs *Store) UpsertRegistry(ctx context.Context, namespace, registry string, dce config.DockerConfigEntry) error { - regs := rs.getRegistries(namespace) - secure, err := rs.checkTLS(ctx, registry) if err != nil { return errors.Wrapf(err, "unable to check TLS for registry %q", registry) } + regs := rs.getRegistries(namespace) err = regs.UpdateImageIntegration(&storage.ImageIntegration{ Name: registry, Type: "docker",