Example #1
0
// Authorized simply checks for the existence of the authorization header,
// responding with a bearer challenge if it doesn't exist.
func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
	req, err := ctxu.GetRequest(ctx)
	if err != nil {
		return nil, err
	}

	if req.Header.Get("Authorization") == "" {
		challenge := challenge{
			realm:   ac.realm,
			service: ac.service,
		}

		if len(accessRecords) > 0 {
			var scopes []string
			for _, access := range accessRecords {
				scopes = append(scopes, fmt.Sprintf("%s:%s:%s", access.Type, access.Resource.Name, access.Action))
			}
			challenge.scope = strings.Join(scopes, " ")
		}

		return nil, &challenge
	}

	return auth.WithUser(ctx, auth.UserInfo{Name: "silly"}), nil
}
Example #2
0
// releases frees any associated with resources from request.
func (cm *contextManager) release(ctx context.Context) {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	r, err := ctxu.GetRequest(ctx)
	if err != nil {
		ctxu.GetLogger(ctx).Errorf("no request found in context during release")
		return
	}
	delete(cm.contexts, r)
}
Example #3
0
func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
	req, err := context.GetRequest(ctx)
	if err != nil {
		return nil, err
	}

	username, password, ok := req.BasicAuth()
	if !ok {
		return nil, &challenge{
			realm: ac.realm,
			err:   auth.ErrInvalidCredential,
		}
	}

	// Dynamically parsing the latest account list
	fstat, err := os.Stat(ac.path)
	if err != nil {
		return nil, err
	}

	lastModified := fstat.ModTime()
	ac.mu.Lock()
	if ac.htpasswd == nil || !ac.modtime.Equal(lastModified) {
		ac.modtime = lastModified

		f, err := os.Open(ac.path)
		if err != nil {
			ac.mu.Unlock()
			return nil, err
		}
		defer f.Close()

		h, err := newHTPasswd(f)
		if err != nil {
			ac.mu.Unlock()
			return nil, err
		}
		ac.htpasswd = h
	}
	localHTPasswd := ac.htpasswd
	ac.mu.Unlock()

	if err := localHTPasswd.authenticateUser(username, password); err != nil {
		context.GetLogger(ctx).Errorf("error authenticating user %q: %v", username, err)
		return nil, &challenge{
			realm: ac.realm,
			err:   auth.ErrAuthenticationFailure,
		}
	}

	return auth.WithUser(ctx, auth.UserInfo{Name: username}), nil
}
Example #4
0
// Authorized implements the auth.AccessController interface and authorizes a
// request if it includes the correct auth key
func (a *Auth) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
	req, err := context.GetRequest(ctx)
	if err != nil {
		return nil, err
	}
	_, password, _ := req.BasicAuth()
	if password == "" {
		password = req.URL.Query().Get("key")
	}
	if subtle.ConstantTimeCompare([]byte(password), []byte(a.key)) != 1 {
		return nil, Challenge{}
	}
	return ctx, nil
}
Example #5
0
// Authorized handles checking whether the given request is authorized
// for actions on resources described by the given access items.
func (ac *accessController) Authorized(ctx context.Context, accessItems ...auth.Access) (context.Context, error) {
	challenge := &authChallenge{
		realm:     ac.realm,
		service:   ac.service,
		accessSet: newAccessSet(accessItems...),
	}

	req, err := context.GetRequest(ctx)
	if err != nil {
		return nil, err
	}

	parts := strings.Split(req.Header.Get("Authorization"), " ")

	if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
		challenge.err = ErrTokenRequired
		return nil, challenge
	}

	rawToken := parts[1]

	token, err := NewToken(rawToken)
	if err != nil {
		challenge.err = err
		return nil, challenge
	}

	verifyOpts := VerifyOptions{
		TrustedIssuers:    []string{ac.issuer},
		AcceptedAudiences: []string{ac.service},
		Roots:             ac.rootCerts,
		TrustedKeys:       ac.trustedKeys,
	}

	if err = token.Verify(verifyOpts); err != nil {
		challenge.err = err
		return nil, challenge
	}

	accessSet := token.accessSet()
	for _, access := range accessItems {
		if !accessSet.contains(access) {
			challenge.err = ErrInsufficientScope
			return nil, challenge
		}
	}

	return auth.WithUser(ctx, auth.UserInfo{Name: token.Claims.Subject}), nil
}
Example #6
0
// wrapErr wraps errors related to authorization in an authChallenge error that will present a WWW-Authenticate challenge response
func (ac *AccessController) wrapErr(ctx context.Context, err error) error {
	switch err {
	case ErrTokenRequired:
		// Challenge for errors that involve missing tokens
		if ac.tokenRealm == nil {
			// Send the basic challenge if we don't have a place to redirect
			return &authChallenge{realm: ac.realm, err: err}
		}

		if len(ac.tokenRealm.Scheme) > 0 && len(ac.tokenRealm.Host) > 0 {
			// Redirect to token auth if we've been given an absolute URL
			return &tokenAuthChallenge{realm: ac.tokenRealm.String(), err: err}
		}

		// Auto-detect scheme/host from request
		req, reqErr := context.GetRequest(ctx)
		if reqErr != nil {
			return reqErr
		}
		scheme, host := httprequest.SchemeHost(req)
		tokenRealmCopy := *ac.tokenRealm
		if len(tokenRealmCopy.Scheme) == 0 {
			tokenRealmCopy.Scheme = scheme
		}
		if len(tokenRealmCopy.Host) == 0 {
			tokenRealmCopy.Host = host
		}
		return &tokenAuthChallenge{realm: tokenRealmCopy.String(), err: err}
	case ErrTokenInvalid, ErrOpenShiftAccessDenied:
		// Challenge for errors that involve tokens or access denied
		return &authChallenge{realm: ac.realm, err: err}
	case ErrNamespaceRequired, ErrUnsupportedAction, ErrUnsupportedResource:
		// Malformed or unsupported request, no challenge
		return err
	default:
		// By default, just return the error, this gets surfaced as a bad request / internal error, but no challenge
		return err
	}
}
Example #7
0
func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
	req, err := ctxu.GetRequest(ctx)
	if err != nil {
		return nil, err
	}

	username, password, ok := req.BasicAuth()
	if !ok {
		return nil, &challenge{
			realm: ac.realm,
			err:   ErrInvalidCredential,
		}
	}

	if err := ac.htpasswd.authenticateUser(username, password); err != nil {
		ctxu.GetLogger(ctx).Errorf("error authenticating user %q: %v", username, err)
		return nil, &challenge{
			realm: ac.realm,
			err:   ErrAuthenticationFailure,
		}
	}

	return auth.WithUser(ctx, auth.UserInfo{Name: username}), nil
}
Example #8
0
// Authorized handles checking whether the given request is authorized
// for actions on resources allowed by openshift.
// Sources of access records:
//   origin/pkg/cmd/dockerregistry/dockerregistry.go#Execute
//   docker/distribution/registry/handlers/app.go#appendAccessRecords
func (ac *AccessController) Authorized(ctx context.Context, accessRecords ...registryauth.Access) (context.Context, error) {
	req, err := context.GetRequest(ctx)
	if err != nil {
		return nil, ac.wrapErr(ctx, err)
	}

	bearerToken, err := getOpenShiftAPIToken(ctx, req)
	if err != nil {
		return nil, ac.wrapErr(ctx, err)
	}

	copied := ac.config
	copied.BearerToken = bearerToken
	osClient, err := client.New(&copied)
	if err != nil {
		return nil, ac.wrapErr(ctx, err)
	}

	// In case of docker login, hits endpoint /v2
	if len(accessRecords) == 0 {
		if err := verifyOpenShiftUser(ctx, osClient); err != nil {
			return nil, ac.wrapErr(ctx, err)
		}
	}

	// pushChecks remembers which ns/name pairs had push access checks done
	pushChecks := map[string]bool{}
	// possibleCrossMountErrors holds errors which may be related to cross mount errors
	possibleCrossMountErrors := deferredErrors{}

	verifiedPrune := false

	// Validate all requested accessRecords
	// Only return failure errors from this loop. Success should continue to validate all records
	for _, access := range accessRecords {
		context.GetLogger(ctx).Debugf("Origin auth: checking for access to %s:%s:%s", access.Resource.Type, access.Resource.Name, access.Action)

		switch access.Resource.Type {
		case "repository":
			imageStreamNS, imageStreamName, err := getNamespaceName(access.Resource.Name)
			if err != nil {
				return nil, ac.wrapErr(ctx, err)
			}

			verb := ""
			switch access.Action {
			case "push":
				verb = "update"
				pushChecks[imageStreamNS+"/"+imageStreamName] = true
			case "pull":
				verb = "get"
			case "*":
				verb = "prune"
			default:
				return nil, ac.wrapErr(ctx, ErrUnsupportedAction)
			}

			switch verb {
			case "prune":
				if verifiedPrune {
					continue
				}
				if err := verifyPruneAccess(ctx, osClient); err != nil {
					return nil, ac.wrapErr(ctx, err)
				}
				verifiedPrune = true
			default:
				if err := verifyImageStreamAccess(ctx, imageStreamNS, imageStreamName, verb, osClient); err != nil {
					if access.Action != "pull" {
						return nil, ac.wrapErr(ctx, err)
					}
					possibleCrossMountErrors.Add(imageStreamNS, imageStreamName, ac.wrapErr(ctx, err))
				}
			}

		case "admin":
			switch access.Action {
			case "prune":
				if verifiedPrune {
					continue
				}
				if err := verifyPruneAccess(ctx, osClient); err != nil {
					return nil, ac.wrapErr(ctx, err)
				}
				verifiedPrune = true
			default:
				return nil, ac.wrapErr(ctx, ErrUnsupportedAction)
			}
		default:
			return nil, ac.wrapErr(ctx, ErrUnsupportedResource)
		}
	}

	// deal with any possible cross-mount errors
	for namespaceAndName, err := range possibleCrossMountErrors {
		// If we have no push requests, this can't be a cross-mount request, so error
		if len(pushChecks) == 0 {
			return nil, err
		}
		// If we also requested a push to this ns/name, this isn't a cross-mount request, so error
		if pushChecks[namespaceAndName] {
			return nil, err
		}
	}

	// Conditionally add auth errors we want to handle later to the context
	if !possibleCrossMountErrors.Empty() {
		context.GetLogger(ctx).Debugf("Origin auth: deferring errors: %#v", possibleCrossMountErrors)
		ctx = WithDeferredErrors(ctx, possibleCrossMountErrors)
	}
	// Always add a marker to the context so we know auth was run
	ctx = WithAuthPerformed(ctx)

	return WithUserClient(ctx, osClient), nil
}
Example #9
0
// Authorized handles checking whether the given request is authorized
// for actions on resources allowed by openshift.
// Sources of access records:
//   origin/pkg/cmd/dockerregistry/dockerregistry.go#Execute
//   docker/distribution/registry/handlers/app.go#appendAccessRecords
func (ac *AccessController) Authorized(ctx context.Context, accessRecords ...registryauth.Access) (context.Context, error) {
	req, err := context.GetRequest(ctx)
	if err != nil {
		return nil, ac.wrapErr(err)
	}

	bearerToken, err := getToken(ctx, req)
	if err != nil {
		return nil, ac.wrapErr(err)
	}

	copied := ac.config
	copied.BearerToken = bearerToken
	osClient, err := client.New(&copied)
	if err != nil {
		return nil, ac.wrapErr(err)
	}

	// In case of docker login, hits endpoint /v2
	if len(accessRecords) == 0 {
		if err := verifyOpenShiftUser(ctx, osClient); err != nil {
			return nil, ac.wrapErr(err)
		}
	}

	verifiedPrune := false

	// Validate all requested accessRecords
	// Only return failure errors from this loop. Success should continue to validate all records
	for _, access := range accessRecords {
		context.GetLogger(ctx).Debugf("Origin auth: checking for access to %s:%s:%s", access.Resource.Type, access.Resource.Name, access.Action)

		switch access.Resource.Type {
		case "repository":
			imageStreamNS, imageStreamName, err := getNamespaceName(access.Resource.Name)
			if err != nil {
				return nil, ac.wrapErr(err)
			}

			verb := ""
			switch access.Action {
			case "push":
				verb = "update"
			case "pull":
				verb = "get"
			case "*":
				verb = "prune"
			default:
				return nil, ac.wrapErr(ErrUnsupportedAction)
			}

			switch verb {
			case "prune":
				if verifiedPrune {
					continue
				}
				if err := verifyPruneAccess(ctx, osClient); err != nil {
					return nil, ac.wrapErr(err)
				}
				verifiedPrune = true
			default:
				if err := verifyImageStreamAccess(ctx, imageStreamNS, imageStreamName, verb, osClient); err != nil {
					return nil, ac.wrapErr(err)
				}
			}

		case "admin":
			switch access.Action {
			case "prune":
				if verifiedPrune {
					continue
				}
				if err := verifyPruneAccess(ctx, osClient); err != nil {
					return nil, ac.wrapErr(err)
				}
				verifiedPrune = true
			default:
				return nil, ac.wrapErr(ErrUnsupportedAction)
			}
		default:
			return nil, ac.wrapErr(ErrUnsupportedResource)
		}
	}

	return WithUserClient(ctx, osClient), nil
}
Example #10
0
// Authorized handles checking whether the given request is authorized
// for actions on resources allowed by openshift.
// Sources of access records:
//   origin/pkg/cmd/dockerregistry/dockerregistry.go#Execute
//   docker/distribution/registry/handlers/app.go#appendAccessRecords
func (ac *AccessController) Authorized(ctx context.Context, accessRecords ...registryauth.Access) (context.Context, error) {
	req, err := ctxu.GetRequest(ctx)
	if err != nil {
		return nil, ac.wrapErr(err)
	}

	// TODO: change this to an anonymous Access record, don't require a token for it, and fold into the access record check look below
	if req.URL.Path == "/healthz" {
		return ctx, nil
	}

	bearerToken, err := getToken(req)
	if err != nil {
		return nil, ac.wrapErr(err)
	}

	client, err := NewUserOpenShiftClient(bearerToken)
	if err != nil {
		return nil, ac.wrapErr(err)
	}

	// In case of docker login, hits endpoint /v2
	if len(accessRecords) == 0 {
		if err := verifyOpenShiftUser(client); err != nil {
			return nil, ac.wrapErr(err)
		}
	}

	verifiedPrune := false

	// Validate all requested accessRecords
	// Only return failure errors from this loop. Success should continue to validate all records
	for _, access := range accessRecords {
		log.Debugf("Origin auth: checking for access to %s:%s:%s", access.Resource.Type, access.Resource.Name, access.Action)

		switch access.Resource.Type {
		case "repository":
			imageStreamNS, imageStreamName, err := getNamespaceName(access.Resource.Name)
			if err != nil {
				return nil, ac.wrapErr(err)
			}

			verb := ""
			switch access.Action {
			case "push":
				verb = "update"
			case "pull":
				verb = "get"
			case "*":
				verb = "prune"
			default:
				return nil, ac.wrapErr(ErrUnsupportedAction)
			}

			switch verb {
			case "prune":
				if verifiedPrune {
					continue
				}
				if err := verifyPruneAccess(client); err != nil {
					return nil, ac.wrapErr(err)
				}
				verifiedPrune = true
			default:
				if err := verifyImageStreamAccess(imageStreamNS, imageStreamName, verb, client); err != nil {
					return nil, ac.wrapErr(err)
				}
			}

		case "admin":
			switch access.Action {
			case "prune":
				if verifiedPrune {
					continue
				}
				if err := verifyPruneAccess(client); err != nil {
					return nil, ac.wrapErr(err)
				}
				verifiedPrune = true
			default:
				return nil, ac.wrapErr(ErrUnsupportedAction)
			}
		default:
			return nil, ac.wrapErr(ErrUnsupportedResource)
		}
	}

	return WithUserClient(ctx, client), nil
}
Example #11
0
// Authorized simply checks for the existence of the SSL CLIENT headers,
// using which entitlement check is done
func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
	var resData ResponseData
	var err1 error
	req, err := context.GetRequest(ctx)
	//res, err2 := context.GetResponseWriter(ctx)
	if err != nil {
		return nil, err
	}

	if req.Header.Get("SSL_CLIENT_CERT") == "" {
		log.Debugln("repo name: %s", getName(ctx))

		return nil, &challenge{
			realm: ac.realm,
			err:   fmt.Errorf("Authentication Failure"),
		}
	}

	pemStr := req.Header.Get("SSL_CLIENT_CERT")
	log.Debugln("SSL CERT: %s", pemStr)
	repoName := getName(ctx)
	//if it is a push request
	//or the the URI requested is /v2/ (ping)
	//then don't call authentication service
	log.Debugln("requestURI: ", req.RequestURI)
	log.Debugln("requested repo name: ", getName(ctx))
	if skipAuth(req) {
		log.Debugln("Returning without calling authentication servie")
		return auth.WithUser(ctx, auth.UserInfo{Name: "entitled-ping"}), nil
	}

	// check for repo name being empty. If repo name is empty
	// and the URI is not for ping, return authentication error
	if "/v2/" != req.RequestURI && repoName == "" {
		log.Errorln("No repo name retrieved. This should not happen")
		return nil, &challenge{
			realm: ac.realm,
			err:   fmt.Errorf("Authentication Failure as no repo name has been supplied"),
		}
	}

	libraryName := repoName[:strings.LastIndex(repoName, "/")+1]
	log.Debugln("Computed library name: ", libraryName)
	path := fmt.Sprintf("/content/dist/rhel/server/7/7Server/x86_64/containers/registry/%s", libraryName)

	if resData, err1 = ac.service.CheckEntitlementV2(req, path); err1 != nil {
		log.Errorln("Service returned error: ", err1)
		return nil, &challenge{
			realm: ac.realm,
			err:   fmt.Errorf("Authentication Failure"),
		}
	}

	if resData.Verified != "true" {
		log.Errorln("Service returned unauthenticated/unauthorized")
		return nil, &challenge{
			realm: ac.realm,
			err:   fmt.Errorf("Authentication Failure"),
		}
	}

	return auth.WithUser(ctx, auth.UserInfo{Name: "entitled"}), nil
}