Example #1
0
func handleError(ctx context.Context, err error, w http.ResponseWriter) {
	ctx, w = context.WithResponseWriter(ctx, w)

	if serveErr := errcode.ServeJSON(w, err); serveErr != nil {
		context.GetResponseLogger(ctx).Errorf("error sending error response: %v", serveErr)
		return
	}

	context.GetResponseLogger(ctx).Info("application error")
}
Example #2
0
// ServeHTTP serves an HTTP request and implements the http.Handler interface.
func (root *rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	ctx := ctxu.WithRequest(root.context, r)
	ctx, w = ctxu.WithResponseWriter(ctx, w)
	ctx = ctxu.WithLogger(ctx, ctxu.GetRequestLogger(ctx))
	ctx = context.WithValue(ctx, "repo", vars["imageName"])
	ctx = context.WithValue(ctx, "cryptoService", root.trust)

	defer func() {
		ctxu.GetResponseLogger(ctx).Info("response completed")
	}()

	if root.auth != nil {
		access := buildAccessRecords(vars["imageName"], root.actions...)
		var authCtx context.Context
		var err error
		if authCtx, err = root.auth.Authorized(ctx, access...); err != nil {
			if err, ok := err.(auth.Challenge); ok {
				err.ServeHTTP(w, r)
				w.WriteHeader(http.StatusUnauthorized)
				return
			}
			errcode.ServeJSON(w, v2.ErrorCodeUnauthorized)
			return
		}
		ctx = authCtx
	}
	if err := root.handler(ctx, w, r); err != nil {
		e := errcode.ServeJSON(w, err)
		if e != nil {
			logrus.Error(e)
		}
		return
	}
}
Example #3
0
func (app *App) logError(context context.Context, errors errcode.Errors) {
	for _, e1 := range errors {
		var c ctxu.Context

		switch e1.(type) {
		case errcode.Error:
			e, _ := e1.(errcode.Error)
			c = ctxu.WithValue(context, "err.code", e.Code)
			c = ctxu.WithValue(c, "err.message", e.Code.Message())
			c = ctxu.WithValue(c, "err.detail", e.Detail)
		case errcode.ErrorCode:
			e, _ := e1.(errcode.ErrorCode)
			c = ctxu.WithValue(context, "err.code", e)
			c = ctxu.WithValue(c, "err.message", e.Message())
		default:
			// just normal go 'error'
			c = ctxu.WithValue(context, "err.code", errcode.ErrorCodeUnknown)
			c = ctxu.WithValue(c, "err.message", e1.Error())
		}

		c = ctxu.WithLogger(c, ctxu.GetLogger(c,
			"err.code",
			"err.message",
			"err.detail"))
		ctxu.GetResponseLogger(c).Errorf("response completed with error")
	}
}
Example #4
0
func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close() // ensure that request body is always closed.

	// Instantiate an http context here so we can track the error codes
	// returned by the request router.
	ctx := defaultContextManager.context(app, w, r)

	defer func() {
		status, ok := ctx.Value("http.response.status").(int)
		if ok && status >= 200 && status <= 399 {
			ctxu.GetResponseLogger(ctx).Infof("response completed")
		}
	}()
	defer defaultContextManager.release(ctx)

	// NOTE(stevvooe): Total hack to get instrumented responsewriter from context.
	var err error
	w, err = ctxu.GetResponseWriter(ctx)
	if err != nil {
		ctxu.GetLogger(ctx).Warnf("response writer not found in context")
	}

	// Set a header with the Docker Distribution API Version for all responses.
	w.Header().Add("Docker-Distribution-API-Version", "registry/2.0")
	app.router.ServeHTTP(w, r)
}
Example #5
0
// ServeHTTP serves an HTTP request and implements the http.Handler interface.
func (root *rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var (
		err  error
		ctx  = ctxu.WithRequest(root.context, r)
		log  = ctxu.GetRequestLogger(ctx)
		vars = mux.Vars(r)
	)
	ctx, w = ctxu.WithResponseWriter(ctx, w)
	ctx = ctxu.WithLogger(ctx, log)
	ctx = context.WithValue(ctx, notary.CtxKeyCryptoSvc, root.trust)

	defer func() {
		ctxu.GetResponseLogger(ctx).Info("response completed")
	}()

	if root.auth != nil {
		ctx = context.WithValue(ctx, notary.CtxKeyRepo, vars["imageName"])
		if ctx, err = root.doAuth(ctx, vars["imageName"], w); err != nil {
			// errors have already been logged/output to w inside doAuth
			// just return
			return
		}
	}
	if err := root.handler(ctx, w, r); err != nil {
		serveError(log, w, err)
	}
}
Example #6
0
// ServeHTTP serves an HTTP request and implements the http.Handler interface.
func (root *rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	ctx := ctxu.WithRequest(root.context, r)
	log := ctxu.GetRequestLogger(ctx)
	ctx, w = ctxu.WithResponseWriter(ctx, w)
	ctx = ctxu.WithLogger(ctx, log)
	ctx = context.WithValue(ctx, "repo", vars["imageName"])
	ctx = context.WithValue(ctx, "cryptoService", root.trust)

	defer func() {
		ctxu.GetResponseLogger(ctx).Info("response completed")
	}()

	if root.auth != nil {
		access := buildAccessRecords(vars["imageName"], root.actions...)
		var authCtx context.Context
		var err error
		if authCtx, err = root.auth.Authorized(ctx, access...); err != nil {
			if challenge, ok := err.(auth.Challenge); ok {
				// Let the challenge write the response.
				challenge.SetHeaders(w)

				if err := errcode.ServeJSON(w, errcode.ErrorCodeUnauthorized.WithDetail(access)); err != nil {
					log.Errorf("failed to serve challenge response: %s", err.Error())
				}
				return
			}
			errcode.ServeJSON(w, errcode.ErrorCodeUnauthorized)
			return
		}
		ctx = authCtx
	}
	if err := root.handler(ctx, w, r); err != nil {
		if httpErr, ok := err.(errcode.ErrorCoder); ok {
			// info level logging for non-5XX http errors
			httpErrCode := httpErr.ErrorCode().Descriptor().HTTPStatusCode
			if httpErrCode >= http.StatusInternalServerError {
				// error level logging for 5XX http errors
				log.Error(httpErr)
			} else {
				log.Info(httpErr)
			}
		}
		e := errcode.ServeJSON(w, err)
		if e != nil {
			log.Error(e)
		}
		return
	}
}
Example #7
0
// postToken handles authenticating the request and authorizing access to the
// requested scopes.
func (ts *tokenServer) postToken(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	grantType := r.PostFormValue("grant_type")
	if grantType == "" {
		handleError(ctx, ErrorMissingRequiredField.WithDetail("missing grant_type value"), w)
		return
	}

	service := r.PostFormValue("service")
	if service == "" {
		handleError(ctx, ErrorMissingRequiredField.WithDetail("missing service value"), w)
		return
	}

	clientID := r.PostFormValue("client_id")
	if clientID == "" {
		handleError(ctx, ErrorMissingRequiredField.WithDetail("missing client_id value"), w)
		return
	}

	var offline bool
	switch r.PostFormValue("access_type") {
	case "", "online":
	case "offline":
		offline = true
	default:
		handleError(ctx, ErrorUnsupportedValue.WithDetail("unknown access_type value"), w)
		return
	}

	requestedAccessList := ResolveScopeList(ctx, r.PostFormValue("scope"))

	var subject string
	var rToken string
	switch grantType {
	case "refresh_token":
		rToken = r.PostFormValue("refresh_token")
		if rToken == "" {
			handleError(ctx, ErrorUnsupportedValue.WithDetail("missing refresh_token value"), w)
			return
		}
		rt, ok := ts.refreshCache[rToken]
		if !ok || rt.service != service {
			handleError(ctx, errcode.ErrorCodeUnauthorized.WithDetail("invalid refresh token"), w)
			return
		}
		subject = rt.subject
	case "password":
		ca, ok := ts.accessController.(auth.CredentialAuthenticator)
		if !ok {
			handleError(ctx, ErrorUnsupportedValue.WithDetail("password grant type not supported"), w)
			return
		}
		subject = r.PostFormValue("username")
		if subject == "" {
			handleError(ctx, ErrorUnsupportedValue.WithDetail("missing username value"), w)
			return
		}
		password := r.PostFormValue("password")
		if password == "" {
			handleError(ctx, ErrorUnsupportedValue.WithDetail("missing password value"), w)
			return
		}
		if err := ca.AuthenticateUser(subject, password); err != nil {
			handleError(ctx, errcode.ErrorCodeUnauthorized.WithDetail("invalid credentials"), w)
			return
		}
	default:
		handleError(ctx, ErrorUnsupportedValue.WithDetail("unknown grant_type value"), w)
		return
	}

	ctx = context.WithValue(ctx, acctSubject{}, subject)
	ctx = context.WithLogger(ctx, context.GetLogger(ctx, acctSubject{}))

	context.GetLogger(ctx).Info("authenticated client")

	ctx = context.WithValue(ctx, requestedAccess{}, requestedAccessList)
	ctx = context.WithLogger(ctx, context.GetLogger(ctx, requestedAccess{}))

	grantedAccessList := filterAccessList(ctx, subject, requestedAccessList)
	ctx = context.WithValue(ctx, grantedAccess{}, grantedAccessList)
	ctx = context.WithLogger(ctx, context.GetLogger(ctx, grantedAccess{}))

	token, err := ts.issuer.CreateJWT(subject, service, grantedAccessList)
	if err != nil {
		handleError(ctx, err, w)
		return
	}

	context.GetLogger(ctx).Info("authorized client")

	response := postTokenResponse{
		Token:     token,
		ExpiresIn: int(ts.issuer.Expiration.Seconds()),
		IssuedAt:  time.Now().UTC().Format(time.RFC3339),
		Scope:     ToScopeList(grantedAccessList),
	}

	if offline {
		rToken = newRefreshToken()
		ts.refreshCache[rToken] = refreshToken{
			subject: subject,
			service: service,
		}
	}

	if rToken != "" {
		response.RefreshToken = rToken
	}

	ctx, w = context.WithResponseWriter(ctx, w)

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)

	context.GetResponseLogger(ctx).Info("post token complete")
}
Example #8
0
// getToken handles authenticating the request and authorizing access to the
// requested scopes.
func (ts *tokenServer) getToken(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	context.GetLogger(ctx).Info("getToken")

	params := r.URL.Query()
	service := params.Get("service")
	scopeSpecifiers := params["scope"]
	var offline bool
	if offlineStr := params.Get("offline_token"); offlineStr != "" {
		var err error
		offline, err = strconv.ParseBool(offlineStr)
		if err != nil {
			handleError(ctx, ErrorBadTokenOption.WithDetail(err), w)
			return
		}
	}

	requestedAccessList := ResolveScopeSpecifiers(ctx, scopeSpecifiers)

	authorizedCtx, err := ts.accessController.Authorized(ctx, requestedAccessList...)
	if err != nil {
		challenge, ok := err.(auth.Challenge)
		if !ok {
			handleError(ctx, err, w)
			return
		}

		// Get response context.
		ctx, w = context.WithResponseWriter(ctx, w)

		challenge.SetHeaders(w)
		handleError(ctx, errcode.ErrorCodeUnauthorized.WithDetail(challenge.Error()), w)

		context.GetResponseLogger(ctx).Info("get token authentication challenge")

		return
	}
	ctx = authorizedCtx

	username := context.GetStringValue(ctx, "auth.user.name")

	ctx = context.WithValue(ctx, acctSubject{}, username)
	ctx = context.WithLogger(ctx, context.GetLogger(ctx, acctSubject{}))

	context.GetLogger(ctx).Info("authenticated client")

	ctx = context.WithValue(ctx, requestedAccess{}, requestedAccessList)
	ctx = context.WithLogger(ctx, context.GetLogger(ctx, requestedAccess{}))

	grantedAccessList := filterAccessList(ctx, username, requestedAccessList)
	ctx = context.WithValue(ctx, grantedAccess{}, grantedAccessList)
	ctx = context.WithLogger(ctx, context.GetLogger(ctx, grantedAccess{}))

	token, err := ts.issuer.CreateJWT(username, service, grantedAccessList)
	if err != nil {
		handleError(ctx, err, w)
		return
	}

	context.GetLogger(ctx).Info("authorized client")

	response := tokenResponse{
		Token:     token,
		ExpiresIn: int(ts.issuer.Expiration.Seconds()),
	}

	if offline {
		response.RefreshToken = newRefreshToken()
		ts.refreshCache[response.RefreshToken] = refreshToken{
			subject: username,
			service: service,
		}
	}

	ctx, w = context.WithResponseWriter(ctx, w)

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)

	context.GetResponseLogger(ctx).Info("get token complete")
}
Example #9
0
// getToken handles authenticating the request and authorizing access to the
// requested scopes.
func (ts *tokenServer) getToken(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	context.GetLogger(ctx).Info("getToken")

	params := r.URL.Query()
	service := params.Get("service")
	scopeSpecifiers := params["scope"]

	requestedAccessList := ResolveScopeSpecifiers(ctx, scopeSpecifiers)

	authorizedCtx, err := ts.accessController.Authorized(ctx, requestedAccessList...)
	if err != nil {
		challenge, ok := err.(auth.Challenge)
		if !ok {
			handleError(ctx, err, w)
			return
		}

		// Get response context.
		ctx, w = context.WithResponseWriter(ctx, w)

		challenge.SetHeaders(w)
		handleError(ctx, errcode.ErrorCodeUnauthorized.WithDetail(challenge.Error()), w)

		context.GetResponseLogger(ctx).Info("get token authentication challenge")

		return
	}
	ctx = authorizedCtx

	username := context.GetStringValue(ctx, "auth.user.name")

	ctx = context.WithValue(ctx, "acctSubject", username)
	ctx = context.WithLogger(ctx, context.GetLogger(ctx, "acctSubject"))

	context.GetLogger(ctx).Info("authenticated client")

	ctx = context.WithValue(ctx, "requestedAccess", requestedAccessList)
	ctx = context.WithLogger(ctx, context.GetLogger(ctx, "requestedAccess"))

	scopePrefix := username + "/"
	grantedAccessList := make([]auth.Access, 0, len(requestedAccessList))
	for _, access := range requestedAccessList {
		if access.Type != "repository" {
			context.GetLogger(ctx).Debugf("Skipping unsupported resource type: %s", access.Type)
			continue
		}
		if !strings.HasPrefix(access.Name, scopePrefix) {
			context.GetLogger(ctx).Debugf("Resource scope not allowed: %s", access.Name)
			continue
		}
		grantedAccessList = append(grantedAccessList, access)
	}

	ctx = context.WithValue(ctx, "grantedAccess", grantedAccessList)
	ctx = context.WithLogger(ctx, context.GetLogger(ctx, "grantedAccess"))

	token, err := ts.issuer.CreateJWT(username, service, grantedAccessList)
	if err != nil {
		handleError(ctx, err, w)
		return
	}

	context.GetLogger(ctx).Info("authorized client")

	// Get response context.
	ctx, w = context.WithResponseWriter(ctx, w)

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{"token": token})

	context.GetResponseLogger(ctx).Info("get token complete")
}