Skip to content

Commit

Permalink
Switch to basic authentication for registry
Browse files Browse the repository at this point in the history
1. Add basic authorizer for registry which modify the request
to add basic authorization header to request based on configuration.
2. Set basic auth header for proxy when accessing registry
3. Switche the registry to use basic auth by default and use the basic
authorizer to access Harbor.

Signed-off-by: Daniel Jiang <jiangd@vmware.com>
  • Loading branch information
reasonerjt committed Jan 30, 2020
1 parent a1b25e1 commit 4026b7a
Show file tree
Hide file tree
Showing 16 changed files with 173 additions and 91 deletions.
3 changes: 0 additions & 3 deletions make/harbor.yml.tmpl
Expand Up @@ -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.
Expand Down
8 changes: 0 additions & 8 deletions make/photon/prepare/templates/registry/config.yml.jinja
Expand Up @@ -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:
Expand Down
4 changes: 0 additions & 4 deletions make/photon/prepare/utils/configs.py
Expand Up @@ -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


Expand Down
3 changes: 1 addition & 2 deletions make/photon/prepare/utils/registry.py
Expand Up @@ -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'])
Expand Down
3 changes: 1 addition & 2 deletions src/api/artifact/abstractor/blob/fetcher.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
40 changes: 40 additions & 0 deletions 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
}
39 changes: 39 additions & 0 deletions 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)
}
3 changes: 1 addition & 2 deletions src/core/api/utils.go
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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),
})
Expand Down
3 changes: 1 addition & 2 deletions src/core/utils/utils.go
Expand Up @@ -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
Expand All @@ -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,
Expand Down
61 changes: 0 additions & 61 deletions src/jobservice/job/impl/utils/utils.go
Expand Up @@ -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
Expand All @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions src/server/middleware/util.go
Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
Expand Down
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package authz
package v2auth

import (
"fmt"
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down
Expand Up @@ -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"
Expand Down
13 changes: 12 additions & 1 deletion src/server/registry/handler.go
Expand Up @@ -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"
Expand All @@ -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()
Expand Down Expand Up @@ -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)
}
}
}

0 comments on commit 4026b7a

Please sign in to comment.