// 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 }
// 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) }
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 }
// 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 }
// 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 }
// 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 } }
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 }
// 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 }
// 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 }
// 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 }
// 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 }