Skip to content

Commit

Permalink
Security Scanners information in Module Template (kyma-project#1531)
Browse files Browse the repository at this point in the history
* introduce new step in create module command to add security scanning data

* fix linting

* introduce flag for security scanning config file

* make docs

* go fmt
  • Loading branch information
fourthisle authored Feb 22, 2023
1 parent 118b858 commit 50d09be
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 5 deletions.
12 changes: 12 additions & 0 deletions cmd/kyma/alpha/create/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ Build module my-domain/modB in version 3.2.1 and push it to a local registry "un
)
cmd.Flags().BoolVar(&o.Insecure, "insecure", false, "Use an insecure connection to access the registry.")
cmd.Flags().BoolVar(&o.Clean, "clean", false, "Remove the mod-path folder and all its contents at the end.")
cmd.Flags().StringVar(&o.SecurityScanConfig, "sec-scan-cfg", "", "Path to the directory holding "+
"the security scan configuration file.")

return cmd
}
Expand Down Expand Up @@ -189,6 +191,16 @@ func (cmd *command) Run(ctx context.Context, args []string) error {

cmd.CurrentStep.Successf("Image created")

/* -- ADD SECURITY SCANNING METADATA -- */
if cmd.opts.SecurityScanConfig != "" {
cmd.NewStep("Configuring security scanning...")
err = module.AddSecurityScanningMetadata(archive.ComponentDescriptor, modDef, fs, cmd.opts.SecurityScanConfig)
if err != nil {
cmd.CurrentStep.Failure()
return err
}
cmd.CurrentStep.Successf("Security scanning configured")
}
/* -- PUSH & TEMPLATE -- */

if cmd.opts.RegistryURL != "" {
Expand Down
1 change: 1 addition & 0 deletions cmd/kyma/alpha/create/module/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Options struct {
Overwrite bool
Clean bool
RegistryCredSelector string
SecurityScanConfig string
}

const (
Expand Down
1 change: 1 addition & 0 deletions docs/gen-docs/kyma_alpha_create_module.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Build module my-domain/modB in version 3.2.1 and push it to a local registry "un
--registry string Repository context url for module to upload. The repository url will be automatically added to the repository contexts in the module
--registry-cred-selector string label selector to identify a secret of type kubernetes.io/dockerconfigjson (that needs to be created externally) which allows the image to be accessed in private image registries. This can be used if you push your module to a registry with authenticated access. Example: "label1=value1,label2=value2"
-r, --resource stringArray Add an extra resource in a new layer with format <NAME:TYPE@PATH>. It is also possible to provide only a path; name will default to the last path element and type to 'helm-chart'
--sec-scan-cfg string Path to the directory holding the security scan configuration file.
-t, --token string Authentication token for the given registry (alternative to basic authentication).
--version string Version of the module. This flag is mandatory.
```
Expand Down
4 changes: 0 additions & 4 deletions pkg/module/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,6 @@ func Push(archive *ctf.ComponentArchive, r *Remote, log *zap.SugaredLogger) erro
return nil
}

func Pull() {
// TODO
}

// UserPass splits the credentials string into user and password.
// If the string is empty or can't be split, it returns 2 empty strings.
func (r *Remote) UserPass() (string, string) {
Expand Down
162 changes: 162 additions & 0 deletions pkg/module/security_scan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package module

import (
"encoding/json"
"errors"
"fmt"
v2 "github.com/gardener/component-spec/bindings-go/apis/v2"
"github.com/gardener/component-spec/bindings-go/ctf"
"github.com/mandelsoft/vfs/pkg/vfs"
"os"
"path"
"sigs.k8s.io/yaml"
"strings"
)

var ErrFailedToParseImageURL = errors.New("error parsing protecode image URL")

const (
secConfigFileName = "sec-scanners-config.yaml"
secScanLabelKey = "scan.security.kyma-project.io"
secLabelKey = "security.kyma-project.io"
secScanEnabled = "enabled"
)

var labelTemplate = "%s." + secScanLabelKey
var globalLabelTemplate = "%s." + secLabelKey

func AddSecurityScanningMetadata(descriptor *v2.ComponentDescriptor, modDef *Definition, fs vfs.FileSystem,
securityConfigPath string,
) error {
//parse security config file
config, err := parseSecurityScanConfig(securityConfigPath)
if err != nil {
return err
}
excludedWhitesourcePathPatterns := strings.Join(config.WhiteSource.Exclude, ",")

// add security scan enabled global label
err = appendLabelToAccessor(descriptor, "scan", secScanEnabled, globalLabelTemplate)
if err != nil {
return err
}
if len(descriptor.Sources) == 0 {
return errors.New("found no sources in component descriptor")
}
//add whitesource sec scan labels
for srcIdx := range descriptor.Sources {
src := &descriptor.Sources[srcIdx]
err := appendLabelToAccessor(src, "language", config.WhiteSource.Language, labelTemplate)
if err != nil {
return err
}
err = appendLabelToAccessor(src, "subprojects", config.WhiteSource.SubProjects, labelTemplate)
if err != nil {
return err
}
err = appendLabelToAccessor(src, "exclude", excludedWhitesourcePathPatterns, labelTemplate)
if err != nil {
return err
}
}
//add protecode sec scan images
if err := appendProtecodeImagesLayers(descriptor, config); err != nil {
return err
}

return WriteComponentDescriptor(fs, descriptor, modDef.ArchivePath,
ctf.ComponentDescriptorFileName)
}

func appendProtecodeImagesLayers(descriptor *v2.ComponentDescriptor, config *SecurityScanCfg) error {
for _, imageURL := range config.Protecode {
imageName, err := getImageName(imageURL)
if err != nil {
return err
}

imageTypeLabel, err := generateOCMLabel("type", "third-party-image", labelTemplate)
if err != nil {
return err
}
imageLayerMetadata := v2.IdentityObjectMeta{
Name: imageName,
Type: v2.OCIImageType,
Version: descriptor.Version,
Labels: []v2.Label{imageTypeLabel},
}
imageLayerAccess, err := v2.NewUnstructured(v2.NewOCIRegistryAccess(imageURL))
if err != nil {
return err
}
protecodeImageLayerResource := v2.Resource{
IdentityObjectMeta: imageLayerMetadata,
Relation: v2.ExternalRelation,
Access: &imageLayerAccess,
}
descriptor.Resources = append(descriptor.Resources, protecodeImageLayerResource)
}
return nil
}

func generateOCMLabel(prefix, value, tpl string) (v2.Label, error) {
labelValue, err := json.Marshal(map[string]string{
prefix: value,
})
if err != nil {
return v2.Label{}, err
}
return v2.Label{
Name: fmt.Sprintf(tpl, prefix),
Value: labelValue,
}, nil
}

func getImageName(imageURL string) (string, error) {
imageTagSlice := strings.Split(imageURL, ":")
if len(imageTagSlice) != 2 {
return "", ErrFailedToParseImageURL
}
repoImageSlice := strings.Split(imageTagSlice[0], "/")
l := len(repoImageSlice)
if l == 0 {
return "", ErrFailedToParseImageURL
}

return repoImageSlice[l-1], nil
}

type SecurityScanCfg struct {
ModuleName string `json:"module-name"`
Protecode []string `json:"protecode"`
WhiteSource WhiteSourceSecCfg `json:"whitesource"`
}
type WhiteSourceSecCfg struct {
Language string `json:"language"`
SubProjects string `json:"subprojects"`
Exclude []string `json:"exclude"`
}

func parseSecurityScanConfig(securityConfigPath string) (*SecurityScanCfg, error) {
configFilePath := path.Join(securityConfigPath, secConfigFileName)
fileBytes, err := os.ReadFile(configFilePath)
if err != nil {
return nil, err
}
secCfg := &SecurityScanCfg{}
if err := yaml.Unmarshal(fileBytes, secCfg); err != nil {
return nil, err
}
return secCfg, nil
}

func appendLabelToAccessor(labeled v2.LabelsAccessor, prefix, value, tpl string) error {
labels := labeled.GetLabels()
labelValue, err := generateOCMLabel(prefix, value, tpl)
if err != nil {
return err
}
labels = append(labels, labelValue)
labeled.SetLabels(labels)
return nil
}
2 changes: 1 addition & 1 deletion pkg/module/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func Template(archive *ctf.ComponentArchive, channel string, data []byte, regist
return w.Bytes(), nil
}

// indent prepends the given number of whitespaces to eachline in the given string
// Indent prepends the given number of whitespaces to eachline in the given string
func Indent(n int, in string) string {
out := strings.Builder{}

Expand Down

0 comments on commit 50d09be

Please sign in to comment.