Skip to content

Commit

Permalink
Merge pull request #143 from kubescape/use-registry-scan-secret-for-i…
Browse files Browse the repository at this point in the history
…mage-scanning
  • Loading branch information
amirmalka authored Jun 14, 2023
2 parents 6bdf1d3 + a66c665 commit d7dc27c
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 28 deletions.
80 changes: 54 additions & 26 deletions mainhandler/imageregistryhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ func (registryScan *registryScan) getRegistryProvider() string {

// parse registry information from secret, configmap and command, giving priority to command
func (registryScan *registryScan) parseRegistry(ctx context.Context, sessionObj *utils.SessionObj) error {
if err := registryScan.parseRegistryAuthFromSecret(ctx); err != nil {
if err := registryScan.setRegistryAuthFromSecret(ctx); err != nil {
logger.L().Info("parseRegistry: could not parse auth from secret, parsing from command", helpers.Error(err))
} else {
sessionObj.Reporter.SendDetails("secret loaded", true, sessionObj.ErrChan)
Expand Down Expand Up @@ -665,27 +665,30 @@ func (registryScan *registryScan) createTriggerRequestCronJob(k8sAPI *k8sinterfa
return nil
}

func (registryScan *registryScan) parseRegistryAuthFromSecret(ctx context.Context) error {
secret, err := registryScan.getRegistryScanSecret()
if err != nil {
return err
func (registryScan *registryScan) setRegistryAuthFromSecret(ctx context.Context) error {
var secretName string

if registryScan.registryInfo.SecretName != "" {
// in newer versions the secret is sent as part of the command
secretName = registryScan.registryInfo.SecretName
} else {
// in older versions the secret is stored in the cluster (backward compatibility)
secretName = armotypes.RegistryScanSecretName
}

secretData := secret.GetData()
var registriesAuth []registryAuth
registriesAuthStr, ok := secretData[registriesAuthFieldInSecret].(string)
if !ok {
return fmt.Errorf("error parsing Secret: %s field must be a string", registriesAuthFieldInSecret)
// find secret in cluster
secrets, err := getRegistryScanSecrets(registryScan.k8sAPI, secretName)
if err != nil || len(secrets) == 0 {
return err
}
data, err := base64.StdEncoding.DecodeString(registriesAuthStr)

secret := secrets[0]

registriesAuth, err := parseRegistryAuthSecret(secret)
if err != nil {
return fmt.Errorf("error parsing Secret: %s", err.Error())
return err
}
registriesAuthStr = strings.Replace(string(data), "\n", "", -1)

if e := json.Unmarshal([]byte(registriesAuthStr), &registriesAuth); e != nil {
return fmt.Errorf("error parsing Secret: %s", e.Error())
}
//try to find an auth with the same registry name from the request
for _, auth := range registriesAuth {
if auth.Registry == registryScan.registryInfo.RegistryName {
Expand Down Expand Up @@ -717,6 +720,26 @@ func (registryScan *registryScan) parseRegistryAuthFromSecret(ctx context.Contex
return nil
}

func parseRegistryAuthSecret(secret k8sinterface.IWorkload) ([]registryAuth, error) {
secretData := secret.GetData()
var registriesAuth []registryAuth
registriesAuthStr, ok := secretData[registriesAuthFieldInSecret].(string)
if !ok {
return nil, fmt.Errorf("error parsing Secret: %s field must be a string", registriesAuthFieldInSecret)
}
data, err := base64.StdEncoding.DecodeString(registriesAuthStr)
if err != nil {
return nil, fmt.Errorf("error parsing Secret: %s", err.Error())
}
registriesAuthStr = strings.Replace(string(data), "\n", "", -1)

if e := json.Unmarshal([]byte(registriesAuthStr), &registriesAuth); e != nil {
return nil, fmt.Errorf("error parsing Secret: %s", e.Error())
}

return registriesAuth, nil
}

func (registryScan *registryScan) setRegistryInfoFromAuth(auth registryAuth, registryInfo *armotypes.RegistryInfo) {
registryInfo.AuthMethod.Type = auth.AuthMethod
registryInfo.AuthMethod.Username = auth.Username
Expand Down Expand Up @@ -774,21 +797,26 @@ func (registryScan *registryScan) setRegistryInfoFromConfigMap(registryInfo *arm
registryInfo.Exclude = registryConfig.Exclude
}

func (registryScan *registryScan) getRegistryScanSecret() (k8sinterface.IWorkload, error) {
// check if secret was sent as part of command (newer versions)
if registryScan.registryInfo.SecretName != "" {
secret, err := registryScan.k8sAPI.GetWorkload(armotypes.KubescapeNamespace, "Secret", registryScan.registryInfo.SecretName)
func getRegistryScanSecrets(k8sAPI *k8sinterface.KubernetesApi, secretName string) ([]k8sinterface.IWorkload, error) {
if secretName != "" {
secret, err := k8sAPI.GetWorkload(armotypes.KubescapeNamespace, "Secret", secretName)
if err == nil && secret != nil {
return secret, err
return []k8sinterface.IWorkload{secret}, err
}
}

// check if secret exists in cluster (backward compatibility)
secret, err := registryScan.k8sAPI.GetWorkload(armotypes.KubescapeNamespace, "Secret", armotypes.RegistryScanSecretName)
if err == nil && secret != nil {
return secret, err
// when secret name is not provided, we will try to find all secrets starting with kubescape-registry-scan
registryScanSecrets := []k8sinterface.IWorkload{}
all, err := k8sAPI.ListWorkloads2(armotypes.KubescapeNamespace, "Secret")
if err == nil {
for _, secret := range all {
if strings.HasPrefix(secret.GetName(), armotypes.RegistryScanSecretName) {
registryScanSecrets = append(registryScanSecrets, secret)
}
}
}
return secret, err

return registryScanSecrets, err
}

func (registryScan *registryScan) setHostnameAndProject() {
Expand Down
24 changes: 22 additions & 2 deletions mainhandler/vulnscan.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"time"

"github.com/docker/docker/api/types"
"github.com/kubescape/go-logger"
"github.com/kubescape/go-logger/helpers"
"github.com/kubescape/operator/utils"
Expand Down Expand Up @@ -496,7 +497,7 @@ func (actionHandler *ActionHandler) getContainerToImageIDsFromWorkload(pod *core
return mapContainerToImageID
}

func (actionHandler *ActionHandler) getCommand(container ContainerData, pod *corev1.Pod, imageID string, sessionObj *utils.SessionObj, command apis.NotificationPolicyType) (*apis.WebsocketScanCommand, error) {
func (actionHandler *ActionHandler) getCommand(container ContainerData, pod *corev1.Pod, imageID string, sessionObj *utils.SessionObj, command apis.NotificationPolicyType, containerRegistryAuths []registryAuth) (*apis.WebsocketScanCommand, error) {
websocketScanCommand := &apis.WebsocketScanCommand{
ImageScanParams: apis.ImageScanParams{
Session: apis.SessionChain{ActionTitle: string(command), JobIDs: make([]string, 0), Timestamp: sessionObj.Reporter.GetTimestamp()},
Expand Down Expand Up @@ -535,11 +536,30 @@ func (actionHandler *ActionHandler) getCommand(container ContainerData, pod *cor
}
}

// add relevant credentials if exist in the registry scan secrets
for _, creds := range containerRegistryAuths {
if strings.Contains(websocketScanCommand.ImageTag, creds.Registry) && creds.Password != "" {
logger.L().Debug(fmt.Sprintf("found registry scan secret for image: %s", websocketScanCommand.ImageTag), helpers.String("ImageTag", websocketScanCommand.ImageTag))
websocketScanCommand.Credentialslist = append(websocketScanCommand.Credentialslist, types.AuthConfig{ServerAddress: creds.Registry, Username: creds.Username, Password: creds.Password})
}
}

return websocketScanCommand, nil
}

func (actionHandler *ActionHandler) sendCommandForContainers(ctx context.Context, containers []ContainerData, mapContainerToImageID map[string]string, pod *corev1.Pod, sessionObj *utils.SessionObj, command apis.NotificationPolicyType) error {
errs := ""

// we build a list of all registry scan secrets
containerRegistryAuths := []registryAuth{}
if secrets, err := getRegistryScanSecrets(actionHandler.k8sAPI, ""); err == nil && len(secrets) > 0 {
for i := range secrets {
if auths, err := parseRegistryAuthSecret(secrets[i]); err == nil {
containerRegistryAuths = append(containerRegistryAuths, auths...)
}
}
}

for i := range containers {
imgID := ""
if val, ok := mapContainerToImageID[containers[i].container]; !ok {
Expand All @@ -551,7 +571,7 @@ func (actionHandler *ActionHandler) sendCommandForContainers(ctx context.Context

// some images don't have imageID prefix, we will add it for them
imgID = getImageIDFromContainer(containers[i], imgID)
websocketScanCommand, err := actionHandler.getCommand(containers[i], pod, imgID, sessionObj, command)
websocketScanCommand, err := actionHandler.getCommand(containers[i], pod, imgID, sessionObj, command, containerRegistryAuths)
if err != nil {
errs += err.Error()
logger.L().Error("failed to get command", helpers.String("image", containers[i].image), helpers.Error(err))
Expand Down

0 comments on commit d7dc27c

Please sign in to comment.