From 016a0ce2f52d2805696ff0b542bcf7e90b26a573 Mon Sep 17 00:00:00 2001 From: Pragyesh Mishra Date: Mon, 6 Jan 2025 07:59:59 +0000 Subject: [PATCH] [feat]: [AH-724]: ECR upstream proxy support (#3181) * [fix]: [AH-724]: pr checks * [fix]: [AH-724]: pr checks * [fix]: [AH-724]: pr checks * [fix]: [AH-724]: fix upstream proxy update * [fix]: [AH-724]: fix upstream proxy update * [fix]: [AH-724]: fix upstream proxy update * Merge branch 'main' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into AH-724-schema-change * [feat]: [AH-724]: ECR upstream proxy support --- .golangci.yml | 4 + ...gs_add_username_secret_identifier.down.sql | 2 + ...figs_add_username_secret_identifier.up.sql | 2 + ...gs_add_username_secret_identifier.down.sql | 2 + ...figs_add_username_secret_identifier.up.sql | 2 + registry/app/api/controller/metadata/base.go | 12 + .../controller/metadata/create_registry.go | 43 +++- .../controller/metadata/update_registry.go | 45 +++- registry/app/api/controller/metadata/utils.go | 1 + registry/app/api/openapi/api.yaml | 24 +- .../contracts/artifact/services.gen.go | 137 +++++----- .../openapi/contracts/artifact/types.gen.go | 46 +++- registry/app/remote/adapter/awsecr/adapter.go | 96 ++++++++ registry/app/remote/adapter/awsecr/auth.go | 233 ++++++++++++++++++ .../app/remote/adapter/dockerhub/adapter.go | 3 +- registry/app/remote/adapter/native/adapter.go | 9 + .../app/remote/controller/proxy/remote.go | 7 +- registry/app/store/database/upstream_proxy.go | 159 +++++++----- registry/types/upstream_proxy_config.go | 71 +++--- 19 files changed, 719 insertions(+), 179 deletions(-) create mode 100644 app/store/database/migrate/postgres/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.down.sql create mode 100644 app/store/database/migrate/postgres/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.up.sql create mode 100644 app/store/database/migrate/sqlite/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.down.sql create mode 100644 app/store/database/migrate/sqlite/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.up.sql create mode 100644 registry/app/remote/adapter/awsecr/adapter.go create mode 100644 registry/app/remote/adapter/awsecr/auth.go diff --git a/.golangci.yml b/.golangci.yml index 1bb0b1ee7..144013072 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -460,6 +460,10 @@ issues: linters: [ goheader ] - path: "^registry/app/remote/adapter/dockerhub/adapter.go" linters: [ goheader ] + - path: "^registry/app/remote/adapter/awsecr/adapter.go" + linters: [ goheader ] + - path: "^registry/app/remote/adapter/awsecr/auth.go" + linters: [ goheader ] - path: "^registry/app/remote/adapter/dockerhub/client.go" linters: [ goheader ] - path: "^registry/app/remote/adapter/dockerhub/consts.go" diff --git a/app/store/database/migrate/postgres/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.down.sql b/app/store/database/migrate/postgres/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.down.sql new file mode 100644 index 000000000..96e4217cc --- /dev/null +++ b/app/store/database/migrate/postgres/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE upstream_proxy_configs DROP COLUMN upstream_proxy_config_user_name_secret_space_id; +ALTER TABLE upstream_proxy_configs DROP COLUMN upstream_proxy_config_user_name_secret_identifier; diff --git a/app/store/database/migrate/postgres/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.up.sql b/app/store/database/migrate/postgres/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.up.sql new file mode 100644 index 000000000..95a9be8aa --- /dev/null +++ b/app/store/database/migrate/postgres/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE upstream_proxy_configs ADD COLUMN upstream_proxy_config_user_name_secret_space_id INTEGER; +ALTER TABLE upstream_proxy_configs ADD COLUMN upstream_proxy_config_user_name_secret_identifier TEXT; \ No newline at end of file diff --git a/app/store/database/migrate/sqlite/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.down.sql b/app/store/database/migrate/sqlite/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.down.sql new file mode 100644 index 000000000..96e4217cc --- /dev/null +++ b/app/store/database/migrate/sqlite/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE upstream_proxy_configs DROP COLUMN upstream_proxy_config_user_name_secret_space_id; +ALTER TABLE upstream_proxy_configs DROP COLUMN upstream_proxy_config_user_name_secret_identifier; diff --git a/app/store/database/migrate/sqlite/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.up.sql b/app/store/database/migrate/sqlite/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.up.sql new file mode 100644 index 000000000..95a9be8aa --- /dev/null +++ b/app/store/database/migrate/sqlite/0093_alter_table_upstream_proxy_configs_add_username_secret_identifier.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE upstream_proxy_configs ADD COLUMN upstream_proxy_config_user_name_secret_space_id INTEGER; +ALTER TABLE upstream_proxy_configs ADD COLUMN upstream_proxy_config_user_name_secret_identifier TEXT; \ No newline at end of file diff --git a/registry/app/api/controller/metadata/base.go b/registry/app/api/controller/metadata/base.go index 83429afc2..a37d75a50 100644 --- a/registry/app/api/controller/metadata/base.go +++ b/registry/app/api/controller/metadata/base.go @@ -364,6 +364,18 @@ func CreateUpstreamProxyResponseJSONResponse(upstreamproxy *types.UpstreamProxy) auth.SecretIdentifier = &upstreamproxy.SecretIdentifier auth.SecretSpacePath = &upstreamproxy.SecretSpacePath _ = configAuth.FromUserPassword(auth) + } else if api.AuthType(upstreamproxy.RepoAuthType) == api.AuthTypeAccessKeySecretKey { + auth := api.AccessKeySecretKey{} + auth.AccessKey = &upstreamproxy.UserName + auth.AccessKeySecretIdentifier = &upstreamproxy.UserNameSecretIdentifier + auth.AccessKeySecretSpacePath = &upstreamproxy.UserNameSecretSpacePath + auth.SecretKeyIdentifier = upstreamproxy.SecretIdentifier + auth.SecretKeySpacePath = &upstreamproxy.SecretSpacePath + err := configAuth.FromAccessKeySecretKey(auth) + if err != nil { + log.Warn().Msgf("error in converting auth config to access and secret key: %v", err) + return &api.RegistryResponseJSONResponse{} + } } source := api.UpstreamConfigSource(upstreamproxy.Source) diff --git a/registry/app/api/controller/metadata/create_registry.go b/registry/app/api/controller/metadata/create_registry.go index a3cdcfb24..7da944790 100644 --- a/registry/app/api/controller/metadata/create_registry.go +++ b/registry/app/api/controller/metadata/create_registry.go @@ -267,6 +267,7 @@ func CreateRegistryEntity( return entity, nil } +//nolint:gocognit,cyclop func (c *APIController) CreateUpstreamProxyEntity( ctx context.Context, dto artifact.RegistryRequest, parentID int64, rootParentID int64, ) (*registrytypes.Registry, *registrytypes.UpstreamProxyConfig, error) { @@ -316,6 +317,7 @@ func (c *APIController) CreateUpstreamProxyEntity( } upstreamProxyConfigEntity.Source = string(*config.Source) } + //nolint:nestif if config.AuthType == artifact.AuthTypeUserPassword { res, err := config.Auth.AsUserPassword() if err != nil { @@ -326,12 +328,47 @@ func (c *APIController) CreateUpstreamProxyEntity( return nil, nil, fmt.Errorf("failed to create upstream proxy: secret_identifier missing") } - upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretID(ctx, res.SecretSpacePath) - if err != nil { - return nil, nil, err + if res.SecretSpacePath != nil && len(*res.SecretSpacePath) > 0 { + upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretID(ctx, res.SecretSpacePath) + if err != nil { + return nil, nil, err + } + } else if res.SecretSpaceId != nil { + upstreamProxyConfigEntity.SecretSpaceID = *res.SecretSpaceId } upstreamProxyConfigEntity.SecretIdentifier = *res.SecretIdentifier + } else if config.AuthType == artifact.AuthTypeAccessKeySecretKey { + res, err := config.Auth.AsAccessKeySecretKey() + if err != nil { + return nil, nil, err + } + switch { + case res.AccessKey != nil && len(*res.AccessKey) > 0: + upstreamProxyConfigEntity.UserName = *res.AccessKey + case res.AccessKeySecretIdentifier == nil: + return nil, nil, fmt.Errorf("failed to create upstream proxy: access_key_secret_identifier missing") + default: + if res.AccessKeySecretSpacePath != nil && len(*res.AccessKeySecretSpacePath) > 0 { + upstreamProxyConfigEntity.UserNameSecretSpaceID, err = c.getSecretID(ctx, res.AccessKeySecretSpacePath) + if err != nil { + return nil, nil, err + } + } else if res.AccessKeySecretSpaceId != nil { + upstreamProxyConfigEntity.UserNameSecretSpaceID = *res.AccessKeySecretSpaceId + } + upstreamProxyConfigEntity.UserNameSecretIdentifier = *res.AccessKeySecretIdentifier + } + + if res.SecretKeySpacePath != nil && len(*res.SecretKeySpacePath) > 0 { + upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretID(ctx, res.SecretKeySpacePath) + if err != nil { + return nil, nil, err + } + } else if res.SecretKeySpaceId != nil { + upstreamProxyConfigEntity.SecretSpaceID = *res.SecretKeySpaceId + } + upstreamProxyConfigEntity.SecretIdentifier = res.SecretKeyIdentifier } return repoEntity, upstreamProxyConfigEntity, nil } diff --git a/registry/app/api/controller/metadata/update_registry.go b/registry/app/api/controller/metadata/update_registry.go index 8c38d6f11..bea977e94 100644 --- a/registry/app/api/controller/metadata/update_registry.go +++ b/registry/app/api/controller/metadata/update_registry.go @@ -328,6 +328,7 @@ func UpdateRepoEntity( return entity, nil } +//nolint:gocognit,cyclop func (c *APIController) UpdateUpstreamProxyEntity( ctx context.Context, dto artifact.RegistryRequest, parentID int64, rootParentID int64, u *types.UpstreamProxy, ) (*types.Registry, *types.UpstreamProxyConfig, error) { @@ -379,7 +380,8 @@ func (c *APIController) UpdateUpstreamProxyEntity( if u.ID != -1 { upstreamProxyConfigEntity.ID = u.ID } - if config.AuthType == artifact.AuthTypeUserPassword { + switch { + case config.AuthType == artifact.AuthTypeUserPassword: res, err := config.Auth.AsUserPassword() if err != nil { return nil, nil, err @@ -389,12 +391,47 @@ func (c *APIController) UpdateUpstreamProxyEntity( return nil, nil, fmt.Errorf("failed to create upstream proxy: secret_identifier missing") } - upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretID(ctx, res.SecretSpacePath) + if res.SecretSpacePath != nil && len(*res.SecretSpacePath) > 0 { + upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretID(ctx, res.SecretSpacePath) + if err != nil { + return nil, nil, err + } + } else if res.SecretSpaceId != nil { + upstreamProxyConfigEntity.SecretSpaceID = *res.SecretSpaceId + } + upstreamProxyConfigEntity.SecretIdentifier = *res.SecretIdentifier + case config.AuthType == artifact.AuthTypeAccessKeySecretKey: + res, err := config.Auth.AsAccessKeySecretKey() if err != nil { return nil, nil, err } - upstreamProxyConfigEntity.SecretIdentifier = *res.SecretIdentifier - } else { + switch { + case res.AccessKey != nil && len(*res.AccessKey) > 0: + upstreamProxyConfigEntity.UserName = *res.AccessKey + case res.AccessKeySecretIdentifier == nil: + return nil, nil, fmt.Errorf("failed to create upstream proxy: access_key_secret_identifier missing") + default: + if res.AccessKeySecretSpacePath != nil && len(*res.AccessKeySecretSpacePath) > 0 { + upstreamProxyConfigEntity.UserNameSecretSpaceID, err = c.getSecretID(ctx, res.AccessKeySecretSpacePath) + if err != nil { + return nil, nil, err + } + } else if res.AccessKeySecretSpaceId != nil { + upstreamProxyConfigEntity.UserNameSecretSpaceID = *res.AccessKeySecretSpaceId + } + upstreamProxyConfigEntity.UserNameSecretIdentifier = *res.AccessKeySecretIdentifier + } + + if res.SecretKeySpacePath != nil && len(*res.SecretKeySpacePath) > 0 { + upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretID(ctx, res.SecretKeySpacePath) + if err != nil { + return nil, nil, err + } + } else if res.SecretKeySpaceId != nil { + upstreamProxyConfigEntity.SecretSpaceID = *res.SecretKeySpaceId + } + upstreamProxyConfigEntity.SecretIdentifier = res.SecretKeyIdentifier + default: upstreamProxyConfigEntity.UserName = "" upstreamProxyConfigEntity.SecretIdentifier = "" upstreamProxyConfigEntity.SecretSpaceID = 0 diff --git a/registry/app/api/controller/metadata/utils.go b/registry/app/api/controller/metadata/utils.go index 89e215f41..05a13064e 100644 --- a/registry/app/api/controller/metadata/utils.go +++ b/registry/app/api/controller/metadata/utils.go @@ -107,6 +107,7 @@ var validPackageTypes = []string{ var validUpstreamSources = []string{ string(a.UpstreamConfigSourceCustom), string(a.UpstreamConfigSourceDockerhub), + string(a.UpstreamConfigSourceAwsEcr), } func ValidatePackageTypes(packageTypes []string) error { diff --git a/registry/app/api/openapi/api.yaml b/registry/app/api/openapi/api.yaml index 7b8658f5d..773654bc1 100644 --- a/registry/app/api/openapi/api.yaml +++ b/registry/app/api/openapi/api.yaml @@ -1490,6 +1490,7 @@ components: oneOf: - $ref: "#/components/schemas/UserPassword" - $ref: "#/components/schemas/Anonymous" + - $ref: "#/components/schemas/AccessKeySecretKey" url: type: string source: @@ -1497,6 +1498,7 @@ components: enum: - Dockerhub - Custom + - AwsEcr x-discriminator-value: UPSTREAM required: - authType @@ -1544,6 +1546,7 @@ components: description: "Authentication type" enum: - UserPassword + - AccessKeySecretKey - Anonymous ClientSetupStepType: type: string @@ -1625,9 +1628,28 @@ components: type: string secretSpacePath: type: string + secretSpaceId: + type: integer required: - userName - - password + AccessKeySecretKey: + properties: + accessKey: + type: string + accessKeySecretIdentifier: + type: string + accessKeySecretSpacePath: + type: string + accessKeySecretSpaceId: + type: integer + secretKeyIdentifier: + type: string + secretKeySpacePath: + type: string + secretKeySpaceId: + type: integer + required: + - secretKeyIdentifier Anonymous: {} parameters: spaceRefQueryParam: diff --git a/registry/app/api/openapi/contracts/artifact/services.gen.go b/registry/app/api/openapi/contracts/artifact/services.gen.go index a5c80c9af..41d75c44d 100644 --- a/registry/app/api/openapi/contracts/artifact/services.gen.go +++ b/registry/app/api/openapi/contracts/artifact/services.gen.go @@ -1,6 +1,6 @@ // Package artifact provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT. +// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.1.0 DO NOT EDIT. package artifact import ( @@ -4179,73 +4179,74 @@ func (sh *strictHandler) GetAllRegistries(w http.ResponseWriter, r *http.Request // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "2HfKFt6eAAA=", + "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", + "ePry9J8AAAD//7UL4SbfoAAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/registry/app/api/openapi/contracts/artifact/types.gen.go b/registry/app/api/openapi/contracts/artifact/types.gen.go index 5b335f734..10dabf287 100644 --- a/registry/app/api/openapi/contracts/artifact/types.gen.go +++ b/registry/app/api/openapi/contracts/artifact/types.gen.go @@ -1,6 +1,6 @@ // Package artifact provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT. +// Code generated by github.com/deepmap/oapi-codegen/v2 version v2.1.0 DO NOT EDIT. package artifact import ( @@ -13,8 +13,9 @@ import ( // Defines values for AuthType. const ( - AuthTypeAnonymous AuthType = "Anonymous" - AuthTypeUserPassword AuthType = "UserPassword" + AuthTypeAccessKeySecretKey AuthType = "AccessKeySecretKey" + AuthTypeAnonymous AuthType = "Anonymous" + AuthTypeUserPassword AuthType = "UserPassword" ) // Defines values for ClientSetupStepType. @@ -46,6 +47,7 @@ const ( // Defines values for UpstreamConfigSource. const ( + UpstreamConfigSourceAwsEcr UpstreamConfigSource = "AwsEcr" UpstreamConfigSourceCustom UpstreamConfigSource = "Custom" UpstreamConfigSourceDockerhub UpstreamConfigSource = "Dockerhub" ) @@ -62,6 +64,17 @@ const ( GetAllRegistriesParamsTypeVIRTUAL GetAllRegistriesParamsType = "VIRTUAL" ) +// AccessKeySecretKey defines model for AccessKeySecretKey. +type AccessKeySecretKey struct { + AccessKey *string `json:"accessKey,omitempty"` + AccessKeySecretIdentifier *string `json:"accessKeySecretIdentifier,omitempty"` + AccessKeySecretSpaceId *int `json:"accessKeySecretSpaceId,omitempty"` + AccessKeySecretSpacePath *string `json:"accessKeySecretSpacePath,omitempty"` + SecretKeyIdentifier string `json:"secretKeyIdentifier"` + SecretKeySpaceId *int `json:"secretKeySpaceId,omitempty"` + SecretKeySpacePath *string `json:"secretKeySpacePath,omitempty"` +} + // Anonymous defines model for Anonymous. type Anonymous interface{} @@ -453,6 +466,7 @@ type UpstreamConfigSource string // UserPassword defines model for UserPassword. type UserPassword struct { SecretIdentifier *string `json:"secretIdentifier,omitempty"` + SecretSpaceId *int `json:"secretSpaceId,omitempty"` SecretSpacePath *string `json:"secretSpacePath,omitempty"` UserName string `json:"userName"` } @@ -1054,6 +1068,32 @@ func (t *UpstreamConfig_Auth) MergeAnonymous(v Anonymous) error { return err } +// AsAccessKeySecretKey returns the union data inside the UpstreamConfig_Auth as a AccessKeySecretKey +func (t UpstreamConfig_Auth) AsAccessKeySecretKey() (AccessKeySecretKey, error) { + var body AccessKeySecretKey + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromAccessKeySecretKey overwrites any union data inside the UpstreamConfig_Auth as the provided AccessKeySecretKey +func (t *UpstreamConfig_Auth) FromAccessKeySecretKey(v AccessKeySecretKey) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeAccessKeySecretKey performs a merge with any union data inside the UpstreamConfig_Auth, using the provided AccessKeySecretKey +func (t *UpstreamConfig_Auth) MergeAccessKeySecretKey(v AccessKeySecretKey) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + func (t UpstreamConfig_Auth) MarshalJSON() ([]byte, error) { b, err := t.union.MarshalJSON() return b, err diff --git a/registry/app/remote/adapter/awsecr/adapter.go b/registry/app/remote/adapter/awsecr/adapter.go new file mode 100644 index 000000000..cc699144e --- /dev/null +++ b/registry/app/remote/adapter/awsecr/adapter.go @@ -0,0 +1,96 @@ +// Source: https://github.com/goharbor/harbor + +// Copyright 2016 Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package awsecr + +import ( + "context" + "regexp" + + store2 "github.com/harness/gitness/app/store" + "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact" + adp "github.com/harness/gitness/registry/app/remote/adapter" + "github.com/harness/gitness/registry/app/remote/adapter/native" + "github.com/harness/gitness/registry/types" + "github.com/harness/gitness/secret" + + awsecrapi "github.com/aws/aws-sdk-go/service/ecr" + "github.com/rs/zerolog/log" +) + +const ( + //nolint:lll + ecrPattern = "https://(?:api|(\\d+)\\.dkr)\\.ecr(\\-fips)?\\.([\\w\\-]+)\\.(amazonaws\\.com(\\.cn)?|sc2s\\.sgov\\.gov|c2s\\.ic\\.gov)" +) + +var ( + ecrRegexp = regexp.MustCompile(ecrPattern) +) + +func init() { + adapterType := string(artifact.UpstreamConfigSourceAwsEcr) + if err := adp.RegisterFactory(adapterType, new(factory)); err != nil { + log.Error().Stack().Err(err).Msgf("Register adapter factory for %s", adapterType) + return + } +} + +func newAdapter( + ctx context.Context, spacePathStore store2.SpacePathStore, service secret.Service, registry types.UpstreamProxy, +) (adp.Adapter, error) { + accessKey, secretKey, err := getCreds(ctx, spacePathStore, service, registry) + if err != nil { + return nil, err + } + svc, err := getAwsSvc(accessKey, secretKey, registry) + if err != nil { + return nil, err + } + authorizer := NewAuth(accessKey, svc) + + return &adapter{ + cacheSvc: svc, + Adapter: native.NewAdapterWithAuthorizer(registry, authorizer), + }, nil +} + +// Create ... +func (f *factory) Create( + ctx context.Context, spacePathStore store2.SpacePathStore, record types.UpstreamProxy, service secret.Service, +) (adp.Adapter, error) { + return newAdapter(ctx, spacePathStore, service, record) +} + +type factory struct { +} + +// HealthCheck checks health status of a proxy. +func (a *adapter) HealthCheck() (string, error) { + return "Not implemented", nil +} + +var ( + _ adp.Adapter = (*adapter)(nil) + _ adp.ArtifactRegistry = (*adapter)(nil) +) + +type adapter struct { + *native.Adapter + cacheSvc *awsecrapi.ECR +} + +// Ensure '*adapter' implements interface 'Adapter'. +var _ adp.Adapter = (*adapter)(nil) diff --git a/registry/app/remote/adapter/awsecr/auth.go b/registry/app/remote/adapter/awsecr/auth.go new file mode 100644 index 000000000..713ca5eb5 --- /dev/null +++ b/registry/app/remote/adapter/awsecr/auth.go @@ -0,0 +1,233 @@ +// Source: https://github.com/goharbor/harbor + +// Copyright 2016 Project Harbor Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package awsecr + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "github.com/harness/gitness/app/store" + api "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact" + commonhttp "github.com/harness/gitness/registry/app/common/http" + "github.com/harness/gitness/registry/app/common/http/modifier" + "github.com/harness/gitness/registry/types" + "github.com/harness/gitness/secret" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + awsecrapi "github.com/aws/aws-sdk-go/service/ecr" + "github.com/rs/zerolog/log" +) + +// Credential ... +type Credential modifier.Modifier + +// Implements interface Credential. +type awsAuthCredential struct { + accessKey string + awssvc *awsecrapi.ECR + + cacheToken *cacheToken + cacheExpired *time.Time +} + +type cacheToken struct { + endpoint string + user string + password string + host string +} + +// DefaultCacheExpiredTime is expired timeout for aws auth token. +const DefaultCacheExpiredTime = time.Hour * 1 + +func (a *awsAuthCredential) Modify(req *http.Request) error { + // url maybe redirect to s3 + if !strings.Contains(req.URL.Host, ".ecr.") { + return nil + } + if !a.isTokenValid() { + endpoint, user, pass, expiresAt, err := a.getAuthorization(req.URL.String()) + + if err != nil { + return err + } + u, err := url.Parse(endpoint) + if err != nil { + return err + } + a.cacheToken = &cacheToken{} + a.cacheToken.host = u.Host + a.cacheToken.user = user + a.cacheToken.password = pass + a.cacheToken.endpoint = endpoint + t := time.Now().Add(DefaultCacheExpiredTime) + if t.Before(*expiresAt) { + a.cacheExpired = &t + } else { + a.cacheExpired = expiresAt + } + } + req.Host = a.cacheToken.host + req.URL.Host = a.cacheToken.host + req.SetBasicAuth(a.cacheToken.user, a.cacheToken.password) + return nil +} + +func getAwsSvc(accessKey, secretKey string, reg types.UpstreamProxy) (*awsecrapi.ECR, error) { + _, region, err := parseAccountRegion(reg.RepoURL) + if err != nil { + return nil, err + } + sess, err := session.NewSession() + if err != nil { + return nil, err + } + var cred *credentials.Credentials + log.Info().Msgf("Aws Ecr getAuthorization %s", accessKey) + if accessKey != "" { + cred = credentials.NewStaticCredentials( + accessKey, + secretKey, + "") + } + + config := &aws.Config{ + Credentials: cred, + Region: ®ion, + HTTPClient: &http.Client{ + Transport: commonhttp.GetHTTPTransport(commonhttp.WithInsecure(false)), + }, + } + + svc := awsecrapi.New(sess, config) + return svc, nil +} + +func parseAccountRegion(url string) (string, string, error) { + rs := ecrRegexp.FindStringSubmatch(url) + if rs == nil || len(rs) < 4 { + return "", "", errors.New("bad aws url") + } + return rs[1], rs[3], nil +} + +func getCreds( + ctx context.Context, spacePathStore store.SpacePathStore, secretService secret.Service, reg types.UpstreamProxy, +) (string, string, error) { + if api.AuthType(reg.RepoAuthType) != api.AuthTypeAccessKeySecretKey { + log.Debug().Msgf("invalid auth type: %s", reg.RepoAuthType) + return "", "", nil + } + secretKey, err := getSecretValue(ctx, spacePathStore, secretService, reg.SecretSpaceID, + reg.SecretIdentifier) + if err != nil { + return "", "", err + } + if reg.UserName != "" { + return reg.UserName, secretKey, nil + } + accessKey, err := getSecretValue(ctx, spacePathStore, secretService, reg.SecretSpaceID, + reg.SecretIdentifier) + if err != nil { + return "", "", err + } + return accessKey, secretKey, nil +} + +func getSecretValue(ctx context.Context, spacePathStore store.SpacePathStore, secretService secret.Service, + secretSpaceID int64, secretSpacePath string) (string, error) { + spacePath, err := spacePathStore.FindPrimaryBySpaceID(ctx, secretSpaceID) + if err != nil { + log.Error().Msgf("failed to find space path: %v", err) + return "", err + } + decryptSecret, err := secretService.DecryptSecret(ctx, spacePath.Value, secretSpacePath) + if err != nil { + log.Error().Msgf("failed to decrypt secret: %v", err) + return "", err + } + return decryptSecret, nil +} + +func (a *awsAuthCredential) getAuthorization(url string) (string, string, string, *time.Time, error) { + id, _, err := parseAccountRegion(url) + if err != nil { + return "", "", "", nil, err + } + + var input *awsecrapi.GetAuthorizationTokenInput + if id != "" { + input = &awsecrapi.GetAuthorizationTokenInput{RegistryIds: []*string{&id}} + } + svc := a.awssvc + result, err := svc.GetAuthorizationToken(input) + if err != nil { + var awsErr *awserr.Error + + if errors.As(err, awsErr) { + return "", "", "", nil, fmt.Errorf("%s", err.Error()) + } + + return "", "", "", nil, err + } + + // Double check + if len(result.AuthorizationData) == 0 { + return "", "", "", nil, errors.New("no authorization token returned") + } + + theOne := result.AuthorizationData[0] + expiresAt := theOne.ExpiresAt + payload, _ := base64.StdEncoding.DecodeString(*theOne.AuthorizationToken) + pair := strings.SplitN(string(payload), ":", 2) + + log.Debug().Msgf("Aws Ecr getAuthorization %s result: %d %s...", a.accessKey, len(pair[1]), pair[1][:25]) + + return *(theOne.ProxyEndpoint), pair[0], pair[1], expiresAt, nil +} + +func (a *awsAuthCredential) isTokenValid() bool { + if a.cacheToken == nil { + return false + } + if a.cacheExpired == nil { + return false + } + if time.Now().After(*a.cacheExpired) { + a.cacheExpired = nil + a.cacheToken = nil + return false + } + return true +} + +// NewAuth new aws auth. +func NewAuth(accessKey string, awssvc *awsecrapi.ECR) Credential { + return &awsAuthCredential{ + accessKey: accessKey, + awssvc: awssvc, + } +} diff --git a/registry/app/remote/adapter/dockerhub/adapter.go b/registry/app/remote/adapter/dockerhub/adapter.go index 35cc492fa..1f5bba1da 100644 --- a/registry/app/remote/adapter/dockerhub/adapter.go +++ b/registry/app/remote/adapter/dockerhub/adapter.go @@ -20,6 +20,7 @@ import ( "context" store2 "github.com/harness/gitness/app/store" + "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact" adp "github.com/harness/gitness/registry/app/remote/adapter" "github.com/harness/gitness/registry/app/remote/adapter/native" "github.com/harness/gitness/registry/types" @@ -29,7 +30,7 @@ import ( ) func init() { - adapterType := "docker" + adapterType := string(artifact.UpstreamConfigSourceDockerhub) if err := adp.RegisterFactory(adapterType, new(factory)); err != nil { log.Error().Stack().Err(err).Msgf("Register adapter factory for %s", adapterType) return diff --git a/registry/app/remote/adapter/native/adapter.go b/registry/app/remote/adapter/native/adapter.go index c63f2c7ad..58dde6278 100644 --- a/registry/app/remote/adapter/native/adapter.go +++ b/registry/app/remote/adapter/native/adapter.go @@ -21,6 +21,7 @@ import ( "github.com/harness/gitness/app/store" api "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact" + "github.com/harness/gitness/registry/app/common/lib" "github.com/harness/gitness/registry/app/common/lib/errors" adp "github.com/harness/gitness/registry/app/remote/adapter" "github.com/harness/gitness/registry/app/remote/clients/registry" @@ -58,6 +59,14 @@ func NewAdapter( return adapter } +// NewAdapterWithAuthorizer returns an instance of the Adapter with provided authorizer. +func NewAdapterWithAuthorizer(reg types.UpstreamProxy, authorizer lib.Authorizer) *Adapter { + return &Adapter{ + proxy: reg, + Client: registry.NewClientWithAuthorizer(reg.RepoURL, authorizer, false), + } +} + // getPwd: lookup secrets.secret_data using secret_identifier & secret_space_id. func getPwd( ctx context.Context, spacePathStore store.SpacePathStore, secretService secret.Service, reg types.UpstreamProxy, diff --git a/registry/app/remote/controller/proxy/remote.go b/registry/app/remote/controller/proxy/remote.go index c72feddfc..7105a6709 100644 --- a/registry/app/remote/controller/proxy/remote.go +++ b/registry/app/remote/controller/proxy/remote.go @@ -29,6 +29,7 @@ import ( "github.com/rs/zerolog/log" "golang.org/x/net/context" + _ "github.com/harness/gitness/registry/app/remote/adapter/awsecr" // This is required to init aws ecr adapter _ "github.com/harness/gitness/registry/app/remote/adapter/dockerhub" // This is required to init docker adapter ) @@ -68,19 +69,19 @@ func NewRemoteHelper( upstreamProxy: proxy, secretService: secretService, } - if err := r.init(ctx, spacePathStore); err != nil { + if err := r.init(ctx, spacePathStore, proxy.Source); err != nil { return nil, err } return r, nil } -func (r *remoteHelper) init(ctx context.Context, spacePathStore store.SpacePathStore) error { +func (r *remoteHelper) init(ctx context.Context, spacePathStore store.SpacePathStore, proxyType string) error { if r.registry != nil { return nil } // TODO add health check. - factory, err := adapter.GetFactory("docker") + factory, err := adapter.GetFactory(proxyType) if err != nil { return err } diff --git a/registry/app/store/database/upstream_proxy.go b/registry/app/store/database/upstream_proxy.go index 1b512ff54..09cde88ce 100644 --- a/registry/app/store/database/upstream_proxy.go +++ b/registry/app/store/database/upstream_proxy.go @@ -53,40 +53,44 @@ func NewUpstreamproxyDao( // upstreamProxyConfigDB holds the record of an upstream_proxy_config in DB. type upstreamProxyConfigDB struct { - ID int64 `db:"upstream_proxy_config_id"` - RegistryID int64 `db:"upstream_proxy_config_registry_id"` - Source string `db:"upstream_proxy_config_source"` - URL string `db:"upstream_proxy_config_url"` - AuthType string `db:"upstream_proxy_config_auth_type"` - UserName string `db:"upstream_proxy_config_user_name"` - SecretIdentifier sql.NullString `db:"upstream_proxy_config_secret_identifier"` - SecretSpaceID sql.NullInt32 `db:"upstream_proxy_config_secret_space_id"` - Token string `db:"upstream_proxy_config_token"` - CreatedAt int64 `db:"upstream_proxy_config_created_at"` - UpdatedAt int64 `db:"upstream_proxy_config_updated_at"` - CreatedBy int64 `db:"upstream_proxy_config_created_by"` - UpdatedBy int64 `db:"upstream_proxy_config_updated_by"` + ID int64 `db:"upstream_proxy_config_id"` + RegistryID int64 `db:"upstream_proxy_config_registry_id"` + Source string `db:"upstream_proxy_config_source"` + URL string `db:"upstream_proxy_config_url"` + AuthType string `db:"upstream_proxy_config_auth_type"` + UserName string `db:"upstream_proxy_config_user_name"` + SecretIdentifier sql.NullString `db:"upstream_proxy_config_secret_identifier"` + SecretSpaceID sql.NullInt32 `db:"upstream_proxy_config_secret_space_id"` + UserNameSecretIdentifier sql.NullString `db:"upstream_proxy_config_user_name_secret_identifier"` + UserNameSecretSpaceID sql.NullInt32 `db:"upstream_proxy_config_user_name_secret_space_id"` + Token string `db:"upstream_proxy_config_token"` + CreatedAt int64 `db:"upstream_proxy_config_created_at"` + UpdatedAt int64 `db:"upstream_proxy_config_updated_at"` + CreatedBy int64 `db:"upstream_proxy_config_created_by"` + UpdatedBy int64 `db:"upstream_proxy_config_updated_by"` } type upstreamProxyDB struct { - ID int64 `db:"id"` - RegistryID int64 `db:"registry_id"` - RepoKey string `db:"repo_key"` - ParentID string `db:"parent_id"` - PackageType artifact.PackageType `db:"package_type"` - AllowedPattern sql.NullString `db:"allowed_pattern"` - BlockedPattern sql.NullString `db:"blocked_pattern"` - Source string `db:"source"` - RepoURL string `db:"repo_url"` - RepoAuthType string `db:"repo_auth_type"` - UserName string `db:"user_name"` - SecretIdentifier sql.NullString `db:"secret_identifier"` - SecretSpaceID sql.NullInt32 `db:"secret_space_id"` - Token string `db:"token"` - CreatedAt int64 `db:"created_at"` - UpdatedAt int64 `db:"updated_at"` - CreatedBy sql.NullInt64 `db:"created_by"` - UpdatedBy sql.NullInt64 `db:"updated_by"` + ID int64 `db:"id"` + RegistryID int64 `db:"registry_id"` + RepoKey string `db:"repo_key"` + ParentID string `db:"parent_id"` + PackageType artifact.PackageType `db:"package_type"` + AllowedPattern sql.NullString `db:"allowed_pattern"` + BlockedPattern sql.NullString `db:"blocked_pattern"` + Source string `db:"source"` + RepoURL string `db:"repo_url"` + RepoAuthType string `db:"repo_auth_type"` + UserName string `db:"user_name"` + SecretIdentifier sql.NullString `db:"secret_identifier"` + SecretSpaceID sql.NullInt32 `db:"secret_space_id"` + UserNameSecretIdentifier sql.NullString `db:"user_name_secret_identifier"` + UserNameSecretSpaceID sql.NullInt32 `db:"user_name_secret_space_id"` + Token string `db:"token"` + CreatedAt int64 `db:"created_at"` + UpdatedAt int64 `db:"updated_at"` + CreatedBy sql.NullInt64 `db:"created_by"` + UpdatedBy sql.NullInt64 `db:"updated_by"` } func getUpstreamProxyQuery() squirrel.SelectBuilder { @@ -104,6 +108,8 @@ func getUpstreamProxyQuery() squirrel.SelectBuilder { " u.upstream_proxy_config_user_name as user_name," + " u.upstream_proxy_config_secret_identifier as secret_identifier," + " u.upstream_proxy_config_secret_space_id as secret_space_id," + + " u.upstream_proxy_config_user_name_secret_identifier as user_name_secret_identifier," + + " u.upstream_proxy_config_user_name_secret_space_id as user_name_secret_space_id," + " u.upstream_proxy_config_token as token," + " r.registry_created_at as created_at," + " r.registry_updated_at as updated_at "). @@ -189,6 +195,8 @@ func (r UpstreamproxyDao) Create( ,upstream_proxy_config_user_name ,upstream_proxy_config_secret_identifier ,upstream_proxy_config_secret_space_id + ,upstream_proxy_config_user_name_secret_identifier + ,upstream_proxy_config_user_name_secret_space_id ,upstream_proxy_config_token ,upstream_proxy_config_created_at ,upstream_proxy_config_updated_at @@ -202,6 +210,8 @@ func (r UpstreamproxyDao) Create( ,:upstream_proxy_config_user_name ,:upstream_proxy_config_secret_identifier ,:upstream_proxy_config_secret_space_id + ,:upstream_proxy_config_user_name_secret_identifier + ,:upstream_proxy_config_user_name_secret_space_id ,:upstream_proxy_config_token ,:upstream_proxy_config_created_at ,:upstream_proxy_config_updated_at @@ -360,19 +370,21 @@ func (r UpstreamproxyDao) mapToInternalUpstreamProxy( in.UpdatedBy = session.Principal.ID return &upstreamProxyConfigDB{ - ID: in.ID, - RegistryID: in.RegistryID, - Source: in.Source, - URL: in.URL, - AuthType: in.AuthType, - UserName: in.UserName, - SecretIdentifier: util.GetEmptySQLString(in.SecretIdentifier), - SecretSpaceID: util.GetEmptySQLInt32(in.SecretSpaceID), - Token: in.Token, - CreatedAt: in.CreatedAt.UnixMilli(), - UpdatedAt: in.UpdatedAt.UnixMilli(), - CreatedBy: in.CreatedBy, - UpdatedBy: in.UpdatedBy, + ID: in.ID, + RegistryID: in.RegistryID, + Source: in.Source, + URL: in.URL, + AuthType: in.AuthType, + UserName: in.UserName, + SecretIdentifier: util.GetEmptySQLString(in.SecretIdentifier), + SecretSpaceID: util.GetEmptySQLInt32(in.SecretSpaceID), + UserNameSecretSpaceID: util.GetEmptySQLInt32(in.UserNameSecretSpaceID), + UserNameSecretIdentifier: util.GetEmptySQLString(in.UserNameSecretIdentifier), + Token: in.Token, + CreatedAt: in.CreatedAt.UnixMilli(), + UpdatedAt: in.UpdatedAt.UnixMilli(), + CreatedBy: in.CreatedBy, + UpdatedBy: in.UpdatedBy, } } @@ -406,26 +418,47 @@ func (r UpstreamproxyDao) mapToUpstreamProxy( secretSpacePath = primary.Value } + userNameSecretIdentifier := "" + userNameSecretSpaceID := int64(-1) + if dst.UserNameSecretIdentifier.Valid { + userNameSecretIdentifier = dst.UserNameSecretIdentifier.String + } + if dst.UserNameSecretSpaceID.Valid { + userNameSecretSpaceID = int64(dst.UserNameSecretSpaceID.Int32) + } + + userNameSecretSpacePath := "" + if dst.UserNameSecretSpaceID.Valid { + primary, err := r.spacePathStore.FindPrimaryBySpaceID(ctx, int64(dst.UserNameSecretSpaceID.Int32)) + if err != nil { + return nil, fmt.Errorf("failed to get secret space path: %w", err) + } + userNameSecretSpacePath = primary.Value + } + return &types.UpstreamProxy{ - ID: dst.ID, - RegistryID: dst.RegistryID, - RepoKey: dst.RepoKey, - ParentID: dst.ParentID, - PackageType: dst.PackageType, - AllowedPattern: util.StringToArr(dst.AllowedPattern.String), - BlockedPattern: util.StringToArr(dst.BlockedPattern.String), - Source: dst.Source, - RepoURL: dst.RepoURL, - RepoAuthType: dst.RepoAuthType, - UserName: dst.UserName, - SecretIdentifier: secretIdentifier, - SecretSpaceID: secretSpaceID, - SecretSpacePath: secretSpacePath, - Token: dst.Token, - CreatedAt: time.UnixMilli(dst.CreatedAt), - UpdatedAt: time.UnixMilli(dst.UpdatedAt), - CreatedBy: createdBy, - UpdatedBy: updatedBy, + ID: dst.ID, + RegistryID: dst.RegistryID, + RepoKey: dst.RepoKey, + ParentID: dst.ParentID, + PackageType: dst.PackageType, + AllowedPattern: util.StringToArr(dst.AllowedPattern.String), + BlockedPattern: util.StringToArr(dst.BlockedPattern.String), + Source: dst.Source, + RepoURL: dst.RepoURL, + RepoAuthType: dst.RepoAuthType, + UserName: dst.UserName, + SecretIdentifier: secretIdentifier, + SecretSpaceID: secretSpaceID, + SecretSpacePath: secretSpacePath, + UserNameSecretIdentifier: userNameSecretIdentifier, + UserNameSecretSpaceID: userNameSecretSpaceID, + UserNameSecretSpacePath: userNameSecretSpacePath, + Token: dst.Token, + CreatedAt: time.UnixMilli(dst.CreatedAt), + UpdatedAt: time.UnixMilli(dst.UpdatedAt), + CreatedBy: createdBy, + UpdatedBy: updatedBy, }, nil } diff --git a/registry/types/upstream_proxy_config.go b/registry/types/upstream_proxy_config.go index bfefd123f..ca52f956c 100644 --- a/registry/types/upstream_proxy_config.go +++ b/registry/types/upstream_proxy_config.go @@ -22,40 +22,45 @@ import ( // UpstreamProxyConfig DTO object. type UpstreamProxyConfig struct { - ID int64 - RegistryID int64 - Source string - URL string - AuthType string - UserName string - Password string - SecretIdentifier string - SecretSpaceID int - Token string - CreatedAt time.Time - UpdatedAt time.Time - CreatedBy int64 - UpdatedBy int64 + ID int64 + RegistryID int64 + Source string + URL string + AuthType string + UserName string + UserNameSecretIdentifier string + UserNameSecretSpaceID int + Password string + SecretIdentifier string + SecretSpaceID int + Token string + CreatedAt time.Time + UpdatedAt time.Time + CreatedBy int64 + UpdatedBy int64 } type UpstreamProxy struct { - ID int64 - RegistryID int64 - RepoKey string - ParentID string - PackageType artifact.PackageType - AllowedPattern []string - BlockedPattern []string - Source string - RepoURL string - RepoAuthType string - UserName string - SecretIdentifier string - SecretSpaceID int64 - SecretSpacePath string - Token string - CreatedAt time.Time - UpdatedAt time.Time - CreatedBy int64 - UpdatedBy int64 + ID int64 + RegistryID int64 + RepoKey string + ParentID string + PackageType artifact.PackageType + AllowedPattern []string + BlockedPattern []string + Source string + RepoURL string + RepoAuthType string + UserName string + UserNameSecretIdentifier string + UserNameSecretSpaceID int64 + UserNameSecretSpacePath string + SecretIdentifier string + SecretSpaceID int64 + SecretSpacePath string + Token string + CreatedAt time.Time + UpdatedAt time.Time + CreatedBy int64 + UpdatedBy int64 }