From fa4c33f5f3b02f95869ae374015387a08284b8b8 Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Mon, 28 Sep 2015 10:41:18 -0700 Subject: [PATCH] [api spec] Update authN and authZ errors Associate HTTP 401s with Authentication errors rather than Authorization errors. Changes the meaning of the UNAUTHORIZED error to be authentication specific. Defines DENIED error code to be associated with authorization errors which result in HTTP 403 responses. Add 'No Such Repository' errors to more endpoints. Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) --- docs/api/errcode/register.go | 22 +++- docs/api/v2/descriptors.go | 237 ++++++++++++----------------------- 2 files changed, 100 insertions(+), 159 deletions(-) diff --git a/docs/api/errcode/register.go b/docs/api/errcode/register.go index f3062ffa..01c34384 100644 --- a/docs/api/errcode/register.go +++ b/docs/api/errcode/register.go @@ -33,16 +33,28 @@ var ( HTTPStatusCode: http.StatusMethodNotAllowed, }) - // ErrorCodeUnauthorized is returned if a request is not authorized. + // ErrorCodeUnauthorized is returned if a request requires + // authentication. ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{ Value: "UNAUTHORIZED", - Message: "access to the requested resource is not authorized", - Description: `The access controller denied access for the operation on - a resource. Often this will be accompanied by a 401 Unauthorized - response status.`, + Message: "authentication required", + Description: `The access controller was unable to authenticate + the client. Often this will be accompanied by a + Www-Authenticate HTTP response header indicating how to + authenticate.`, HTTPStatusCode: http.StatusUnauthorized, }) + // ErrorCodeDenied is returned if a client does not have sufficient + // permission to perform an action. + ErrorCodeDenied = Register("errcode", ErrorDescriptor{ + Value: "DENIED", + Message: "requested access to the resource is denied", + Description: `The access controller denied access for the + operation on a resource.`, + HTTPStatusCode: http.StatusForbidden, + }) + // ErrorCodeUnavailable provides a common error to report unavialability // of a service or endpoint. ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{ diff --git a/docs/api/v2/descriptors.go b/docs/api/v2/descriptors.go index c5630fed..c8270308 100644 --- a/docs/api/v2/descriptors.go +++ b/docs/api/v2/descriptors.go @@ -111,45 +111,67 @@ var ( }, } - unauthorizedResponse = ResponseDescriptor{ - Description: "The client does not have access to the repository.", + unauthorizedResponseDescriptor = ResponseDescriptor{ + Name: "Authentication Required", StatusCode: http.StatusUnauthorized, + Description: "The client is not authenticated.", Headers: []ParameterDescriptor{ authChallengeHeader, { Name: "Content-Length", Type: "integer", - Description: "Length of the JSON error response body.", + Description: "Length of the JSON response body.", Format: "", }, }, - ErrorCodes: []errcode.ErrorCode{ - errcode.ErrorCodeUnauthorized, - }, Body: BodyDescriptor{ ContentType: "application/json; charset=utf-8", - Format: unauthorizedErrorsBody, + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + errcode.ErrorCodeUnauthorized, }, } - unauthorizedResponsePush = ResponseDescriptor{ - Description: "The client does not have access to push to the repository.", - StatusCode: http.StatusUnauthorized, + repositoryNotFoundResponseDescriptor = ResponseDescriptor{ + Name: "No Such Repository Error", + StatusCode: http.StatusNotFound, + Description: "The repository is not known to the registry.", Headers: []ParameterDescriptor{ - authChallengeHeader, { Name: "Content-Length", Type: "integer", - Description: "Length of the JSON error response body.", + Description: "Length of the JSON response body.", Format: "", }, }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, ErrorCodes: []errcode.ErrorCode{ - errcode.ErrorCodeUnauthorized, + ErrorCodeNameUnknown, + }, + } + + deniedResponseDescriptor = ResponseDescriptor{ + Name: "Access Denied", + StatusCode: http.StatusForbidden, + Description: "The client does not have required access to the repository.", + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, }, Body: BodyDescriptor{ ContentType: "application/json; charset=utf-8", - Format: unauthorizedErrorsBody, + Format: errorsBody, + }, + ErrorCodes: []errcode.ErrorCode{ + errcode.ErrorCodeDenied, }, } ) @@ -345,7 +367,7 @@ var routeDescriptors = []RouteDescriptor{ Name: RouteNameBase, Path: "/v2/", Entity: "Base", - Description: `Base V2 API route. Typically, this can be used for lightweight version checks and to validate registry authorization.`, + Description: `Base V2 API route. Typically, this can be used for lightweight version checks and to validate registry authentication.`, Methods: []MethodDescriptor{ { Method: "GET", @@ -363,24 +385,11 @@ var routeDescriptors = []RouteDescriptor{ }, }, Failures: []ResponseDescriptor{ - { - Description: "The client is not authorized to access the registry.", - StatusCode: http.StatusUnauthorized, - Headers: []ParameterDescriptor{ - authChallengeHeader, - }, - Body: BodyDescriptor{ - ContentType: "application/json; charset=utf-8", - Format: errorsBody, - }, - ErrorCodes: []errcode.ErrorCode{ - errcode.ErrorCodeUnauthorized, - }, - }, { Description: "The registry does not implement the V2 API.", StatusCode: http.StatusNotFound, }, + unauthorizedResponseDescriptor, }, }, }, @@ -432,28 +441,9 @@ var routeDescriptors = []RouteDescriptor{ }, }, Failures: []ResponseDescriptor{ - { - StatusCode: http.StatusNotFound, - Description: "The repository is not known to the registry.", - Body: BodyDescriptor{ - ContentType: "application/json; charset=utf-8", - Format: errorsBody, - }, - ErrorCodes: []errcode.ErrorCode{ - ErrorCodeNameUnknown, - }, - }, - { - StatusCode: http.StatusUnauthorized, - Description: "The client does not have access to the repository.", - Body: BodyDescriptor{ - ContentType: "application/json; charset=utf-8", - Format: errorsBody, - }, - ErrorCodes: []errcode.ErrorCode{ - errcode.ErrorCodeUnauthorized, - }, - }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, { @@ -487,28 +477,9 @@ var routeDescriptors = []RouteDescriptor{ }, }, Failures: []ResponseDescriptor{ - { - StatusCode: http.StatusNotFound, - Description: "The repository is not known to the registry.", - Body: BodyDescriptor{ - ContentType: "application/json; charset=utf-8", - Format: errorsBody, - }, - ErrorCodes: []errcode.ErrorCode{ - ErrorCodeNameUnknown, - }, - }, - { - StatusCode: http.StatusUnauthorized, - Description: "The client does not have access to the repository.", - Body: BodyDescriptor{ - ContentType: "application/json; charset=utf-8", - Format: errorsBody, - }, - ErrorCodes: []errcode.ErrorCode{ - errcode.ErrorCodeUnauthorized, - }, - }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, }, @@ -560,29 +531,9 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, - { - StatusCode: http.StatusUnauthorized, - Description: "The client does not have access to the repository.", - Body: BodyDescriptor{ - ContentType: "application/json; charset=utf-8", - Format: errorsBody, - }, - ErrorCodes: []errcode.ErrorCode{ - errcode.ErrorCodeUnauthorized, - }, - }, - { - Description: "The named manifest is not known to the registry.", - StatusCode: http.StatusNotFound, - ErrorCodes: []errcode.ErrorCode{ - ErrorCodeNameUnknown, - ErrorCodeManifestUnknown, - }, - Body: BodyDescriptor{ - ContentType: "application/json; charset=utf-8", - Format: errorsBody, - }, - }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, }, @@ -637,17 +588,9 @@ var routeDescriptors = []RouteDescriptor{ ErrorCodeBlobUnknown, }, }, - { - StatusCode: http.StatusUnauthorized, - Description: "The client does not have permission to push to the repository.", - Body: BodyDescriptor{ - ContentType: "application/json; charset=utf-8", - Format: errorsBody, - }, - ErrorCodes: []errcode.ErrorCode{ - errcode.ErrorCodeUnauthorized, - }, - }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, { Name: "Missing Layer(s)", Description: "One or more layers may be missing during a manifest upload. If so, the missing layers will be enumerated in the error response.", @@ -670,25 +613,6 @@ var routeDescriptors = []RouteDescriptor{ }`, }, }, - { - StatusCode: http.StatusUnauthorized, - Headers: []ParameterDescriptor{ - authChallengeHeader, - { - Name: "Content-Length", - Type: "integer", - Description: "Length of the JSON error response body.", - Format: "", - }, - }, - ErrorCodes: []errcode.ErrorCode{ - errcode.ErrorCodeUnauthorized, - }, - Body: BodyDescriptor{ - ContentType: "application/json; charset=utf-8", - Format: errorsBody, - }, - }, { Name: "Not allowed", Description: "Manifest put is not allowed because the registry is configured as a pull-through cache or for some other reason", @@ -733,25 +657,9 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, - { - StatusCode: http.StatusUnauthorized, - Headers: []ParameterDescriptor{ - authChallengeHeader, - { - Name: "Content-Length", - Type: "integer", - Description: "Length of the JSON error response body.", - Format: "", - }, - }, - ErrorCodes: []errcode.ErrorCode{ - errcode.ErrorCodeUnauthorized, - }, - Body: BodyDescriptor{ - ContentType: "application/json; charset=utf-8", - Format: errorsBody, - }, - }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, { Name: "Unknown Manifest", Description: "The specified `name` or `reference` are unknown to the registry and the delete was unable to proceed. Clients can assume the manifest was already deleted if this response is returned.", @@ -845,7 +753,6 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, - unauthorizedResponse, { Description: "The blob, identified by `name` and `digest`, is unknown to the registry.", StatusCode: http.StatusNotFound, @@ -858,6 +765,9 @@ var routeDescriptors = []RouteDescriptor{ ErrorCodeBlobUnknown, }, }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, { @@ -914,7 +824,6 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, - unauthorizedResponse, { StatusCode: http.StatusNotFound, ErrorCodes: []errcode.ErrorCode{ @@ -930,6 +839,9 @@ var routeDescriptors = []RouteDescriptor{ Description: "The range specification cannot be satisfied for the requested content. This can happen when the range is not formatted correctly or if the range is outside of the valid size of the content.", StatusCode: http.StatusRequestedRangeNotSatisfiable, }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, }, @@ -993,6 +905,9 @@ var routeDescriptors = []RouteDescriptor{ errcode.ErrorCodeUnsupported, }, }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, }, @@ -1066,7 +981,6 @@ var routeDescriptors = []RouteDescriptor{ ErrorCodeNameInvalid, }, }, - unauthorizedResponsePush, { Name: "Not allowed", Description: "Blob upload is not allowed because the registry is configured as a pull-through cache or for some other reason", @@ -1075,6 +989,9 @@ var routeDescriptors = []RouteDescriptor{ errcode.ErrorCodeUnsupported, }, }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, { @@ -1118,7 +1035,9 @@ var routeDescriptors = []RouteDescriptor{ ErrorCodeNameInvalid, }, }, - unauthorizedResponsePush, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, }, @@ -1177,7 +1096,6 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, - unauthorizedResponse, { Description: "The upload is unknown to the registry. The upload must be restarted.", StatusCode: http.StatusNotFound, @@ -1189,6 +1107,9 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, }, @@ -1249,7 +1170,6 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, - unauthorizedResponsePush, { Description: "The upload is unknown to the registry. The upload must be restarted.", StatusCode: http.StatusNotFound, @@ -1261,6 +1181,9 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, { @@ -1328,7 +1251,6 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, - unauthorizedResponsePush, { Description: "The upload is unknown to the registry. The upload must be restarted.", StatusCode: http.StatusNotFound, @@ -1344,6 +1266,9 @@ var routeDescriptors = []RouteDescriptor{ Description: "The `Content-Range` specification cannot be accepted, either because it does not overlap with the current progress or it is invalid.", StatusCode: http.StatusRequestedRangeNotSatisfiable, }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, }, @@ -1420,7 +1345,6 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, - unauthorizedResponsePush, { Description: "The upload is unknown to the registry. The upload must be restarted.", StatusCode: http.StatusNotFound, @@ -1432,6 +1356,9 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, }, @@ -1474,7 +1401,6 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, - unauthorizedResponse, { Description: "The upload is unknown to the registry. The client may ignore this error and assume the upload has been deleted.", StatusCode: http.StatusNotFound, @@ -1486,6 +1412,9 @@ var routeDescriptors = []RouteDescriptor{ Format: errorsBody, }, }, + unauthorizedResponseDescriptor, + repositoryNotFoundResponseDescriptor, + deniedResponseDescriptor, }, }, },