diff --git a/make/harbor.yml.tmpl b/make/harbor.yml.tmpl index 2d6655c65f67..b5df0bac9495 100644 --- a/make/harbor.yml.tmpl +++ b/make/harbor.yml.tmpl @@ -26,9 +26,6 @@ https: # Remember Change the admin password from UI after launching Harbor. harbor_admin_password: Harbor12345 -#TODO: remove this temporary flag before ships v1.11/v2, this should always be true -registry_use_basic_auth: false - # Harbor DB configuration database: # The password for the root user of Harbor DB. Change this before any production use. diff --git a/make/photon/prepare/templates/registry/config.yml.jinja b/make/photon/prepare/templates/registry/config.yml.jinja index e99a27b5cda9..d3e6b9a4c124 100644 --- a/make/photon/prepare/templates/registry/config.yml.jinja +++ b/make/photon/prepare/templates/registry/config.yml.jinja @@ -26,17 +26,9 @@ http: debug: addr: localhost:5001 auth: -{% if registry_use_basic_auth %} htpasswd: realm: harbor-registry-basic-realm path: /etc/registry/passwd -{% else %} - token: - issuer: harbor-token-issuer - realm: {{public_url}}/service/token - rootcertbundle: /etc/registry/root.crt - service: harbor-registry -{% endif %} validation: disabled: true notifications: diff --git a/make/photon/prepare/utils/configs.py b/make/photon/prepare/utils/configs.py index e6e684da68e5..df24f8122b11 100644 --- a/make/photon/prepare/utils/configs.py +++ b/make/photon/prepare/utils/configs.py @@ -327,10 +327,6 @@ def parse_yaml_config(config_file_path, with_notary, with_clair, with_chartmuseu config_dict['registry_username'] = REGISTRY_USER_NAME config_dict['registry_password'] = generate_random_string(32) - - # TODO: remove the flag before release - config_dict['registry_use_basic_auth'] = configs['registry_use_basic_auth'] - return config_dict diff --git a/make/photon/prepare/utils/registry.py b/make/photon/prepare/utils/registry.py index 932ef563bb53..f4c86796ae59 100644 --- a/make/photon/prepare/utils/registry.py +++ b/make/photon/prepare/utils/registry.py @@ -24,8 +24,7 @@ def prepare_registry(config_dict): prepare_dir(registry_data_dir, uid=DEFAULT_UID, gid=DEFAULT_GID) prepare_dir(registry_config_dir) - if config_dict['registry_use_basic_auth']: - gen_passwd_file(config_dict) + gen_passwd_file(config_dict) storage_provider_info = get_storage_provider_info( config_dict['storage_provider_name'], config_dict['storage_provider_config']) diff --git a/src/api/artifact/abstractor/blob/fetcher.go b/src/api/artifact/abstractor/blob/fetcher.go index 58cb03a14ea1..b03de9c802a6 100644 --- a/src/api/artifact/abstractor/blob/fetcher.go +++ b/src/api/artifact/abstractor/blob/fetcher.go @@ -21,7 +21,6 @@ import ( "github.com/goharbor/harbor/src/common/utils/registry" "github.com/goharbor/harbor/src/common/utils/registry/auth" "github.com/goharbor/harbor/src/core/config" - "github.com/goharbor/harbor/src/core/service/token" coreutils "github.com/goharbor/harbor/src/core/utils" v1 "github.com/opencontainers/image-spec/specs-go/v1" "io/ioutil" @@ -86,7 +85,7 @@ func newRepositoryClient(repository string) (*registry.Repository, error) { uam := &auth.UserAgentModifier{ UserAgent: "harbor-registry-client", } - authorizer := auth.NewRawTokenAuthorizer("admin", token.Registry) + authorizer := auth.DefaultBasicAuthorizer() transport := registry.NewTransport(http.DefaultTransport, authorizer, uam) client := &http.Client{ Transport: transport, diff --git a/src/common/utils/registry/auth/basicauthorizer.go b/src/common/utils/registry/auth/basicauthorizer.go new file mode 100644 index 000000000000..c633a4b40043 --- /dev/null +++ b/src/common/utils/registry/auth/basicauthorizer.go @@ -0,0 +1,40 @@ +// Copyright 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 auth + +import ( + "github.com/goharbor/harbor/src/common/http/modifier" + "github.com/goharbor/harbor/src/core/config" + "sync" +) + +// NewBasicAuthorizer create an authorizer to add basic auth header as is set in the parameter +func NewBasicAuthorizer(u, p string) modifier.Modifier { + return NewBasicAuthCredential(u, p) +} + +var ( + defaultAuthorizer modifier.Modifier + once sync.Once +) + +// DefaultBasicAuthorizer returns the basic authorizer that sets the basic auth as configured in env variables +func DefaultBasicAuthorizer() modifier.Modifier { + once.Do(func() { + u, p := config.RegistryCredential() + defaultAuthorizer = NewBasicAuthCredential(u, p) + }) + return defaultAuthorizer +} diff --git a/src/common/utils/registry/auth/basicauthorizer_test.go b/src/common/utils/registry/auth/basicauthorizer_test.go new file mode 100644 index 000000000000..f68c03a3044b --- /dev/null +++ b/src/common/utils/registry/auth/basicauthorizer_test.go @@ -0,0 +1,39 @@ +// Copyright 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 auth + +import ( + "github.com/stretchr/testify/assert" + "net/http" + "os" + "testing" +) + +func TestDefaultBasicAuthorizer(t *testing.T) { + os.Setenv("REGISTRY_CREDENTIAL_USERNAME", "testuser") + os.Setenv("REGISTRY_CREDENTIAL_PASSWORD", "testpassword") + defer func() { + os.Unsetenv("REGISTRY_CREDENTIAL_USERNAME") + os.Unsetenv("REGISTRY_CREDENTIAL_PASSWORD") + }() + req, _ := http.NewRequest(http.MethodGet, "http://127.0.0.1", nil) + a := DefaultBasicAuthorizer() + err := a.Modify(req) + assert.Nil(t, err) + u, p, ok := req.BasicAuth() + assert.True(t, ok) + assert.Equal(t, "testuser", u) + assert.Equal(t, "testpassword", p) +} diff --git a/src/core/api/utils.go b/src/core/api/utils.go index 8f3a7e994961..276dcf426c8b 100644 --- a/src/core/api/utils.go +++ b/src/core/api/utils.go @@ -29,7 +29,6 @@ import ( "github.com/goharbor/harbor/src/common/utils/registry/auth" "github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/core/promgr" - "github.com/goharbor/harbor/src/core/service/token" coreutils "github.com/goharbor/harbor/src/core/utils" ) @@ -248,7 +247,7 @@ func initRegistryClient() (r *registry.Registry, err error) { return nil, err } - authorizer := auth.NewRawTokenAuthorizer("harbor-core", token.Registry) + authorizer := auth.DefaultBasicAuthorizer() return registry.NewRegistry(endpoint, &http.Client{ Transport: registry.NewTransport(registry.GetHTTPTransport(), authorizer), }) diff --git a/src/core/utils/utils.go b/src/core/utils/utils.go index 43d3dba1c518..647b95d9ba83 100644 --- a/src/core/utils/utils.go +++ b/src/core/utils/utils.go @@ -24,7 +24,6 @@ import ( "github.com/goharbor/harbor/src/common/utils/registry" "github.com/goharbor/harbor/src/common/utils/registry/auth" "github.com/goharbor/harbor/src/core/config" - "github.com/goharbor/harbor/src/core/service/token" ) // NewRepositoryClientForUI creates a repository client that can only be used to @@ -51,7 +50,7 @@ func newRepositoryClient(endpoint, username, repository string) (*registry.Repos uam := &auth.UserAgentModifier{ UserAgent: "harbor-registry-client", } - authorizer := auth.NewRawTokenAuthorizer(username, token.Registry) + authorizer := auth.DefaultBasicAuthorizer() transport := registry.NewTransport(http.DefaultTransport, authorizer, uam) client := &http.Client{ Transport: transport, diff --git a/src/jobservice/job/impl/utils/utils.go b/src/jobservice/job/impl/utils/utils.go index 2eda9d3f8363..d50ab564c27f 100644 --- a/src/jobservice/job/impl/utils/utils.go +++ b/src/jobservice/job/impl/utils/utils.go @@ -20,53 +20,13 @@ import ( "os" "sync" - "github.com/docker/distribution/registry/auth/token" httpauth "github.com/goharbor/harbor/src/common/http/modifier/auth" "github.com/goharbor/harbor/src/common/utils/registry" - "github.com/goharbor/harbor/src/common/utils/registry/auth" ) var coreClient *http.Client var mutex = &sync.Mutex{} -// NewRepositoryClient creates a repository client with standard token authorizer -func NewRepositoryClient(endpoint string, insecure bool, credential auth.Credential, - tokenServiceEndpoint, repository string) (*registry.Repository, error) { - - transport := registry.GetHTTPTransport(insecure) - - authorizer := auth.NewStandardTokenAuthorizer(&http.Client{ - Transport: transport, - }, credential, tokenServiceEndpoint) - - uam := &UserAgentModifier{ - UserAgent: "harbor-registry-client", - } - - return registry.NewRepository(repository, endpoint, &http.Client{ - Transport: registry.NewTransport(transport, authorizer, uam), - }) -} - -// NewRepositoryClientForJobservice creates a repository client that can only be used to -// access the internal registry -func NewRepositoryClientForJobservice(repository, internalRegistryURL, secret, internalTokenServiceURL string) (*registry.Repository, error) { - transport := registry.GetHTTPTransport() - credential := httpauth.NewSecretAuthorizer(secret) - - authorizer := auth.NewStandardTokenAuthorizer(&http.Client{ - Transport: transport, - }, credential, internalTokenServiceURL) - - uam := &UserAgentModifier{ - UserAgent: "harbor-registry-client", - } - - return registry.NewRepository(repository, internalRegistryURL, &http.Client{ - Transport: registry.NewTransport(transport, authorizer, uam), - }) -} - // UserAgentModifier adds the "User-Agent" header to the request type UserAgentModifier struct { UserAgent string @@ -78,27 +38,6 @@ func (u *UserAgentModifier) Modify(req *http.Request) error { return nil } -// BuildBlobURL ... -func BuildBlobURL(endpoint, repository, digest string) string { - return fmt.Sprintf("%s/v2/%s/blobs/%s", endpoint, repository, digest) -} - -// GetTokenForRepo is used for job handler to get a token for clair. -func GetTokenForRepo(repository, secret, internalTokenServiceURL string) (string, error) { - credential := httpauth.NewSecretAuthorizer(secret) - t, err := auth.GetToken(internalTokenServiceURL, false, credential, - []*token.ResourceActions{{ - Type: "repository", - Name: repository, - Actions: []string{"pull"}, - }}) - if err != nil { - return "", err - } - - return t.Token, nil -} - // GetClient returns the HTTP client that will attach jobservce secret to the request, which can be used for // accessing Harbor's Core Service. // This function returns error if the secret of Job service is not set. diff --git a/src/server/middleware/util.go b/src/server/middleware/util.go index de111a11f6b4..4007eba18fbd 100644 --- a/src/server/middleware/util.go +++ b/src/server/middleware/util.go @@ -23,6 +23,8 @@ const ( manifestInfoKey = contextKey("ManifestInfo") // ScannerPullCtxKey the context key for robot account to bypass the pull policy check. ScannerPullCtxKey = contextKey("ScannerPullCheck") + // SkipInjectRegistryCredKey is the context key telling registry proxy to skip adding credentials + SkipInjectRegistryCredKey = contextKey("SkipInjectRegistryCredential") ) var ( @@ -63,6 +65,12 @@ func ArtifactInfoFromContext(ctx context.Context) (*ArtifactInfo, bool) { return info, ok } +// SkipInjectRegistryCred reflects whether the inject credentials should be skipped +func SkipInjectRegistryCred(ctx context.Context) bool { + res, ok := ctx.Value(SkipInjectRegistryCredKey).(bool) + return ok && res +} + // NewManifestInfoContext returns context with manifest info func NewManifestInfoContext(ctx context.Context, info *ManifestInfo) context.Context { return context.WithValue(ctx, manifestInfoKey, info) diff --git a/src/server/middleware/v2authz/authz.go b/src/server/middleware/v2auth/auth.go similarity index 90% rename from src/server/middleware/v2authz/authz.go rename to src/server/middleware/v2auth/auth.go index 8254cc651ad9..a3757dd43059 100644 --- a/src/server/middleware/v2authz/authz.go +++ b/src/server/middleware/v2auth/auth.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package authz +package v2auth import ( "fmt" @@ -24,7 +24,9 @@ import ( ierror "github.com/goharbor/harbor/src/internal/error" "github.com/goharbor/harbor/src/server/middleware" reg_err "github.com/goharbor/harbor/src/server/registry/error" + "golang.org/x/net/context" "net/http" + "sync" ) type reqChecker struct { @@ -62,6 +64,9 @@ func (rc *reqChecker) check(req *http.Request) error { } } else if len(middleware.V2CatalogURLRe.FindStringSubmatch(req.URL.Path)) == 1 && !securityCtx.IsSysAdmin() { return fmt.Errorf("unauthorized to list catalog") + } else if req.URL.Path == "/v2/" && !securityCtx.IsAuthenticated() { + ctx := context.WithValue(req.Context(), middleware.SkipInjectRegistryCredKey, true) + *req = *(req.WithContext(ctx)) } return nil } @@ -98,12 +103,18 @@ func getAction(req *http.Request) rbac.Action { } -var checker = reqChecker{ - pm: config.GlobalProjectMgr, -} +var ( + once sync.Once + checker reqChecker +) // Middleware checks the permission of the request to access the artifact func Middleware() func(http.Handler) http.Handler { + once.Do(func() { + checker = reqChecker{ + pm: config.GlobalProjectMgr, + } + }) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if err := checker.check(req); err != nil { diff --git a/src/server/middleware/v2authz/authz_test.go b/src/server/middleware/v2auth/auth_test.go similarity index 99% rename from src/server/middleware/v2authz/authz_test.go rename to src/server/middleware/v2auth/auth_test.go index 78527e30128d..42ce6aafd800 100644 --- a/src/server/middleware/v2authz/authz_test.go +++ b/src/server/middleware/v2auth/auth_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package authz +package v2auth import ( "github.com/goharbor/harbor/src/common/models" diff --git a/src/server/registry/handler.go b/src/server/registry/handler.go index d323f1b1027c..c0395ce8c97e 100644 --- a/src/server/registry/handler.go +++ b/src/server/registry/handler.go @@ -15,6 +15,7 @@ package registry import ( + "github.com/goharbor/harbor/src/core/config" pkg_repo "github.com/goharbor/harbor/src/pkg/repository" pkg_tag "github.com/goharbor/harbor/src/pkg/tag" "github.com/goharbor/harbor/src/server/middleware" @@ -34,9 +35,9 @@ import ( // New return the registry instance to handle the registry APIs func New(url *url.URL) http.Handler { - // TODO add a director to add the basic auth for docker registry // TODO customize the reverse proxy to improve the performance? proxy := httputil.NewSingleHostReverseProxy(url) + proxy.Director = basicAuthDirector(proxy.Director) // create the root rooter rootRouter := mux.NewRouter() @@ -75,3 +76,13 @@ func New(url *url.URL) http.Handler { return rootRouter } + +func basicAuthDirector(d func(*http.Request)) func(*http.Request) { + return func(r *http.Request) { + d(r) + if r != nil && !middleware.SkipInjectRegistryCred(r.Context()) { + u, p := config.RegistryCredential() + r.SetBasicAuth(u, p) + } + } +} diff --git a/src/server/registry/handler_test.go b/src/server/registry/handler_test.go new file mode 100644 index 000000000000..78c031325d71 --- /dev/null +++ b/src/server/registry/handler_test.go @@ -0,0 +1,44 @@ +// Copyright 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 registry + +import ( + "github.com/stretchr/testify/assert" + "net/http" + "os" + "testing" +) + +func direct(req *http.Request) { + req.Header.Add("test-key", "test-value") +} + +func TestBasicAuthDirector(t *testing.T) { + req, _ := http.NewRequest(http.MethodGet, "127.0.0.1", nil) + os.Setenv("REGISTRY_CREDENTIAL_USERNAME", "testuser") + os.Setenv("REGISTRY_CREDENTIAL_PASSWORD", "testpassword") + defer func() { + os.Unsetenv("REGISTRY_CREDENTIAL_USERNAME") + os.Unsetenv("REGISTRY_CREDENTIAL_PASSWORD") + }() + + d := basicAuthDirector(direct) + d(req) + assert.Equal(t, "test-value", req.Header.Get("test-key")) + user, pass, ok := req.BasicAuth() + assert.True(t, ok) + assert.Equal(t, "testuser", user) + assert.Equal(t, "testpassword", pass) +} diff --git a/src/server/registry/route.go b/src/server/registry/route.go index 2cd738c3ba41..bec79ee7e25c 100644 --- a/src/server/registry/route.go +++ b/src/server/registry/route.go @@ -17,8 +17,11 @@ package registry import ( "github.com/goharbor/harbor/src/core/config" "github.com/goharbor/harbor/src/server/middleware/immutable" + + "github.com/goharbor/harbor/src/server/middleware/artifactinfo" "github.com/goharbor/harbor/src/server/middleware/manifestinfo" "github.com/goharbor/harbor/src/server/middleware/readonly" + "github.com/goharbor/harbor/src/server/middleware/v2auth" "github.com/goharbor/harbor/src/server/registry/manifest" "github.com/goharbor/harbor/src/server/router" "net/http" @@ -32,11 +35,17 @@ func RegisterRoutes() { regURL, _ := config.RegistryURL() url, _ := url.Parse(regURL) proxy := httputil.NewSingleHostReverseProxy(url) + proxy.Director = basicAuthDirector(proxy.Director) - router.NewRoute().Path("/v2/*").Handler(New(url)) + router.NewRoute().Path("/v2/*"). + Middleware(artifactinfo.Middleware()). + Middleware(v2auth.Middleware()). + Handler(New(url)) router.NewRoute(). Method(http.MethodPut). Path("/v2/*/manifests/:reference"). + Middleware(artifactinfo.Middleware()). + Middleware(v2auth.Middleware()). Middleware(readonly.Middleware()). Middleware(manifestinfo.Middleware()). Middleware(immutable.MiddlewarePush()).