diff --git a/docs/extend/authorization.md b/docs/extend/authorization.md index a48a8fbdf7..5330e9d245 100644 --- a/docs/extend/authorization.md +++ b/docs/extend/authorization.md @@ -146,13 +146,13 @@ should implement the following two methods: **Request**: ```json -{ - "User": "The user identification" - "UserAuthNMethod": "The authentication method used" - "RequestMethod": "The HTTP method" - "RequestUri": "The HTTP request URI" - "RequestBody": "Byte array containing the raw HTTP request body" - "RequestHeader": "Byte array containing the raw HTTP request header as a map[string][]string " +{ + "User": "The user identification", + "UserAuthNMethod": "The authentication method used", + "RequestMethod": "The HTTP method", + "RequestUri": "The HTTP request URI", + "RequestBody": "Byte array containing the raw HTTP request body", + "RequestHeader": "Byte array containing the raw HTTP request header as a map[string][]string ", "RequestStatusCode": "Request status code" } ``` @@ -160,27 +160,27 @@ should implement the following two methods: **Response**: ```json -{ - "Allow" : "Determined whether the user is allowed or not" - "Msg": "The authorization message" +{ + "Allow": "Determined whether the user is allowed or not", + "Msg": "The authorization message", + "Err": "The error message if things go wrong" } ``` - #### /AuthzPlugin.AuthZRes **Request**: ```json { - "User": "The user identification" - "UserAuthNMethod": "The authentication method used" - "RequestMethod": "The HTTP method" - "RequestUri": "The HTTP request URI" - "RequestBody": "Byte array containing the raw HTTP request body" - "RequestHeader": "Byte array containing the raw HTTP request header as a map[string][]string" - "RequestStatusCode": "Request status code" - "ResponseBody": "Byte array containing the raw HTTP response body" - "ResponseHeader": "Byte array containing the raw HTTP response header as a map[string][]string" + "User": "The user identification", + "UserAuthNMethod": "The authentication method used", + "RequestMethod": "The HTTP method", + "RequestUri": "The HTTP request URI", + "RequestBody": "Byte array containing the raw HTTP request body", + "RequestHeader": "Byte array containing the raw HTTP request header as a map[string][]string", + "RequestStatusCode": "Request status code", + "ResponseBody": "Byte array containing the raw HTTP response body", + "ResponseHeader": "Byte array containing the raw HTTP response header as a map[string][]string", "ResponseStatusCode":"Response status code" } ``` @@ -189,11 +189,12 @@ should implement the following two methods: ```json { - "Allow" : "Determined whether the user is allowed or not" - "Msg": "The authorization message" - "ModifiedBody": "Byte array containing a modified body of the raw HTTP body (or nil if no changes required)" - "ModifiedHeader": "Byte array containing a modified header of the HTTP response (or nil if no changes required)" - "ModifiedStatusCode": "int containing the modified version of the status code (or 0 if not change is required)" + "Allow": "Determined whether the user is allowed or not", + "Msg": "The authorization message", + "Err": "The error message if things go wrong", + "ModifiedBody": "Byte array containing a modified body of the raw HTTP body (or nil if no changes required)", + "ModifiedHeader": "Byte array containing a modified header of the HTTP response (or nil if no changes required)", + "ModifiedStatusCode": "int containing the modified version of the status code (or 0 if not change is required)" } ``` @@ -222,7 +223,8 @@ Request body | []byte | Raw request body Name | Type | Description --------|--------|---------------------------------------------------------------------------------- Allow | bool | Boolean value indicating whether the request is allowed or denied -Message | string | Authorization message (will be returned to the client in case the access is denied) +Msg | string | Authorization message (will be returned to the client in case the access is denied) +Err | string | Error message (will be returned to the client in case the plugin encounter an error) ### Response authorization @@ -249,4 +251,5 @@ Response body | []byte | Raw docker daemon response body Name | Type | Description --------|--------|---------------------------------------------------------------------------------- Allow | bool | Boolean value indicating whether the response is allowed or denied -Message | string | Authorization message (will be returned to the client in case the access is denied) \ No newline at end of file +Msg | string | Authorization message (will be returned to the client in case the access is denied) +Err | string | Error message (will be returned to the client in case the plugin encounter an error) diff --git a/integration-cli/docker_cli_authz_unix_test.go b/integration-cli/docker_cli_authz_unix_test.go index 1f1a758e1f..95b7d74e98 100644 --- a/integration-cli/docker_cli_authz_unix_test.go +++ b/integration-cli/docker_cli_authz_unix_test.go @@ -17,9 +17,12 @@ import ( "github.com/go-check/check" ) -const testAuthZPlugin = "authzplugin" -const unauthorizedMessage = "User unauthorized authz plugin" -const containerListAPI = "/containers/json" +const ( + testAuthZPlugin = "authzplugin" + unauthorizedMessage = "User unauthorized authz plugin" + errorMessage = "something went wrong..." + containerListAPI = "/containers/json" +) func init() { check.Suite(&DockerAuthzSuite{ @@ -66,9 +69,12 @@ func (s *DockerAuthzSuite) SetUpSuite(c *check.C) { }) mux.HandleFunc("/AuthZPlugin.AuthZReq", func(w http.ResponseWriter, r *http.Request) { + if s.ctrl.reqRes.Err != "" { + w.WriteHeader(http.StatusInternalServerError) + } b, err := json.Marshal(s.ctrl.reqRes) - w.Write(b) c.Assert(err, check.IsNil) + w.Write(b) defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) c.Assert(err, check.IsNil) @@ -88,6 +94,9 @@ func (s *DockerAuthzSuite) SetUpSuite(c *check.C) { }) mux.HandleFunc("/AuthZPlugin.AuthZRes", func(w http.ResponseWriter, r *http.Request) { + if s.ctrl.resRes.Err != "" { + w.WriteHeader(http.StatusInternalServerError) + } b, err := json.Marshal(s.ctrl.resRes) c.Assert(err, check.IsNil) w.Write(b) @@ -214,6 +223,31 @@ func (s *DockerAuthzSuite) TestAuthZPluginDenyResponse(c *check.C) { c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: %s\n", unauthorizedMessage)) } +func (s *DockerAuthzSuite) TestAuthZPluginErrorResponse(c *check.C) { + err := s.d.Start("--authz-plugin=" + testAuthZPlugin) + c.Assert(err, check.IsNil) + s.ctrl.reqRes.Allow = true + s.ctrl.resRes.Err = errorMessage + + // Ensure command is blocked + res, err := s.d.Cmd("ps") + c.Assert(err, check.NotNil) + + c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: Plugin Error: %s, %s\n", errorMessage, authorization.AuthZApiResponse)) +} + +func (s *DockerAuthzSuite) TestAuthZPluginErrorRequest(c *check.C) { + err := s.d.Start("--authz-plugin=" + testAuthZPlugin) + c.Assert(err, check.IsNil) + s.ctrl.reqRes.Err = errorMessage + + // Ensure command is blocked + res, err := s.d.Cmd("ps") + c.Assert(err, check.NotNil) + + c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: Plugin Error: %s, %s\n", errorMessage, authorization.AuthZApiRequest)) +} + // assertURIRecorded verifies that the given URI was sent and recorded in the authz plugin func assertURIRecorded(c *check.C, uris []string, uri string) { diff --git a/pkg/authorization/api.go b/pkg/authorization/api.go index 0d931a09c1..fc82c46b01 100644 --- a/pkg/authorization/api.go +++ b/pkg/authorization/api.go @@ -43,10 +43,12 @@ type Request struct { // Response represents authZ plugin response type Response struct { - // Allow indicating whether the user is allowed or not Allow bool `json:"Allow"` // Msg stores the authorization message Msg string `json:"Msg,omitempty"` + + // Err stores a message in case there's an error + Err string `json:"Err,omitempty"` } diff --git a/pkg/authorization/authz.go b/pkg/authorization/authz.go index 0388391ce1..0ccee4f8d2 100644 --- a/pkg/authorization/authz.go +++ b/pkg/authorization/authz.go @@ -84,6 +84,10 @@ func (a *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error { return err } + if authRes.Err != "" { + return fmt.Errorf(authRes.Err) + } + if !authRes.Allow { return fmt.Errorf(authRes.Msg) } @@ -107,6 +111,10 @@ func (a *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error { return err } + if authRes.Err != "" { + return fmt.Errorf(authRes.Err) + } + if !authRes.Allow { return fmt.Errorf(authRes.Msg) } diff --git a/pkg/authorization/authz_test.go b/pkg/authorization/authz_test.go index f1c844c420..369150bca8 100644 --- a/pkg/authorization/authz_test.go +++ b/pkg/authorization/authz_test.go @@ -19,6 +19,37 @@ import ( const pluginAddress = "authzplugin.sock" +func TestAuthZRequestPluginError(t *testing.T) { + server := authZPluginTestServer{t: t} + go server.start() + defer server.stop() + + authZPlugin := createTestPlugin(t) + + request := Request{ + User: "user", + RequestBody: []byte("sample body"), + RequestURI: "www.authz.com", + RequestMethod: "GET", + RequestHeaders: map[string]string{"header": "value"}, + } + server.replayResponse = Response{ + Err: "an error", + } + + actualResponse, err := authZPlugin.AuthZRequest(&request) + if err != nil { + t.Fatalf("Failed to authorize request %v", err) + } + + if !reflect.DeepEqual(server.replayResponse, *actualResponse) { + t.Fatalf("Response must be equal") + } + if !reflect.DeepEqual(request, server.recordedRequest) { + t.Fatalf("Requests must be equal") + } +} + func TestAuthZRequestPlugin(t *testing.T) { server := authZPluginTestServer{t: t} go server.start()