diff --git a/registryclients/common.go b/registryclients/common.go new file mode 100644 index 0000000..03d3f24 --- /dev/null +++ b/registryclients/common.go @@ -0,0 +1,86 @@ +package registryclients + +import ( + "context" + "github.com/Masterminds/semver/v3" + "github.com/armosec/registryx/common" + "github.com/armosec/registryx/interfaces" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/v1/remote" + "slices" + "sort" + "strings" +) + +const ( + latestTag = "latest" +) + +func getAllRepositories(ctx context.Context, registry interfaces.IRegistry) ([]string, error) { + var repos, pageRepos []string + var nextPage *common.PaginationOption + var err error + + firstPage := common.MakePagination(registry.GetMaxPageSize()) + catalogOpts := common.CatalogOption{} + + for pageRepos, nextPage, err = registry.Catalog(ctx, firstPage, catalogOpts, authn.FromConfig(*registry.GetAuth())); ; pageRepos, nextPage, err = registry.Catalog(ctx, *nextPage, catalogOpts, authn.FromConfig(*registry.GetAuth())) { + if err != nil { + return nil, err + } + if len(pageRepos) == 0 { + break + } + repos = append(repos, pageRepos...) + + if nextPage == nil || nextPage.Cursor == "" { + break + } + } + return repos, nil +} + +func getImageLatestTag(repo string, registry interfaces.IRegistry) (string, error) { + firstPage := common.MakePagination(1000) + var tags []string + withAuth := remote.WithAuth(authn.FromConfig(*registry.GetAuth())) + if latestTags, err := registry.GetLatestTags(repo, 1, withAuth); err == nil { + for _, tag := range latestTags { + if strings.HasSuffix(tag, ".sig") { + continue + } + tagsForDigest := strings.Split(tag, ",") + return tagsForDigest[0], nil + } + } else { + for tagsPage, nextPage, err := registry.List(repo, firstPage, withAuth); ; tagsPage, nextPage, err = registry.List(repo, *nextPage, withAuth) { + if err != nil { + return "", err + } + + if slices.Contains(tagsPage, latestTag) { + return latestTag, nil + } + + tags = append(tags, tagsPage...) + + if nextPage == nil { + break + } + } + return getLatestTag(tags), nil + } + return "", nil +} + +func getLatestTag(tags []string) string { + var versions []*semver.Version + for _, tag := range tags { + version, err := semver.NewVersion(tag) + if err == nil { + versions = append(versions, version) + } + } + sort.Sort(sort.Reverse(semver.Collection(versions))) + return versions[0].String() +} diff --git a/registryclients/harbor.go b/registryclients/harbor.go new file mode 100644 index 0000000..4aa89b0 --- /dev/null +++ b/registryclients/harbor.go @@ -0,0 +1,59 @@ +package registryclients + +import ( + "context" + "fmt" + "github.com/armosec/armoapi-go/armotypes" + "github.com/armosec/registryx/common" + "github.com/armosec/registryx/registries/harbor" + dockerregistry "github.com/docker/docker/api/types/registry" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" +) + +type HarborRegistryClient struct { + Registry *armotypes.HarborImageRegistry +} + +func (h *HarborRegistryClient) GetAllRepositories(ctx context.Context) ([]string, error) { + registry, err := name.NewRegistry(h.Registry.InstanceURL) + if err != nil { + return nil, err + } + iRegistry, err := harbor.NewHarborRegistry(&authn.AuthConfig{Username: h.Registry.Username, Password: h.Registry.Password}, ®istry, &common.RegistryOptions{}) + if err != nil { + return nil, err + } + + return getAllRepositories(ctx, iRegistry) +} + +func (h *HarborRegistryClient) GetImagesToScan(_ context.Context) (map[string]string, error) { + registry, err := name.NewRegistry(h.Registry.InstanceURL) + if err != nil { + return nil, err + } + iRegistry, err := harbor.NewHarborRegistry(&authn.AuthConfig{Username: h.Registry.Username, Password: h.Registry.Password}, ®istry, &common.RegistryOptions{}) + if err != nil { + return nil, err + } + + images := make(map[string]string, len(h.Registry.Repositories)) + for _, repository := range h.Registry.Repositories { + tag, err := getImageLatestTag(repository, iRegistry) + if err != nil { + return nil, err + } else if tag == "" { + return nil, fmt.Errorf("failed to find latest tag for repository %s", repository) + } + images[fmt.Sprintf("%s/%s", h.Registry.InstanceURL, repository)] = tag + } + return images, nil +} + +func (h *HarborRegistryClient) GetDockerAuth() *dockerregistry.AuthConfig { + return &dockerregistry.AuthConfig{ + Username: h.Registry.Username, + Password: h.Registry.Password, + } +} diff --git a/registryclients/quay.go b/registryclients/quay.go index 5468b31..f4892ff 100644 --- a/registryclients/quay.go +++ b/registryclients/quay.go @@ -3,16 +3,11 @@ package registryclients import ( "context" "fmt" - "github.com/Masterminds/semver/v3" "github.com/armosec/armoapi-go/armotypes" - "github.com/armosec/registryx/common" - "github.com/armosec/registryx/registries" + "github.com/armosec/registryx/registries/quay" dockerregistry "github.com/docker/docker/api/types/registry" "github.com/google/go-containerregistry/pkg/authn" - "github.com/google/go-containerregistry/pkg/v1/remote" - "slices" - "sort" - "strings" + "github.com/google/go-containerregistry/pkg/name" ) type QuayRegistryClient struct { @@ -20,38 +15,34 @@ type QuayRegistryClient struct { } func (q *QuayRegistryClient) GetAllRepositories(ctx context.Context) ([]string, error) { - iRegistry, err := registries.Factory(&authn.AuthConfig{Username: q.Registry.RobotAccountName, Password: q.Registry.RobotAccountToken}, q.Registry.ContainerRegistryName, nil) + registry, err := name.NewRegistry(q.Registry.ContainerRegistryName) if err != nil { return nil, err } - var repos, pageRepos []string - var nextPage *common.PaginationOption - - firstPage := common.MakePagination(iRegistry.GetMaxPageSize()) - catalogOpts := common.CatalogOption{} - - for pageRepos, nextPage, err = iRegistry.Catalog(ctx, firstPage, catalogOpts, authn.FromConfig(*iRegistry.GetAuth())); ; pageRepos, nextPage, err = iRegistry.Catalog(ctx, *nextPage, catalogOpts, authn.FromConfig(*iRegistry.GetAuth())) { - if err != nil { - return nil, err - } - if len(pageRepos) == 0 { - break - } - repos = append(repos, pageRepos...) - - if nextPage == nil || nextPage.Cursor == "" { - break - } + iRegistry, err := quay.NewQuayIORegistry(&authn.AuthConfig{Username: q.Registry.RobotAccountName, Password: q.Registry.RobotAccountToken}, ®istry, nil) + if err != nil { + return nil, err } - return repos, nil + return getAllRepositories(ctx, iRegistry) } -func (q *QuayRegistryClient) GetImagesToScan(ctx context.Context) (map[string]string, error) { +func (q *QuayRegistryClient) GetImagesToScan(_ context.Context) (map[string]string, error) { + registry, err := name.NewRegistry(q.Registry.ContainerRegistryName) + if err != nil { + return nil, err + } + iRegistry, err := quay.NewQuayIORegistry(&authn.AuthConfig{Username: q.Registry.RobotAccountName, Password: q.Registry.RobotAccountToken}, ®istry, nil) + if err != nil { + return nil, err + } + images := make(map[string]string, len(q.Registry.Repositories)) for _, repository := range q.Registry.Repositories { - tag, err := q.getImageLatestTag(ctx, repository) + tag, err := getImageLatestTag(repository, iRegistry) if err != nil { return nil, err + } else if tag == "" { + return nil, fmt.Errorf("failed to find latest tag for repository %s", repository) } images[fmt.Sprintf("%s/%s", q.Registry.ContainerRegistryName, repository)] = tag } @@ -64,53 +55,3 @@ func (q *QuayRegistryClient) GetDockerAuth() *dockerregistry.AuthConfig { Password: q.Registry.RobotAccountToken, } } - -func (q *QuayRegistryClient) getImageLatestTag(_ context.Context, repo string) (string, error) { - iRegistry, err := registries.Factory(&authn.AuthConfig{Username: q.Registry.RobotAccountName, Password: q.Registry.RobotAccountToken}, q.Registry.ContainerRegistryName, nil) - if err != nil { - return "", err - } - - firstPage := common.MakePagination(1000) - var tags []string - options := []remote.Option{remote.WithAuth(authn.FromConfig(*iRegistry.GetAuth()))} - if latestTags, err := iRegistry.GetLatestTags(repo, 1, options...); err == nil { - for _, tag := range latestTags { - if strings.HasSuffix(tag, ".sig") { - continue - } - tagsForDigest := strings.Split(tag, ",") - return tagsForDigest[0], nil - } - } else { - for tagsPage, nextPage, err := iRegistry.List(repo, firstPage, options...); ; tagsPage, nextPage, err = iRegistry.List(repo, *nextPage) { - if err != nil { - return "", err - } - - if slices.Contains(tagsPage, "latest") { - return "latest", nil - } - - tags = append(tags, tagsPage...) - - if nextPage == nil { - break - } - } - return getLatestTag(tags), nil - } - return "", nil -} - -func getLatestTag(tags []string) string { - var versions []*semver.Version - for _, tag := range tags { - version, err := semver.NewVersion(tag) - if err == nil { - versions = append(versions, version) - } - } - sort.Sort(sort.Reverse(semver.Collection(versions))) - return versions[0].String() -}