Example #1
0
func formatRepositoryError(repository *importRepository, refName string, refID string, defErr error) (err error) {
	err = defErr
	switch {
	case isDockerError(err, v2.ErrorCodeManifestUnknown):
		ref := repository.Ref
		ref.Tag, ref.ID = refName, refID
		err = kapierrors.NewNotFound(api.Resource("dockerimage"), ref.Exact())
	case isDockerError(err, errcode.ErrorCodeUnauthorized):
		err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact()))
	case strings.HasSuffix(err.Error(), "no basic auth credentials"):
		err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact()))
	}
	return
}
Example #2
0
// ConfirmNoEscalation determines if the roles for a given user in a given namespace encompass the provided role.
func ConfirmNoEscalation(ctx api.Context, ruleResolver AuthorizationRuleResolver, rules []rbac.PolicyRule) error {
	ruleResolutionErrors := []error{}

	ownerLocalRules, err := ruleResolver.GetEffectivePolicyRules(ctx)
	if err != nil {
		// As per AuthorizationRuleResolver contract, this may return a non fatal error with an incomplete list of policies. Log the error and continue.
		user, _ := api.UserFrom(ctx)
		glog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err)
		ruleResolutionErrors = append(ruleResolutionErrors, err)
	}

	masterContext := api.WithNamespace(ctx, "")
	ownerGlobalRules, err := ruleResolver.GetEffectivePolicyRules(masterContext)
	if err != nil {
		// Same case as above. Log error, don't fail.
		user, _ := api.UserFrom(ctx)
		glog.V(1).Infof("non-fatal error getting global rules for %v: %v", user, err)
		ruleResolutionErrors = append(ruleResolutionErrors, err)
	}

	ownerRules := make([]rbac.PolicyRule, 0, len(ownerGlobalRules)+len(ownerLocalRules))
	ownerRules = append(ownerRules, ownerLocalRules...)
	ownerRules = append(ownerRules, ownerGlobalRules...)

	ownerRightsCover, missingRights := Covers(ownerRules, rules)
	if !ownerRightsCover {
		user, _ := api.UserFrom(ctx)
		return errors.NewUnauthorized(fmt.Sprintf("attempt to grant extra privileges: %v user=%v ownerrules=%v ruleResolutionErrors=%v", missingRights, user, ownerRules, ruleResolutionErrors))
	}
	return nil
}
Example #3
0
func ConfirmNoEscalation(ctx kapi.Context, ruleResolver AuthorizationRuleResolver, role authorizationinterfaces.Role) error {
	ruleResolutionErrors := []error{}

	ownerLocalRules, err := ruleResolver.GetEffectivePolicyRules(ctx)
	if err != nil {
		// do not fail in this case.  Rules are purely additive, so we can continue with a coverage check based on the rules we have
		user, _ := kapi.UserFrom(ctx)
		glog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err)
		ruleResolutionErrors = append(ruleResolutionErrors, err)
	}
	masterContext := kapi.WithNamespace(ctx, "")
	ownerGlobalRules, err := ruleResolver.GetEffectivePolicyRules(masterContext)
	if err != nil {
		// do not fail in this case.  Rules are purely additive, so we can continue with a coverage check based on the rules we have
		user, _ := kapi.UserFrom(ctx)
		glog.V(1).Infof("non-fatal error getting global rules for %v: %v", user, err)
		ruleResolutionErrors = append(ruleResolutionErrors, err)
	}

	ownerRules := make([]authorizationapi.PolicyRule, 0, len(ownerGlobalRules)+len(ownerLocalRules))
	ownerRules = append(ownerRules, ownerLocalRules...)
	ownerRules = append(ownerRules, ownerGlobalRules...)

	ownerRightsCover, missingRights := Covers(ownerRules, role.Rules())
	if !ownerRightsCover {
		user, _ := kapi.UserFrom(ctx)
		return kapierrors.NewUnauthorized(fmt.Sprintf("attempt to grant extra privileges: %v user=%v ownerrules=%v ruleResolutionErrors=%v", missingRights, user, ownerRules, ruleResolutionErrors))
	}

	return nil
}
Example #4
0
func (m *VirtualStorage) confirmNoEscalation(ctx kapi.Context, roleBinding *authorizationapi.RoleBinding) error {
	modifyingRole, err := m.getReferencedRole(roleBinding.RoleRef)
	if err != nil {
		return err
	}

	ruleResolver := rulevalidation.NewDefaultRuleResolver(
		m.PolicyRegistry,
		m.BindingRegistry,
		m.ClusterPolicyRegistry,
		m.ClusterPolicyBindingRegistry,
	)
	ownerLocalRules, err := ruleResolver.GetEffectivePolicyRules(ctx)
	if err != nil {
		return kapierrors.NewInternalError(err)
	}
	masterContext := kapi.WithNamespace(ctx, "")
	ownerGlobalRules, err := ruleResolver.GetEffectivePolicyRules(masterContext)
	if err != nil {
		return kapierrors.NewInternalError(err)
	}

	ownerRules := make([]authorizationapi.PolicyRule, 0, len(ownerGlobalRules)+len(ownerLocalRules))
	ownerRules = append(ownerRules, ownerLocalRules...)
	ownerRules = append(ownerRules, ownerGlobalRules...)

	ownerRightsCover, missingRights := rulevalidation.Covers(ownerRules, modifyingRole.Rules)
	if !ownerRightsCover {
		user, _ := kapi.UserFrom(ctx)
		return kapierrors.NewUnauthorized(fmt.Sprintf("attempt to grant extra privileges: %v user=%v ownerrules=%v", missingRights, user, ownerRules))
	}

	return nil
}
Example #5
0
// ServeHTTP implements rest.HookHandler
func (w *WebHook) ServeHTTP(writer http.ResponseWriter, req *http.Request, ctx kapi.Context, name, subpath string) error {
	parts := strings.Split(subpath, "/")
	if len(parts) != 2 {
		return errors.NewBadRequest(fmt.Sprintf("unexpected hook subpath %s", subpath))
	}
	secret, hookType := parts[0], parts[1]

	plugin, ok := w.plugins[hookType]
	if !ok {
		return errors.NewNotFound(buildapi.Resource("buildconfighook"), hookType)
	}

	config, err := w.registry.GetBuildConfig(ctx, name)
	if err != nil {
		// clients should not be able to find information about build configs in
		// the system unless the config exists and the secret matches
		return errors.NewUnauthorized(fmt.Sprintf("the webhook %q for %q did not accept your secret", hookType, name))
	}

	revision, envvars, proceed, err := plugin.Extract(config, secret, "", req)
	if !proceed {
		switch err {
		case webhook.ErrSecretMismatch, webhook.ErrHookNotEnabled:
			return errors.NewUnauthorized(fmt.Sprintf("the webhook %q for %q did not accept your secret", hookType, name))
		case webhook.MethodNotSupported:
			return errors.NewMethodNotSupported(buildapi.Resource("buildconfighook"), req.Method)
		}
		if _, ok := err.(*errors.StatusError); !ok && err != nil {
			return errors.NewInternalError(fmt.Errorf("hook failed: %v", err))
		}
		return err
	}
	warning := err

	buildTriggerCauses := generateBuildTriggerInfo(revision, hookType, secret)
	request := &buildapi.BuildRequest{
		TriggeredBy: buildTriggerCauses,
		ObjectMeta:  kapi.ObjectMeta{Name: name},
		Revision:    revision,
		Env:         envvars,
	}
	if _, err := w.instantiator.Instantiate(config.Namespace, request); err != nil {
		return errors.NewInternalError(fmt.Errorf("could not generate a build: %v", err))
	}
	return warning
}
Example #6
0
// ServeHTTP implements rest.HookHandler
func (c *controller) ServeHTTP(w http.ResponseWriter, req *http.Request, ctx kapi.Context, name, subpath string) error {
	parts := strings.Split(subpath, "/")
	if len(parts) < 2 {
		return errors.NewBadRequest(fmt.Sprintf("unexpected hook subpath %s", subpath))
	}
	secret, hookType := parts[0], parts[1]

	plugin, ok := c.plugins[hookType]
	if !ok {
		return errors.NewNotFound(buildapi.Resource("buildconfighook"), hookType)
	}

	config, err := c.registry.GetBuildConfig(ctx, name)
	if err != nil {
		// clients should not be able to find information about build configs in the system unless the config exists
		// and the secret matches
		return errors.NewUnauthorized(fmt.Sprintf("the webhook %q for %q did not accept your secret", hookType, name))
	}

	revision, proceed, err := plugin.Extract(config, secret, "", req)
	switch err {
	case webhook.ErrSecretMismatch, webhook.ErrHookNotEnabled:
		return errors.NewUnauthorized(fmt.Sprintf("the webhook %q for %q did not accept your secret", hookType, name))
	case nil:
	default:
		return errors.NewInternalError(fmt.Errorf("hook failed: %v", err))
	}

	if !proceed {
		return nil
	}

	request := &buildapi.BuildRequest{
		ObjectMeta: kapi.ObjectMeta{Name: name},
		Revision:   revision,
	}
	if _, err := c.instantiator.Instantiate(config.Namespace, request); err != nil {
		return errors.NewInternalError(fmt.Errorf("could not generate a build: %v", err))
	}
	return nil
}
Example #7
0
// ConfirmNoEscalation determines if the roles for a given user in a given namespace encompass the provided role.
func ConfirmNoEscalation(ctx api.Context, ruleResolver AuthorizationRuleResolver, rules []rbac.PolicyRule) error {
	ruleResolutionErrors := []error{}

	user, ok := api.UserFrom(ctx)
	if !ok {
		return fmt.Errorf("no user on context")
	}
	namespace, _ := api.NamespaceFrom(ctx)

	ownerRules, err := ruleResolver.RulesFor(user, namespace)
	if err != nil {
		// As per AuthorizationRuleResolver contract, this may return a non fatal error with an incomplete list of policies. Log the error and continue.
		glog.V(1).Infof("non-fatal error getting local rules for %v: %v", user, err)
		ruleResolutionErrors = append(ruleResolutionErrors, err)
	}

	ownerRightsCover, missingRights := Covers(ownerRules, rules)
	if !ownerRightsCover {
		user, _ := api.UserFrom(ctx)
		return apierrors.NewUnauthorized(fmt.Sprintf("attempt to grant extra privileges: %v user=%v ownerrules=%v ruleResolutionErrors=%v", missingRights, user, ownerRules, ruleResolutionErrors))
	}
	return nil
}
Example #8
0
func (h *MultiHandler) HandleChallenge(requestURL string, headers http.Header) (http.Header, bool, error) {
	// If we've already selected a handler, it alone can handle all subsequent challenges (don't change horses in mid-stream)
	if h.handler != nil {
		return h.handler.HandleChallenge(requestURL, headers)
	}

	// Otherwise, filter our list of handlers to the ones that can handle this request
	applicable := []ChallengeHandler{}
	for _, handler := range h.possibleHandlers {
		if handler.CanHandle(headers) {
			applicable = append(applicable, handler)
		}
	}
	h.possibleHandlers = applicable

	// Then select the first available handler that successfully handles the request
	var (
		retryHeaders http.Header
		retry        bool
		err          error
	)
	for i, handler := range h.possibleHandlers {
		retryHeaders, retry, err = handler.HandleChallenge(requestURL, headers)

		if err != nil {
			glog.V(5).Infof("handler[%d] error: %v", i, err)
		}
		// If the handler successfully handled the challenge, or we have no other options, select it as our handler
		if err == nil || i == len(h.possibleHandlers)-1 {
			h.handler = handler
			return retryHeaders, retry, err
		}
	}

	return nil, false, apierrs.NewUnauthorized("unhandled challenge")
}
Example #9
0
// RequestToken uses the cmd arguments to locate an openshift oauth server and attempts to authenticate
// it returns the access token if it gets one.  An error if it does not
func RequestToken(clientCfg *restclient.Config, reader io.Reader, defaultUsername string, defaultPassword string) (string, error) {
	challengeHandler := &BasicChallengeHandler{
		Host:     clientCfg.Host,
		Reader:   reader,
		Username: defaultUsername,
		Password: defaultPassword,
	}

	rt, err := restclient.TransportFor(clientCfg)
	if err != nil {
		return "", err
	}

	// requestURL holds the current URL to make requests to. This can change if the server responds with a redirect
	requestURL := clientCfg.Host + "/oauth/authorize?response_type=token&client_id=openshift-challenging-client"
	// requestHeaders holds additional headers to add to the request. This can be changed by challengeHandlers
	requestHeaders := http.Header{}
	// requestedURLSet/requestedURLList hold the URLs we have requested, to prevent redirect loops. Gets reset when a challenge is handled.
	requestedURLSet := sets.NewString()
	requestedURLList := []string{}

	for {
		// Make the request
		resp, err := request(rt, requestURL, requestHeaders)
		if err != nil {
			return "", err
		}
		defer resp.Body.Close()

		if resp.StatusCode == http.StatusUnauthorized {
			if resp.Header.Get("WWW-Authenticate") != "" {
				if !challengeHandler.CanHandle(resp.Header) {
					return "", apierrs.NewUnauthorized("unhandled challenge")
				}
				// Handle a challenge
				newRequestHeaders, shouldRetry, err := challengeHandler.HandleChallenge(resp.Header)
				if err != nil {
					return "", err
				}
				if !shouldRetry {
					return "", apierrs.NewUnauthorized("challenger chose not to retry the request")
				}

				// Reset request set/list. Since we're setting different headers, it is legitimate to request the same urls
				requestedURLSet = sets.NewString()
				requestedURLList = []string{}
				// Use the response to the challenge as the new headers
				requestHeaders = newRequestHeaders
				continue
			}

			// Unauthorized with no challenge
			unauthorizedError := apierrs.NewUnauthorized("")
			// Attempt to read body content and include as an error detail
			if details, err := ioutil.ReadAll(resp.Body); err == nil && len(details) > 0 {
				unauthorizedError.ErrStatus.Details = &unversioned.StatusDetails{
					Causes: []unversioned.StatusCause{
						{Message: string(details)},
					},
				}
			}

			return "", unauthorizedError
		}

		if resp.StatusCode == http.StatusFound {
			redirectURL := resp.Header.Get("Location")

			// OAuth response case (access_token or error parameter)
			accessToken, err := oauthAuthorizeResult(redirectURL)
			if err != nil {
				return "", err
			}
			if len(accessToken) > 0 {
				return accessToken, err
			}

			// Non-OAuth response, just follow the URL
			// add to our list of redirects
			requestedURLList = append(requestedURLList, redirectURL)
			// detect loops
			if !requestedURLSet.Has(redirectURL) {
				requestedURLSet.Insert(redirectURL)
				requestURL = redirectURL
				continue
			}
			return "", apierrs.NewInternalError(fmt.Errorf("redirect loop: %s", strings.Join(requestedURLList, " -> ")))
		}

		// Unknown response
		return "", apierrs.NewInternalError(fmt.Errorf("unexpected response: %d", resp.StatusCode))
	}
}
Example #10
0
// importRepositoryFromDocker loads the tags and images requested in the passed importRepository, obeying the
// optional rate limiter.  Errors are set onto the individual tags and digest objects.
func (isi *ImageStreamImporter) importRepositoryFromDocker(ctx gocontext.Context, retriever RepositoryRetriever, repository *importRepository, limiter flowcontrol.RateLimiter) {
	glog.V(5).Infof("importing remote Docker repository registry=%s repository=%s insecure=%t", repository.Registry, repository.Name, repository.Insecure)
	// retrieve the repository
	repo, err := retriever.Repository(ctx, repository.Registry, repository.Name, repository.Insecure)
	if err != nil {
		glog.V(5).Infof("unable to access repository %#v: %#v", repository, err)
		switch {
		case err == reference.ErrReferenceInvalidFormat:
			err = field.Invalid(field.NewPath("from", "name"), repository.Name, "the provided repository name is not valid")
		case isDockerError(err, v2.ErrorCodeNameUnknown):
			err = kapierrors.NewNotFound(api.Resource("dockerimage"), repository.Ref.Exact())
		case isDockerError(err, errcode.ErrorCodeUnauthorized):
			err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact()))
		case strings.Contains(err.Error(), "tls: oversized record received with length") && !repository.Insecure:
			err = kapierrors.NewBadRequest("this repository is HTTP only and requires the insecure flag to import")
		case strings.HasSuffix(err.Error(), "no basic auth credentials"):
			err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q and did not have credentials to the repository", repository.Ref.Exact()))
		case strings.HasSuffix(err.Error(), "does not support v2 API"):
			importRepositoryFromDockerV1(ctx, repository, limiter)
			return
		}
		applyErrorToRepository(repository, err)
		return
	}

	// get a manifest context
	s, err := repo.Manifests(ctx)
	if err != nil {
		glog.V(5).Infof("unable to access manifests for repository %#v: %#v", repository, err)
		switch {
		case isDockerError(err, v2.ErrorCodeNameUnknown):
			err = kapierrors.NewNotFound(api.Resource("dockerimage"), repository.Ref.Exact())
		case isDockerError(err, errcode.ErrorCodeUnauthorized):
			err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact()))
		case strings.HasSuffix(err.Error(), "no basic auth credentials"):
			err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q and did not have credentials to the repository", repository.Ref.Exact()))
		}
		applyErrorToRepository(repository, err)
		return
	}

	// get a blob context
	b := repo.Blobs(ctx)

	// if repository import is requested (MaximumTags), attempt to load the tags, sort them, and request the first N
	if count := repository.MaximumTags; count > 0 || count == -1 {
		tags, err := repo.Tags(ctx).All(ctx)
		if err != nil {
			glog.V(5).Infof("unable to access tags for repository %#v: %#v", repository, err)
			switch {
			case isDockerError(err, v2.ErrorCodeNameUnknown):
				err = kapierrors.NewNotFound(api.Resource("dockerimage"), repository.Ref.Exact())
			case isDockerError(err, errcode.ErrorCodeUnauthorized):
				err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact()))
			}
			repository.Err = err
			return
		}
		// some images on the Hub have empty tags - treat those as "latest"
		set := sets.NewString(tags...)
		if set.Has("") {
			set.Delete("")
			set.Insert(api.DefaultImageTag)
		}
		tags = set.List()
		// include only the top N tags in the result, put the rest in AdditionalTags
		api.PrioritizeTags(tags)
		for _, s := range tags {
			if count <= 0 && repository.MaximumTags != -1 {
				repository.AdditionalTags = append(repository.AdditionalTags, s)
				continue
			}
			count--
			repository.Tags = append(repository.Tags, importTag{
				Name: s,
			})
		}
	}

	// load digests
	for i := range repository.Digests {
		importDigest := &repository.Digests[i]
		if importDigest.Err != nil || importDigest.Image != nil {
			continue
		}
		d, err := digest.ParseDigest(importDigest.Name)
		if err != nil {
			importDigest.Err = err
			continue
		}
		limiter.Accept()
		manifest, err := s.Get(ctx, d)
		if err != nil {
			glog.V(5).Infof("unable to access digest %q for repository %#v: %#v", d, repository, err)
			importDigest.Err = formatRepositoryError(repository, "", importDigest.Name, err)
			continue
		}

		if signedManifest, isSchema1 := manifest.(*schema1.SignedManifest); isSchema1 {
			importDigest.Image, err = schema1ToImage(signedManifest, d)
		} else if deserializedManifest, isSchema2 := manifest.(*schema2.DeserializedManifest); isSchema2 {
			imageConfig, err := b.Get(ctx, deserializedManifest.Config.Digest)
			if err != nil {
				glog.V(5).Infof("unable to access the image config using digest %q for repository %#v: %#v", d, repository, err)
				if isDockerError(err, v2.ErrorCodeManifestUnknown) {
					ref := repository.Ref
					ref.ID = deserializedManifest.Config.Digest.String()
					importDigest.Err = kapierrors.NewNotFound(api.Resource("dockerimage"), ref.Exact())
				} else {
					importDigest.Err = formatRepositoryError(repository, "", importDigest.Name, err)
				}
				continue
			}

			importDigest.Image, err = schema2ToImage(deserializedManifest, imageConfig, d)
		} else {
			glog.V(5).Infof("unsupported manifest type: %T", manifest)
			continue
		}

		if err != nil {
			importDigest.Err = err
			continue
		}

		if err := api.ImageWithMetadata(importDigest.Image); err != nil {
			importDigest.Err = err
			continue
		}
		if importDigest.Image.DockerImageMetadata.Size == 0 {
			if err := isi.calculateImageSize(ctx, repo, importDigest.Image); err != nil {
				importDigest.Err = err
				continue
			}
		}
	}

	for i := range repository.Tags {
		importTag := &repository.Tags[i]
		if importTag.Err != nil || importTag.Image != nil {
			continue
		}
		limiter.Accept()
		desc, err := repo.Tags(ctx).Get(ctx, importTag.Name)
		if err != nil {
			glog.V(5).Infof("unable to get tag %q for repository %#v: %#v", importTag.Name, repository, err)
			importTag.Err = formatRepositoryError(repository, importTag.Name, "", err)
			continue
		}
		manifest, err := s.Get(ctx, desc.Digest)
		if err != nil {
			glog.V(5).Infof("unable to access digest %q for tag %q for repository %#v: %#v", desc.Digest, importTag.Name, repository, err)
			importTag.Err = formatRepositoryError(repository, importTag.Name, "", err)
			continue
		}

		if signedManifest, isSchema1 := manifest.(*schema1.SignedManifest); isSchema1 {
			importTag.Image, err = schema1ToImage(signedManifest, "")
		} else if deserializedManifest, isSchema2 := manifest.(*schema2.DeserializedManifest); isSchema2 {
			imageConfig, err := b.Get(ctx, deserializedManifest.Config.Digest)
			if err != nil {
				glog.V(5).Infof("unable to access image config using digest %q for tag %q for repository %#v: %#v", desc.Digest, importTag.Name, repository, err)
				importTag.Err = formatRepositoryError(repository, importTag.Name, "", err)
				continue
			}
			importTag.Image, err = schema2ToImage(deserializedManifest, imageConfig, "")
		} else {
			glog.V(5).Infof("unsupported manifest type: %T", manifest)
			continue
		}

		if err != nil {
			importTag.Err = err
			continue
		}
		if err := api.ImageWithMetadata(importTag.Image); err != nil {
			importTag.Err = err
			continue
		}
		if importTag.Image.DockerImageMetadata.Size == 0 {
			if err := isi.calculateImageSize(ctx, repo, importTag.Image); err != nil {
				importTag.Err = err
				continue
			}
		}
	}
}
Example #11
0
// TestIsErrorQuotaExceeded verifies that if a resource exceedes allowed usage, the admission will return
// error we can recognize.
func TestIsErrorQuotaExceeded(t *testing.T) {
	for _, tc := range []struct {
		name        string
		err         error
		shouldMatch bool
	}{
		{
			name: "unrelated error",
			err:  errors.New("unrelated"),
		},
		{
			name: "wrong type",
			err:  errors.New(errQuotaMessageString),
		},
		{
			name: "wrong kapi type",
			err:  kerrors.NewUnauthorized(errQuotaMessageString),
		},
		{
			name: "unrelated forbidden error",
			err:  kerrors.NewForbidden(kapi.Resource("imageStreams"), "is", errors.New("unrelated")),
		},
		{
			name: "unrelated invalid error",
			err: kerrors.NewInvalid(imageapi.Kind("imageStreams"), "is",
				field.ErrorList{
					field.Required(field.NewPath("imageStream").Child("Spec"), "detail"),
				}),
		},
		{
			name: "quota error not recognized with invalid reason",
			err: kerrors.NewInvalid(imageapi.Kind("imageStreams"), "is",
				field.ErrorList{
					field.Forbidden(field.NewPath("imageStreams"), errQuotaMessageString),
				}),
		},
		{
			name: "quota unknown error not recognized with invalid reason",
			err: kerrors.NewInvalid(imageapi.Kind("imageStreams"), "is",
				field.ErrorList{
					field.Forbidden(field.NewPath("imageStreams"), errQuotaUnknownMessageString),
				}),
		},
		{
			name:        "quota exceeded error",
			err:         kerrors.NewForbidden(kapi.Resource("imageStream"), "is", errors.New(errQuotaMessageString)),
			shouldMatch: true,
		},
		{
			name:        "quota unknown error",
			err:         kerrors.NewForbidden(kapi.Resource("imageStream"), "is", errors.New(errQuotaUnknownMessageString)),
			shouldMatch: true,
		},
		{
			name:        "limits exceeded error with forbidden reason",
			err:         kerrors.NewForbidden(imageapi.Resource("imageStream"), "is", errors.New(errLimitsMessageString)),
			shouldMatch: true,
		},
		{
			name: "limits exceeded error with invalid reason",
			err: kerrors.NewInvalid(imageapi.Kind("imageStreams"), "is",
				field.ErrorList{
					field.Forbidden(field.NewPath("imageStream"), errLimitsMessageString),
				}),
			shouldMatch: true,
		},
	} {
		match := IsErrorQuotaExceeded(tc.err)

		if !match && tc.shouldMatch {
			t.Errorf("[%s] expected to match error [%T]: %v", tc.name, tc.err, tc.err)
		}
		if match && !tc.shouldMatch {
			t.Errorf("[%s] expected not to match error [%T]: %v", tc.name, tc.err, tc.err)
		}
	}
}
Example #12
0
// importRepositoryFromDocker loads the tags and images requested in the passed importRepository, obeying the
// optional rate limiter.  Errors are set onto the individual tags and digest objects.
func importRepositoryFromDocker(ctx gocontext.Context, retriever RepositoryRetriever, repository *importRepository, limiter util.RateLimiter) {
	// retrieve the repository
	repo, err := retriever.Repository(ctx, repository.Registry, repository.Name, repository.Insecure)
	if err != nil {
		glog.V(5).Infof("unable to access repository %#v: %#v", repository, err)
		switch {
		case isDockerError(err, v2.ErrorCodeNameUnknown):
			err = kapierrors.NewNotFound("DockerImage", repository.Ref.Exact())
		case isDockerError(err, errcode.ErrorCodeUnauthorized):
			err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact()))
		case strings.Contains(err.Error(), "tls: oversized record received with length") && !repository.Insecure:
			err = kapierrors.NewBadRequest("this repository is HTTP only and requires the insecure flag to import")
		case strings.HasSuffix(err.Error(), "no basic auth credentials"):
			err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q and did not have credentials to the repository", repository.Ref.Exact()))
		case strings.HasSuffix(err.Error(), "does not support v2 API"):
			importRepositoryFromDockerV1(ctx, repository, limiter)
			return
		}
		applyErrorToRepository(repository, err)
		return
	}

	// get a manifest context
	s, err := repo.Manifests(ctx)
	if err != nil {
		glog.V(5).Infof("unable to access manifests for repository %#v: %#v", repository, err)
		switch {
		case isDockerError(err, v2.ErrorCodeNameUnknown):
			err = kapierrors.NewNotFound("DockerImage", repository.Ref.Exact())
		case isDockerError(err, errcode.ErrorCodeUnauthorized):
			err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact()))
		case strings.HasSuffix(err.Error(), "no basic auth credentials"):
			err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q and did not have credentials to the repository", repository.Ref.Exact()))
		}
		applyErrorToRepository(repository, err)
		return
	}

	// if repository import is requested (MaximumTags), attempt to load the tags, sort them, and request the first N
	if count := repository.MaximumTags; count > 0 || count == -1 {
		tags, err := s.Tags()
		if err != nil {
			glog.V(5).Infof("unable to access tags for repository %#v: %#v", repository, err)
			switch {
			case isDockerError(err, v2.ErrorCodeNameUnknown):
				err = kapierrors.NewNotFound("DockerImage", repository.Ref.Exact())
			case isDockerError(err, errcode.ErrorCodeUnauthorized):
				err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact()))
			}
			repository.Err = err
			return
		}
		// some images on the Hub have empty tags - treat those as "latest"
		set := sets.NewString(tags...)
		if set.Has("") {
			set.Delete("")
			set.Insert(api.DefaultImageTag)
		}
		tags = set.List()
		// include only the top N tags in the result, put the rest in AdditionalTags
		api.PrioritizeTags(tags)
		for _, s := range tags {
			if count <= 0 && repository.MaximumTags != -1 {
				repository.AdditionalTags = append(repository.AdditionalTags, s)
				continue
			}
			count--
			repository.Tags = append(repository.Tags, importTag{
				Name: s,
			})
		}
	}

	// load digests
	for i := range repository.Digests {
		importDigest := &repository.Digests[i]
		if importDigest.Err != nil || importDigest.Image != nil {
			continue
		}
		d, err := digest.ParseDigest(importDigest.Name)
		if err != nil {
			importDigest.Err = err
			continue
		}
		limiter.Accept()
		m, err := s.Get(d)
		if err != nil {
			glog.V(5).Infof("unable to access digest %q for repository %#v: %#v", d, repository, err)
			switch {
			case isDockerError(err, v2.ErrorCodeManifestUnknown):
				ref := repository.Ref
				ref.Tag, ref.ID = "", importDigest.Name
				err = kapierrors.NewNotFound("DockerImage", ref.Exact())
			case isDockerError(err, errcode.ErrorCodeUnauthorized):
				err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact()))
			case strings.HasSuffix(err.Error(), "no basic auth credentials"):
				err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact()))
			}
			importDigest.Err = err
			continue
		}
		importDigest.Image, err = schema1ToImage(m, d)
		if err != nil {
			importDigest.Err = err
			continue
		}
		if err := api.ImageWithMetadata(importDigest.Image); err != nil {
			importDigest.Err = err
			continue
		}
	}

	for i := range repository.Tags {
		importTag := &repository.Tags[i]
		if importTag.Err != nil || importTag.Image != nil {
			continue
		}
		limiter.Accept()
		m, err := s.GetByTag(importTag.Name)
		if err != nil {
			glog.V(5).Infof("unable to access tag %q for repository %#v: %#v", importTag.Name, repository, err)
			switch {
			case isDockerError(err, v2.ErrorCodeManifestUnknown):
				ref := repository.Ref
				ref.Tag = importTag.Name
				err = kapierrors.NewNotFound("DockerImage", ref.Exact())
			case isDockerError(err, errcode.ErrorCodeUnauthorized):
				err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact()))
			case strings.HasSuffix(err.Error(), "no basic auth credentials"):
				err = kapierrors.NewUnauthorized(fmt.Sprintf("you may not have access to the Docker image %q", repository.Ref.Exact()))
			}
			importTag.Err = err
			continue
		}
		importTag.Image, err = schema1ToImage(m, "")
		if err != nil {
			importTag.Err = err
			continue
		}
		if err := api.ImageWithMetadata(importTag.Image); err != nil {
			importTag.Err = err
			continue
		}
	}
}
Example #13
0
// RequestToken locates an openshift oauth server and attempts to authenticate.
// It returns the access token if it gets one, or an error if it does not.
// It should only be invoked once on a given RequestTokenOptions instance.
// The Handler held by the options is released as part of this call.
func (o *RequestTokenOptions) RequestToken() (string, error) {
	defer func() {
		// Always release the handler
		if err := o.Handler.Release(); err != nil {
			// Release errors shouldn't fail the token request, just log
			glog.V(4).Infof("error releasing handler: %v", err)
		}
	}()

	rt, err := restclient.TransportFor(o.ClientConfig)
	if err != nil {
		return "", err
	}

	// requestURL holds the current URL to make requests to. This can change if the server responds with a redirect
	requestURL := o.ClientConfig.Host + "/oauth/authorize?response_type=token&client_id=openshift-challenging-client"
	// requestHeaders holds additional headers to add to the request. This can be changed by o.Handlers
	requestHeaders := http.Header{}
	// requestedURLSet/requestedURLList hold the URLs we have requested, to prevent redirect loops. Gets reset when a challenge is handled.
	requestedURLSet := sets.NewString()
	requestedURLList := []string{}
	handledChallenge := false

	for {
		// Make the request
		resp, err := request(rt, requestURL, requestHeaders)
		if err != nil {
			return "", err
		}
		defer resp.Body.Close()

		if resp.StatusCode == http.StatusUnauthorized {
			if resp.Header.Get("WWW-Authenticate") != "" {
				if !o.Handler.CanHandle(resp.Header) {
					return "", apierrs.NewUnauthorized("unhandled challenge")
				}
				// Handle the challenge
				newRequestHeaders, shouldRetry, err := o.Handler.HandleChallenge(requestURL, resp.Header)
				if err != nil {
					return "", err
				}
				if !shouldRetry {
					return "", apierrs.NewUnauthorized("challenger chose not to retry the request")
				}
				// Remember if we've ever handled a challenge
				handledChallenge = true

				// Reset request set/list. Since we're setting different headers, it is legitimate to request the same urls
				requestedURLSet = sets.NewString()
				requestedURLList = []string{}
				// Use the response to the challenge as the new headers
				requestHeaders = newRequestHeaders
				continue
			}

			// Unauthorized with no challenge
			unauthorizedError := apierrs.NewUnauthorized("")
			// Attempt to read body content and include as an error detail
			if details, err := ioutil.ReadAll(resp.Body); err == nil && len(details) > 0 {
				unauthorizedError.ErrStatus.Details = &unversioned.StatusDetails{
					Causes: []unversioned.StatusCause{
						{Message: string(details)},
					},
				}
			}

			return "", unauthorizedError
		}

		// if we've ever handled a challenge, see if the handler also considers the interaction complete.
		// this is required for negotiate flows with mutual authentication.
		if handledChallenge {
			if err := o.Handler.CompleteChallenge(requestURL, resp.Header); err != nil {
				return "", err
			}
		}

		if resp.StatusCode == http.StatusFound {
			redirectURL := resp.Header.Get("Location")

			// OAuth response case (access_token or error parameter)
			accessToken, err := oauthAuthorizeResult(redirectURL)
			if err != nil {
				return "", err
			}
			if len(accessToken) > 0 {
				return accessToken, err
			}

			// Non-OAuth response, just follow the URL
			// add to our list of redirects
			requestedURLList = append(requestedURLList, redirectURL)
			// detect loops
			if !requestedURLSet.Has(redirectURL) {
				requestedURLSet.Insert(redirectURL)
				requestURL = redirectURL
				continue
			}
			return "", apierrs.NewInternalError(fmt.Errorf("redirect loop: %s", strings.Join(requestedURLList, " -> ")))
		}

		// Unknown response
		return "", apierrs.NewInternalError(fmt.Errorf("unexpected response: %d", resp.StatusCode))
	}
}
Example #14
0
func TestCache(t *testing.T) {
	tokenAuthInvocations := []string{}
	tokenAuth := authenticator.TokenFunc(func(token string) (user.Info, bool, error) {
		tokenAuthInvocations = append(tokenAuthInvocations, token)
		switch {
		case strings.HasPrefix(token, "user"):
			return &user.DefaultInfo{Name: token}, true, nil
		case strings.HasPrefix(token, "unauthorized"):
			return nil, false, kerrs.NewUnauthorized(token)
		case strings.HasPrefix(token, "error"):
			return nil, false, errors.New(token)
		default:
			return nil, false, nil
		}
	})

	tests := map[string]struct {
		TTL       time.Duration
		CacheSize int

		Requests            []testRequest
		ExpectedInvocations []string
		ExpectedCacheSize   int
	}{
		"miss": {
			TTL:       time.Minute,
			CacheSize: 1,
			Requests: []testRequest{
				{Token: "user1", ExpectedUserName: "******", ExpectedOK: true},
			},
			ExpectedInvocations: []string{"user1"},
			ExpectedCacheSize:   1,
		},
		"cache hit user": {
			TTL:       time.Minute,
			CacheSize: 1,
			Requests: []testRequest{
				{Token: "user1", ExpectedUserName: "******", ExpectedOK: true},
				{Token: "user1", ExpectedUserName: "******", ExpectedOK: true},
			},
			ExpectedInvocations: []string{"user1"},
			ExpectedCacheSize:   1,
		},
		"cache hit invalid": {
			TTL:       time.Minute,
			CacheSize: 1,
			Requests: []testRequest{
				{Token: "invalid1", ExpectedOK: false},
				{Token: "invalid1", ExpectedOK: false},
			},
			ExpectedInvocations: []string{"invalid1"},
			ExpectedCacheSize:   1,
		},
		"cache hit unauthorized error": {
			TTL:       time.Minute,
			CacheSize: 1,
			Requests: []testRequest{
				{Token: "unauthorized1", ExpectedErr: true},
				{Token: "unauthorized1", ExpectedErr: true},
			},
			ExpectedInvocations: []string{"unauthorized1"},
			ExpectedCacheSize:   1,
		},
		"uncacheable error": {
			TTL:       time.Minute,
			CacheSize: 1,
			Requests: []testRequest{
				{Token: "error1", ExpectedErr: true},
				{Token: "error1", ExpectedErr: true},
			},
			ExpectedInvocations: []string{"error1", "error1"},
			ExpectedCacheSize:   0,
		},
		"expire": {
			TTL:       time.Minute,
			CacheSize: 1,
			Requests: []testRequest{
				{Token: "user1", ExpectedUserName: "******", ExpectedOK: true},
				{Token: "user1", ExpectedUserName: "******", ExpectedOK: true, Offset: 2 * time.Minute},
			},
			ExpectedInvocations: []string{"user1", "user1"},
			ExpectedCacheSize:   1,
		},
		"evacuation": {
			TTL:       time.Minute,
			CacheSize: 2,
			Requests: []testRequest{
				// Request user1
				{Token: "user1", ExpectedUserName: "******", ExpectedOK: true},
				// Requests for user2 and user3 evacuate user1
				{Token: "user2", ExpectedUserName: "******", ExpectedOK: true, Offset: 10 * time.Second},
				{Token: "user3", ExpectedUserName: "******", ExpectedOK: true, Offset: 20 * time.Second},
				{Token: "user2", ExpectedUserName: "******", ExpectedOK: true, Offset: 30 * time.Second},
				{Token: "user3", ExpectedUserName: "******", ExpectedOK: true, Offset: 40 * time.Second},
				{Token: "user2", ExpectedUserName: "******", ExpectedOK: true, Offset: 50 * time.Second},
				{Token: "user3", ExpectedUserName: "******", ExpectedOK: true, Offset: 60 * time.Second},
				// Request for user1 refetches
				{Token: "user1", ExpectedUserName: "******", ExpectedOK: true},
			},
			ExpectedInvocations: []string{"user1", "user2", "user3", "user1"},
			ExpectedCacheSize:   2,
		},
	}

	for k, tc := range tests {
		tokenAuthInvocations = []string{}
		start := time.Now()

		auth, err := NewAuthenticator(tokenAuth, tc.TTL, tc.CacheSize)
		if err != nil {
			t.Errorf("%s: Unexpected error: %v", k, err)
		}
		cacheAuth := auth.(*CacheAuthenticator)
		for i, r := range tc.Requests {
			cacheAuth.now = func() time.Time { return start.Add(r.Offset) }
			u, ok, err := cacheAuth.AuthenticateToken(r.Token)

			if r.ExpectedErr != (err != nil) {
				t.Errorf("%s: %d: Expected err=%v, got %v", k, i, r.ExpectedErr, err)
				continue
			}
			if ok != r.ExpectedOK {
				t.Errorf("%s: %d: Expected ok=%v, got %v", k, i, r.ExpectedOK, ok)
				continue
			}
			if ok && u.GetName() != r.ExpectedUserName {
				t.Errorf("%s: %d: Expected username=%v, got %v", k, i, r.ExpectedUserName, u.GetName())
				continue
			}
		}

		if !reflect.DeepEqual(tc.ExpectedInvocations, tokenAuthInvocations) {
			t.Errorf("%s: Expected invocations=%v, got %v", k, tc.ExpectedInvocations, tokenAuthInvocations)
		}
		if cacheAuth.cache.Len() != tc.ExpectedCacheSize {
			t.Errorf("%s: Expected cache size %d, got %d", k, tc.ExpectedCacheSize, cacheAuth.cache.Len())
		}
	}
}