Skip to content

Commit

Permalink
feat: add initial registry-proxy implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Christian Kotzbauer <[email protected]>
  • Loading branch information
ckotzbauer committed Dec 4, 2022
1 parent 47092aa commit 437dfce
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 14 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ all: build
build: fmt vet
goreleaser build --rm-dist --single-target --snapshot

build-all: fmt vet
goreleaser build --rm-dist --snapshot

# Run against the configured Kubernetes cluster in ~/.kube/config
run: fmt vet
go run ./main.go
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ All parameters are cli-flags. The flags can be configured as args or as environm
| `pod-label-selector` | `false` | `""` | Kubernetes Label-Selector for pods. |
| `namespace-label-selector` | `false` | `""` | Kubernetes Label-Selector for namespaces. |
| `fallback-image-pull-secret` | `false` | `""` | Kubernetes Pull-Secret Name to load as a fallback when all others fail (must be in the same namespace as the sbom-operator) |
| `registry-proxy` | `false` | `[]` | Proxy-Registry-Hosts to use. Flag can be used multiple times. Value-Mapping e.g. `docker.io=ghcr.io` |


### Example Helm-Config
Expand Down
2 changes: 2 additions & 0 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Config struct {
OciUser string `yaml:"ociUser" env:"SBOM_OCI_USER" flag:"oci-user"`
OciToken string `yaml:"ociToken" env:"SBOM_OCI_TOKEN" flag:"oci-token"`
FallbackPullSecret string `yaml:"fallbackPullSecret" env:"SBOM_FALLBACK_PULL_SECRET" flag:"fallback-pull-secret"`
RegistryProxies []string `yaml:"registryProxy" env:"SBOM_REGISTRY_PROXY" flag:"registry-proxy"`
Verbosity string `env:"SBOM_VERBOSITY" flag:"verbosity"`
}

