diff --git a/go.mod b/go.mod index 5854e96..37bb027 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,12 @@ go 1.23.0 require ( github.com/Masterminds/semver/v3 v3.3.0 - github.com/armosec/armoapi-go v0.0.467 + github.com/armosec/armoapi-go v0.0.474 github.com/armosec/gojay v1.2.17 github.com/docker/docker v27.3.1+incompatible github.com/google/go-containerregistry v0.20.2 github.com/hashicorp/go-version v1.7.0 + golang.org/x/oauth2 v0.23.0 k8s.io/utils v0.0.0-20240502163921-fe8a2dddb1d0 ) @@ -18,6 +19,7 @@ require ( ) require ( + cloud.google.com/go/compute/metadata v0.5.2 // indirect github.com/armosec/utils-k8s-go v0.0.30 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect @@ -27,7 +29,7 @@ require ( github.com/docker/cli v27.1.1+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -50,7 +52,7 @@ require ( github.com/vbatts/tar-split v0.11.3 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect + golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.18.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index ede5d63..bf702dd 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= +cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= @@ -16,8 +18,8 @@ github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/armosec/armoapi-go v0.0.467 h1:HkPC4ZWMIoQo0rh0M0yFumOlo/Orrf1TvaRciVv48JI= -github.com/armosec/armoapi-go v0.0.467/go.mod h1:TruqDSAPgfRBXCeM+Cgp6nN4UhJSbe7la+XDKV2pTsY= +github.com/armosec/armoapi-go v0.0.474 h1:gMvVk7Cd4OL6wQEhWZTMDhDuiifq32MeOZVPHE4YyBI= +github.com/armosec/armoapi-go v0.0.474/go.mod h1:TruqDSAPgfRBXCeM+Cgp6nN4UhJSbe7la+XDKV2pTsY= github.com/armosec/gojay v1.2.17 h1:VSkLBQzD1c2V+FMtlGFKqWXNsdNvIKygTKJI9ysY8eM= github.com/armosec/gojay v1.2.17/go.mod h1:vuvX3DlY0nbVrJ0qCklSS733AWMoQboq3cFyuQW9ybc= github.com/armosec/utils-k8s-go v0.0.30 h1:Gj8MJck0jZPSLSq8ZMiRPT3F/laOYQdaLxXKKcjijt4= @@ -49,8 +51,8 @@ github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJn github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -205,6 +207,8 @@ golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -228,8 +232,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/interfaces/registryclient.go b/interfaces/registryclient.go index 4f59cf8..01e231f 100644 --- a/interfaces/registryclient.go +++ b/interfaces/registryclient.go @@ -8,5 +8,5 @@ import ( type RegistryClient interface { GetAllRepositories(ctx context.Context) ([]string, error) GetImagesToScan(ctx context.Context) (map[string]string, error) - GetDockerAuth() *dockerregistry.AuthConfig + GetDockerAuth() (*dockerregistry.AuthConfig, error) } diff --git a/registryclients/factory.go b/registryclients/factory.go index 4d25122..48def65 100644 --- a/registryclients/factory.go +++ b/registryclients/factory.go @@ -21,6 +21,12 @@ func GetRegistryClient(registry armotypes.ContainerImageRegistry) (interfaces.Re } else { return nil, fmt.Errorf("failed to convert registry to HarborImageRegistry type") } + case armotypes.Google: + if googleRegistry, ok := registry.(*armotypes.GoogleImageRegistry); ok { + return NewGoogleArtifactRegistryClient(googleRegistry) + } else { + return nil, fmt.Errorf("failed to convert registry to GoogleImageRegistry type") + } } return nil, fmt.Errorf("unsupported provider %s", provider) } diff --git a/registryclients/google.go b/registryclients/google.go new file mode 100644 index 0000000..8257c01 --- /dev/null +++ b/registryclients/google.go @@ -0,0 +1,103 @@ +package registryclients + +import ( + "context" + "encoding/json" + "fmt" + "github.com/armosec/armoapi-go/armotypes" + "github.com/armosec/registryx/common" + "github.com/armosec/registryx/registries/defaultregistry" + dockerregistry "github.com/docker/docker/api/types/registry" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" + "net/http" +) + +const ( + gcpScope = "https://www.googleapis.com/auth/cloud-platform" + oauth2user = "oauth2accesstoken" + accessTokenAuth = "accesstoken" +) + +type GoogleArtifactRegistryClient struct { + Registry *armotypes.GoogleImageRegistry + httpClient *http.Client + projectID string + ts oauth2.TokenSource +} + +func NewGoogleArtifactRegistryClient(registry *armotypes.GoogleImageRegistry) (*GoogleArtifactRegistryClient, error) { + jsonData, err := json.Marshal(registry.Key) + if err != nil { + return nil, fmt.Errorf("failed to marshal json key: %w", err) + } + creds, err := google.CredentialsFromJSON(context.Background(), jsonData, gcpScope) + if err != nil { + return nil, fmt.Errorf("failed to parse credentials: %w", err) + } + + return &GoogleArtifactRegistryClient{ + Registry: registry, + httpClient: oauth2.NewClient(context.Background(), creds.TokenSource), + projectID: creds.ProjectID, + ts: creds.TokenSource, + }, nil +} + +func (g *GoogleArtifactRegistryClient) GetAllRepositories(ctx context.Context) ([]string, error) { + registry, err := name.NewRegistry(g.Registry.RegistryURI) + if err != nil { + return nil, err + } + token, err := g.ts.Token() + if err != nil { + return nil, err + } + iRegistry, err := defaultregistry.NewRegistry(&authn.AuthConfig{Username: oauth2user, Password: token.AccessToken}, ®istry, &common.RegistryOptions{}) + if err != nil { + return nil, err + } + + return getAllRepositories(ctx, iRegistry) +} + +func (g *GoogleArtifactRegistryClient) GetImagesToScan(_ context.Context) (map[string]string, error) { + registry, err := name.NewRegistry(g.Registry.RegistryURI) + if err != nil { + return nil, err + } + token, err := g.ts.Token() + if err != nil { + return nil, err + } + iRegistry, err := defaultregistry.NewRegistry(&authn.AuthConfig{Username: oauth2user, Password: token.AccessToken}, ®istry, &common.RegistryOptions{}) + if err != nil { + return nil, err + } + + images := make(map[string]string, len(g.Registry.Repositories)) + for _, repository := range g.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", g.Registry.RegistryURI, repository)] = tag + } + return images, nil +} + +func (g *GoogleArtifactRegistryClient) GetDockerAuth() (*dockerregistry.AuthConfig, error) { + token, err := g.ts.Token() + if err != nil { + return nil, err + } + return &dockerregistry.AuthConfig{ + Username: oauth2user, + Password: token.AccessToken, + Auth: accessTokenAuth, + }, nil +} diff --git a/registryclients/harbor.go b/registryclients/harbor.go index 4aa89b0..e96e75a 100644 --- a/registryclients/harbor.go +++ b/registryclients/harbor.go @@ -51,9 +51,9 @@ func (h *HarborRegistryClient) GetImagesToScan(_ context.Context) (map[string]st return images, nil } -func (h *HarborRegistryClient) GetDockerAuth() *dockerregistry.AuthConfig { +func (h *HarborRegistryClient) GetDockerAuth() (*dockerregistry.AuthConfig, error) { return &dockerregistry.AuthConfig{ Username: h.Registry.Username, Password: h.Registry.Password, - } + }, nil } diff --git a/registryclients/quay.go b/registryclients/quay.go index f4892ff..bc325bc 100644 --- a/registryclients/quay.go +++ b/registryclients/quay.go @@ -49,9 +49,9 @@ func (q *QuayRegistryClient) GetImagesToScan(_ context.Context) (map[string]stri return images, nil } -func (q *QuayRegistryClient) GetDockerAuth() *dockerregistry.AuthConfig { +func (q *QuayRegistryClient) GetDockerAuth() (*dockerregistry.AuthConfig, error) { return &dockerregistry.AuthConfig{ Username: q.Registry.RobotAccountName, Password: q.Registry.RobotAccountToken, - } + }, nil }