Expand Down Expand Up @@ -66,6 +67,7 @@ var (
ConfigKeyOciUser = "oci-user"
ConfigKeyOciToken = "oci-token"
ConfigKeyFallbackPullSecret = "fallback-pull-secret"
ConfigKeyRegistryProxy = "registry-proxy"

OperatorConfig *Config
)
2 changes: 1 addition & 1 deletion internal/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func Start(cronTime string) {
logrus.Debugf("Cron set to: %v", cr)

k8s := kubernetes.NewClient(internal.OperatorConfig.IgnoreAnnotations, internal.OperatorConfig.FallbackPullSecret)
sy := syft.New(internal.OperatorConfig.Format)
sy := syft.New(internal.OperatorConfig.Format, libstandard.ToMap(internal.OperatorConfig.RegistryProxies))
processor := processor.New(k8s, sy)

cs := CronService{cron: cr, processor: processor}
Expand Down
57 changes: 50 additions & 7 deletions internal/syft/syft.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,22 @@ import (
"github.com/anchore/syft/syft/source"
"github.com/ckotzbauer/libk8soci/pkg/oci"
"github.com/sirupsen/logrus"

parser "github.com/novln/docker-parser"
"github.com/novln/docker-parser/docker"
)

type Syft struct {
sbomFormat string
resolveVersion func() string
sbomFormat string
resolveVersion func() string
proxyRegistryMap map[string]string
}

func New(sbomFormat string) *Syft {
func New(sbomFormat string, proxyRegistryMap map[string]string) *Syft {
return &Syft{
sbomFormat: sbomFormat,
resolveVersion: getSyftVersion,
sbomFormat: sbomFormat,
resolveVersion: getSyftVersion,
proxyRegistryMap: proxyRegistryMap,
}
}

Expand All @@ -34,14 +39,18 @@ func (s Syft) WithVersion(version string) Syft {

func (s *Syft) ExecuteSyft(img *oci.RegistryImage) (string, error) {
logrus.Infof("Processing image %s", img.ImageID)
err := s.ApplyProxyRegistry(img)
if err != nil {
return "", err
}

input, err := source.ParseInput(fmt.Sprintf("registry:%s", img.ImageID), "", false)
if err != nil {
logrus.WithError(fmt.Errorf("failed to parse input registry:%s: %w", img.ImageID, err)).Error("Input-Parsing failed")
return "", err
}

opts := &image.RegistryOptions{Credentials: convertSecrets(*img)}
opts := &image.RegistryOptions{Credentials: s.convertSecrets(*img)}
src, cleanup, err := source.New(*input, opts, nil)
if err != nil {
logrus.WithError(fmt.Errorf("failed to construct source from input registry:%s: %w", img.ImageID, err)).Error("Source-Creation failed")
Expand Down Expand Up @@ -115,7 +124,7 @@ func getSyftVersion() string {
return ""
}

func convertSecrets(img oci.RegistryImage) []image.RegistryCredentials {
func (s *Syft) convertSecrets(img oci.RegistryImage) []image.RegistryCredentials {
credentials := make([]image.RegistryCredentials, 0)
for _, secret := range img.PullSecrets {
cfg, err := oci.ResolveAuthConfigWithPullSecret(img, *secret)
Expand All @@ -124,6 +133,13 @@ func convertSecrets(img oci.RegistryImage) []image.RegistryCredentials {
continue
}

for registryToReplace, proxyRegistry := range s.proxyRegistryMap {
if cfg.ServerAddress == registryToReplace ||
(strings.Contains(cfg.ServerAddress, docker.DefaultHostname) && strings.Contains(registryToReplace, docker.DefaultHostname)) {
cfg.ServerAddress = proxyRegistry
}
}

credentials = append(credentials, image.RegistryCredentials{
Username: cfg.Username,
Password: cfg.Password,
Expand All @@ -134,3 +150,30 @@ func convertSecrets(img oci.RegistryImage) []image.RegistryCredentials {

return credentials
}

func (s *Syft) ApplyProxyRegistry(img *oci.RegistryImage) error {
imageRef, err := parser.Parse(img.ImageID)
if err != nil {
logrus.WithError(err).Errorf("Could not parse image %s", img.ImageID)
return err
}

for registryToReplace, proxyRegistry := range s.proxyRegistryMap {
if imageRef.Registry() == registryToReplace {
shortName := strings.TrimPrefix(imageRef.ShortName(), docker.DefaultRepoPrefix)
fullName := fmt.Sprintf("%s/%s", imageRef.Registry(), shortName)
if strings.HasPrefix(imageRef.Tag(), "sha256") {
fullName = fmt.Sprintf("%s@%s", fullName, imageRef.Tag())
} else {
fullName = fmt.Sprintf("%s:%s", fullName, imageRef.Tag())
}

img.ImageID = strings.ReplaceAll(fullName, registryToReplace, proxyRegistry)
img.Image = strings.ReplaceAll(img.Image, registryToReplace, proxyRegistry)
logrus.Debugf("Applied Registry-Proxy %s", img.ImageID)
break
}
}

return nil
}
58 changes: 54 additions & 4 deletions internal/syft/syft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ type simpleTestData struct {
expected string
}

type mapTestData struct {
input string
expected string
dataMap map[string]string
}

type testData struct {
image string
digest string
Expand Down Expand Up @@ -59,7 +65,7 @@ func marshalCyclonedx(t *testing.T, x interface{}) string {

func testJsonSbom(t *testing.T, name, imageID string) {
format := "json"
s := syft.New(format).WithVersion("v9.9.9")
s := syft.New(format, map[string]string{}).WithVersion("v9.9.9")
sbom, err := s.ExecuteSyft(&oci.RegistryImage{ImageID: imageID, PullSecrets: []*oci.KubeCreds{}})

assert.NoError(t, err)
Expand All @@ -83,7 +89,7 @@ func testJsonSbom(t *testing.T, name, imageID string) {

func testCyclonedxSbom(t *testing.T, name, imageID string) {
format := "cyclonedx"
s := syft.New(format).WithVersion("v9.9.9")
s := syft.New(format, map[string]string{}).WithVersion("v9.9.9")
sbom, err := s.ExecuteSyft(&oci.RegistryImage{ImageID: imageID, PullSecrets: []*oci.KubeCreds{}})
assert.NoError(t, err)

Expand All @@ -103,7 +109,7 @@ func testCyclonedxSbom(t *testing.T, name, imageID string) {

func testSpdxSbom(t *testing.T, name, imageID string) {
format := "spdxjson"
s := syft.New(format).WithVersion("v9.9.9")
s := syft.New(format, map[string]string{}).WithVersion("v9.9.9")
sbom, err := s.ExecuteSyft(&oci.RegistryImage{ImageID: imageID, PullSecrets: []*oci.KubeCreds{}})
assert.NoError(t, err)

Expand All @@ -127,7 +133,7 @@ func testSpdxSbom(t *testing.T, name, imageID string) {
// test for analysing an image completely without pullSecret
func testCyclonedxSbomWithoutPullSecrets(t *testing.T, name, imageID string) {
format := "cyclonedx"
s := syft.New(format).WithVersion("v9.9.9")
s := syft.New(format, map[string]string{}).WithVersion("v9.9.9")
sbom, err := s.ExecuteSyft(&oci.RegistryImage{ImageID: imageID, PullSecrets: []*oci.KubeCreds{}})
assert.NoError(t, err)

Expand Down Expand Up @@ -251,3 +257,47 @@ func TestGetFileName(t *testing.T) {
})
}
}

func TestApplyProxyRegistry(t *testing.T) {
tests := []mapTestData{
{
input: "alpine:3.17",
expected: "alpine:3.17",
dataMap: make(map[string]string),
},
{
input: "docker.io/alpine:3.17",
expected: "ghcr.io/alpine:3.17",
dataMap: map[string]string{"docker.io": "ghcr.io"},
},
{
input: "alpine:3.17",
expected: "ghcr.io/alpine:3.17",
dataMap: map[string]string{"docker.io": "ghcr.io"},
},
{
input: "alpine:3.17",
expected: "alpine:3.17",
dataMap: map[string]string{"ghcr.io": "docker.io"},
},
{
input: "alpine:3.17",
expected: "my.registry.com:5000/alpine:3.17",
dataMap: map[string]string{"docker.io": "my.registry.com:5000"},
},
{
input: "my.registry.com:5000/alpine:3.17",
expected: "ghcr.io/alpine:3.17",
dataMap: map[string]string{"my.registry.com:5000": "ghcr.io"},
},
}

for _, v := range tests {
t.Run(v.input, func(t *testing.T) {
s := syft.New("json", v.dataMap)
img := &oci.RegistryImage{ImageID: v.input, Image: v.input}
s.ApplyProxyRegistry(img)
assert.Equal(t, v.expected, img.ImageID)
})
}
}
5 changes: 3 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func newRootCmd() *cobra.Command {
daemon.Start(internal.OperatorConfig.Cron)
} else {
k8s := kubernetes.NewClient(internal.OperatorConfig.IgnoreAnnotations, internal.OperatorConfig.FallbackPullSecret)
sy := syft.New(internal.OperatorConfig.Format)
sy := syft.New(internal.OperatorConfig.Format, libstandard.ToMap(internal.OperatorConfig.RegistryProxies))
p := processor.New(k8s, sy)
p.ListenForPods()
}
Expand All @@ -53,7 +53,7 @@ func newRootCmd() *cobra.Command {
libstandard.AddVerbosityFlag(rootCmd)
rootCmd.PersistentFlags().String(internal.ConfigKeyCron, "", "Backround-Service interval (CRON)")
rootCmd.PersistentFlags().String(internal.ConfigKeyFormat, "json", "SBOM-Format.")
rootCmd.PersistentFlags().StringSlice(internal.ConfigKeyTargets, []string{"git"}, "Targets for created SBOMs (git, dtrack).")
rootCmd.PersistentFlags().StringSlice(internal.ConfigKeyTargets, []string{"git"}, "Targets for created SBOMs (git, dtrack, oci, configmap).")
rootCmd.PersistentFlags().Bool(internal.ConfigKeyIgnoreAnnotations, false, "Force analyzing of all images, including those from annotated pods.")
rootCmd.PersistentFlags().String(internal.ConfigKeyGitWorkingTree, "/work", "Directory to place the git-repo.")
rootCmd.PersistentFlags().String(internal.ConfigKeyGitRepository, "", "Git-Repository-URL (HTTPS).")
Expand All @@ -76,6 +76,7 @@ func newRootCmd() *cobra.Command {
rootCmd.PersistentFlags().String(internal.ConfigKeyJobImage, "", "Custom Job-Image")
rootCmd.PersistentFlags().String(internal.ConfigKeyJobImagePullSecret, "", "Custom Job-Image-Pull-Secret")
rootCmd.PersistentFlags().String(internal.ConfigKeyFallbackPullSecret, "", "Fallback-Pull-Secret")
rootCmd.PersistentFlags().StringSlice(internal.ConfigKeyRegistryProxy, []string{}, "Registry-Proxy")
rootCmd.PersistentFlags().Int64(internal.ConfigKeyJobTimeout, 60*60, "Job-Timeout")
rootCmd.PersistentFlags().String(internal.ConfigKeyOciRegistry, "", "OCI-Registry")
rootCmd.PersistentFlags().String(internal.ConfigKeyOciUser, "", "OCI-User")
Expand Down

0 comments on commit 437dfce

Please sign in to comment.