- fix naming and formatting
- provide more context when erroring auth
- do not capitalize errors
- fix wrong documentation
- remove ugly remoteError{}
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
| ... | ... |
@@ -56,6 +56,7 @@ func debugRequestMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
| 56 | 56 |
// authorizationMiddleware perform authorization on the request. |
| 57 | 57 |
func (s *Server) authorizationMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
| 58 | 58 |
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| 59 |
+ // FIXME: fill when authN gets in |
|
| 59 | 60 |
// User and UserAuthNMethod are taken from AuthN plugins |
| 60 | 61 |
// Currently tracked in https://github.com/docker/docker/pull/13994 |
| 61 | 62 |
user := "" |
| ... | ... |
@@ -104,9 +104,6 @@ Docker's authorization subsystem supports multiple `--authz-plugin` parameters. |
| 104 | 104 |
|
| 105 | 105 |
### Calling authorized command (allow) |
| 106 | 106 |
|
| 107 |
-Your plugin must support calling the `allow` command to authorize a command. |
|
| 108 |
-This call does not impact Docker's command line. |
|
| 109 |
- |
|
| 110 | 107 |
```bash |
| 111 | 108 |
$ docker pull centos |
| 112 | 109 |
... |
| ... | ... |
@@ -116,21 +113,19 @@ f1b10cd84249: Pull complete |
| 116 | 116 |
|
| 117 | 117 |
### Calling unauthorized command (deny) |
| 118 | 118 |
|
| 119 |
-Your plugin must support calling the `deny` command to report on the outcome of |
|
| 120 |
-a plugin interaction. This call returns messages to Docker's command line informing |
|
| 121 |
-the user of the outcome of each call. |
|
| 122 |
- |
|
| 123 | 119 |
```bash |
| 124 | 120 |
$ docker pull centos |
| 125 |
-… |
|
| 126 |
-Authorization failed. Pull command for user 'john_doe' is |
|
| 127 |
-denied by authorization plugin 'ACME' with message |
|
| 128 |
-‘[ACME] User 'john_doe' is not allowed to perform the pull |
|
| 129 |
-command’ |
|
| 121 |
+... |
|
| 122 |
+docker: Error response from daemon: authorization denied by plugin PLUGIN_NAME: volumes are not allowed. |
|
| 130 | 123 |
``` |
| 131 | 124 |
|
| 132 |
-Where multiple authorization plugins are installed, multiple messages are expected. |
|
| 125 |
+### Error from plugins |
|
| 133 | 126 |
|
| 127 |
+```bash |
|
| 128 |
+$ docker pull centos |
|
| 129 |
+... |
|
| 130 |
+docker: Error response from daemon: plugin PLUGIN_NAME failed with error: AuthZPlugin.AuthZReq: Cannot connect to the Docker daemon. Is the docker daemon running on this host?. |
|
| 131 |
+``` |
|
| 134 | 132 |
|
| 135 | 133 |
## API schema and implementation |
| 136 | 134 |
|
| ... | ... |
@@ -43,7 +43,6 @@ type authorizationController struct {
|
| 43 | 43 |
psRequestCnt int // psRequestCnt counts the number of calls to list container request api |
| 44 | 44 |
psResponseCnt int // psResponseCnt counts the number of calls to list containers response API |
| 45 | 45 |
requestsURIs []string // requestsURIs stores all request URIs that are sent to the authorization controller |
| 46 |
- |
|
| 47 | 46 |
} |
| 48 | 47 |
|
| 49 | 48 |
func (s *DockerAuthzSuite) SetUpTest(c *check.C) {
|
| ... | ... |
@@ -165,7 +164,6 @@ func (s *DockerAuthzSuite) TearDownSuite(c *check.C) {
|
| 165 | 165 |
} |
| 166 | 166 |
|
| 167 | 167 |
func (s *DockerAuthzSuite) TestAuthZPluginAllowRequest(c *check.C) {
|
| 168 |
- |
|
| 169 | 168 |
err := s.d.Start("--authz-plugin=" + testAuthZPlugin)
|
| 170 | 169 |
c.Assert(err, check.IsNil) |
| 171 | 170 |
s.ctrl.reqRes.Allow = true |
| ... | ... |
@@ -189,7 +187,6 @@ func (s *DockerAuthzSuite) TestAuthZPluginAllowRequest(c *check.C) {
|
| 189 | 189 |
} |
| 190 | 190 |
|
| 191 | 191 |
func (s *DockerAuthzSuite) TestAuthZPluginDenyRequest(c *check.C) {
|
| 192 |
- |
|
| 193 | 192 |
err := s.d.Start("--authz-plugin=" + testAuthZPlugin)
|
| 194 | 193 |
c.Assert(err, check.IsNil) |
| 195 | 194 |
s.ctrl.reqRes.Allow = false |
| ... | ... |
@@ -202,11 +199,10 @@ func (s *DockerAuthzSuite) TestAuthZPluginDenyRequest(c *check.C) {
|
| 202 | 202 |
c.Assert(s.ctrl.psResponseCnt, check.Equals, 0) |
| 203 | 203 |
|
| 204 | 204 |
// Ensure unauthorized message appears in response |
| 205 |
- c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: %s\n", unauthorizedMessage))
|
|
| 205 |
+ c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage))
|
|
| 206 | 206 |
} |
| 207 | 207 |
|
| 208 | 208 |
func (s *DockerAuthzSuite) TestAuthZPluginDenyResponse(c *check.C) {
|
| 209 |
- |
|
| 210 | 209 |
err := s.d.Start("--authz-plugin=" + testAuthZPlugin)
|
| 211 | 210 |
c.Assert(err, check.IsNil) |
| 212 | 211 |
s.ctrl.reqRes.Allow = true |
| ... | ... |
@@ -220,7 +216,7 @@ func (s *DockerAuthzSuite) TestAuthZPluginDenyResponse(c *check.C) {
|
| 220 | 220 |
c.Assert(s.ctrl.psResponseCnt, check.Equals, 1) |
| 221 | 221 |
|
| 222 | 222 |
// Ensure unauthorized message appears in response |
| 223 |
- c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: %s\n", unauthorizedMessage))
|
|
| 223 |
+ c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage))
|
|
| 224 | 224 |
} |
| 225 | 225 |
|
| 226 | 226 |
func (s *DockerAuthzSuite) TestAuthZPluginErrorResponse(c *check.C) {
|
| ... | ... |
@@ -233,7 +229,7 @@ func (s *DockerAuthzSuite) TestAuthZPluginErrorResponse(c *check.C) {
|
| 233 | 233 |
res, err := s.d.Cmd("ps")
|
| 234 | 234 |
c.Assert(err, check.NotNil) |
| 235 | 235 |
|
| 236 |
- c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: Plugin Error: %s, %s\n", errorMessage, authorization.AuthZApiResponse))
|
|
| 236 |
+ c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s\n", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage))
|
|
| 237 | 237 |
} |
| 238 | 238 |
|
| 239 | 239 |
func (s *DockerAuthzSuite) TestAuthZPluginErrorRequest(c *check.C) {
|
| ... | ... |
@@ -245,7 +241,7 @@ func (s *DockerAuthzSuite) TestAuthZPluginErrorRequest(c *check.C) {
|
| 245 | 245 |
res, err := s.d.Cmd("ps")
|
| 246 | 246 |
c.Assert(err, check.NotNil) |
| 247 | 247 |
|
| 248 |
- c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: Plugin Error: %s, %s\n", errorMessage, authorization.AuthZApiRequest))
|
|
| 248 |
+ c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s\n", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage))
|
|
| 249 | 249 |
} |
| 250 | 250 |
|
| 251 | 251 |
// assertURIRecorded verifies that the given URI was sent and recorded in the authz plugin |
| ... | ... |
@@ -7,6 +7,8 @@ import ( |
| 7 | 7 |
"io/ioutil" |
| 8 | 8 |
"net/http" |
| 9 | 9 |
"strings" |
| 10 |
+ |
|
| 11 |
+ "github.com/Sirupsen/logrus" |
|
| 10 | 12 |
) |
| 11 | 13 |
|
| 12 | 14 |
// NewCtx creates new authZ context, it is used to store authorization information related to a specific docker |
| ... | ... |
@@ -47,9 +49,9 @@ type Ctx struct {
|
| 47 | 47 |
} |
| 48 | 48 |
|
| 49 | 49 |
// AuthZRequest authorized the request to the docker daemon using authZ plugins |
| 50 |
-func (a *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
|
|
| 50 |
+func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
|
|
| 51 | 51 |
var body []byte |
| 52 |
- if sendBody(a.requestURI, r.Header) {
|
|
| 52 |
+ if sendBody(ctx.requestURI, r.Header) {
|
|
| 53 | 53 |
var ( |
| 54 | 54 |
err error |
| 55 | 55 |
drainedBody io.ReadCloser |
| ... | ... |
@@ -70,26 +72,25 @@ func (a *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
|
| 70 | 70 |
return err |
| 71 | 71 |
} |
| 72 | 72 |
|
| 73 |
- a.authReq = &Request{
|
|
| 74 |
- User: a.user, |
|
| 75 |
- UserAuthNMethod: a.userAuthNMethod, |
|
| 76 |
- RequestMethod: a.requestMethod, |
|
| 77 |
- RequestURI: a.requestURI, |
|
| 73 |
+ ctx.authReq = &Request{
|
|
| 74 |
+ User: ctx.user, |
|
| 75 |
+ UserAuthNMethod: ctx.userAuthNMethod, |
|
| 76 |
+ RequestMethod: ctx.requestMethod, |
|
| 77 |
+ RequestURI: ctx.requestURI, |
|
| 78 | 78 |
RequestBody: body, |
| 79 |
- RequestHeaders: headers(r.Header)} |
|
| 79 |
+ RequestHeaders: headers(r.Header), |
|
| 80 |
+ } |
|
| 80 | 81 |
|
| 81 |
- for _, plugin := range a.plugins {
|
|
| 82 |
- authRes, err := plugin.AuthZRequest(a.authReq) |
|
| 83 |
- if err != nil {
|
|
| 84 |
- return err |
|
| 85 |
- } |
|
| 82 |
+ for _, plugin := range ctx.plugins {
|
|
| 83 |
+ logrus.Debugf("AuthZ request using plugin %s", plugin.Name())
|
|
| 86 | 84 |
|
| 87 |
- if authRes.Err != "" {
|
|
| 88 |
- return fmt.Errorf(authRes.Err) |
|
| 85 |
+ authRes, err := plugin.AuthZRequest(ctx.authReq) |
|
| 86 |
+ if err != nil {
|
|
| 87 |
+ return fmt.Errorf("plugin %s failed with error: %s", plugin.Name(), err)
|
|
| 89 | 88 |
} |
| 90 | 89 |
|
| 91 | 90 |
if !authRes.Allow {
|
| 92 |
- return fmt.Errorf(authRes.Msg) |
|
| 91 |
+ return fmt.Errorf("authorization denied by plugin %s: %s", plugin.Name(), authRes.Msg)
|
|
| 93 | 92 |
} |
| 94 | 93 |
} |
| 95 | 94 |
|
| ... | ... |
@@ -97,26 +98,24 @@ func (a *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
|
| 97 | 97 |
} |
| 98 | 98 |
|
| 99 | 99 |
// AuthZResponse authorized and manipulates the response from docker daemon using authZ plugins |
| 100 |
-func (a *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error {
|
|
| 101 |
- a.authReq.ResponseStatusCode = rm.StatusCode() |
|
| 102 |
- a.authReq.ResponseHeaders = headers(rm.Header()) |
|
| 100 |
+func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error {
|
|
| 101 |
+ ctx.authReq.ResponseStatusCode = rm.StatusCode() |
|
| 102 |
+ ctx.authReq.ResponseHeaders = headers(rm.Header()) |
|
| 103 | 103 |
|
| 104 |
- if sendBody(a.requestURI, rm.Header()) {
|
|
| 105 |
- a.authReq.ResponseBody = rm.RawBody() |
|
| 104 |
+ if sendBody(ctx.requestURI, rm.Header()) {
|
|
| 105 |
+ ctx.authReq.ResponseBody = rm.RawBody() |
|
| 106 | 106 |
} |
| 107 | 107 |
|
| 108 |
- for _, plugin := range a.plugins {
|
|
| 109 |
- authRes, err := plugin.AuthZResponse(a.authReq) |
|
| 110 |
- if err != nil {
|
|
| 111 |
- return err |
|
| 112 |
- } |
|
| 108 |
+ for _, plugin := range ctx.plugins {
|
|
| 109 |
+ logrus.Debugf("AuthZ response using plugin %s", plugin.Name())
|
|
| 113 | 110 |
|
| 114 |
- if authRes.Err != "" {
|
|
| 115 |
- return fmt.Errorf(authRes.Err) |
|
| 111 |
+ authRes, err := plugin.AuthZResponse(ctx.authReq) |
|
| 112 |
+ if err != nil {
|
|
| 113 |
+ return fmt.Errorf("plugin %s failed with error: %s", plugin.Name(), err)
|
|
| 116 | 114 |
} |
| 117 | 115 |
|
| 118 | 116 |
if !authRes.Allow {
|
| 119 |
- return fmt.Errorf(authRes.Msg) |
|
| 117 |
+ return fmt.Errorf("authorization denied by plugin %s: %s", plugin.Name(), authRes.Msg)
|
|
| 120 | 118 |
} |
| 121 | 119 |
} |
| 122 | 120 |
|
| ... | ... |
@@ -1,13 +1,13 @@ |
| 1 | 1 |
package authorization |
| 2 | 2 |
|
| 3 |
-import ( |
|
| 4 |
- "github.com/Sirupsen/logrus" |
|
| 5 |
- "github.com/docker/docker/pkg/plugins" |
|
| 6 |
-) |
|
| 3 |
+import "github.com/docker/docker/pkg/plugins" |
|
| 7 | 4 |
|
| 8 | 5 |
// Plugin allows third party plugins to authorize requests and responses |
| 9 | 6 |
// in the context of docker API |
| 10 | 7 |
type Plugin interface {
|
| 8 |
+ // Name returns the registered plugin name |
|
| 9 |
+ Name() string |
|
| 10 |
+ |
|
| 11 | 11 |
// AuthZRequest authorize the request from the client to the daemon |
| 12 | 12 |
AuthZRequest(*Request) (*Response, error) |
| 13 | 13 |
|
| ... | ... |
@@ -34,9 +34,11 @@ func newAuthorizationPlugin(name string) Plugin {
|
| 34 | 34 |
return &authorizationPlugin{name: name}
|
| 35 | 35 |
} |
| 36 | 36 |
|
| 37 |
-func (a *authorizationPlugin) AuthZRequest(authReq *Request) (*Response, error) {
|
|
| 38 |
- logrus.Debugf("AuthZ requset using plugins %s", a.name)
|
|
| 37 |
+func (a *authorizationPlugin) Name() string {
|
|
| 38 |
+ return a.name |
|
| 39 |
+} |
|
| 39 | 40 |
|
| 41 |
+func (a *authorizationPlugin) AuthZRequest(authReq *Request) (*Response, error) {
|
|
| 40 | 42 |
if err := a.initPlugin(); err != nil {
|
| 41 | 43 |
return nil, err |
| 42 | 44 |
} |
| ... | ... |
@@ -50,8 +52,6 @@ func (a *authorizationPlugin) AuthZRequest(authReq *Request) (*Response, error) |
| 50 | 50 |
} |
| 51 | 51 |
|
| 52 | 52 |
func (a *authorizationPlugin) AuthZResponse(authReq *Request) (*Response, error) {
|
| 53 |
- logrus.Debugf("AuthZ response using plugins %s", a.name)
|
|
| 54 |
- |
|
| 55 | 53 |
if err := a.initPlugin(); err != nil {
|
| 56 | 54 |
return nil, err |
| 57 | 55 |
} |
| ... | ... |
@@ -20,15 +20,6 @@ const ( |
| 20 | 20 |
defaultTimeOut = 30 |
| 21 | 21 |
) |
| 22 | 22 |
|
| 23 |
-type remoteError struct {
|
|
| 24 |
- method string |
|
| 25 |
- err string |
|
| 26 |
-} |
|
| 27 |
- |
|
| 28 |
-func (e *remoteError) Error() string {
|
|
| 29 |
- return fmt.Sprintf("Plugin Error: %s, %s", e.err, e.method)
|
|
| 30 |
-} |
|
| 31 |
- |
|
| 32 | 23 |
// NewClient creates a new plugin client (http). |
| 33 | 24 |
func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
|
| 34 | 25 |
tr := &http.Transport{}
|
| ... | ... |
@@ -133,7 +124,7 @@ func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool) |
| 133 | 133 |
if resp.StatusCode != http.StatusOK {
|
| 134 | 134 |
b, err := ioutil.ReadAll(resp.Body) |
| 135 | 135 |
if err != nil {
|
| 136 |
- return nil, &remoteError{method: serviceMethod, err: err.Error()}
|
|
| 136 |
+ return nil, fmt.Errorf("%s: %s", serviceMethod, err)
|
|
| 137 | 137 |
} |
| 138 | 138 |
|
| 139 | 139 |
// Plugins' Response(s) should have an Err field indicating what went |
| ... | ... |
@@ -144,13 +135,13 @@ func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool) |
| 144 | 144 |
} |
| 145 | 145 |
remoteErr := responseErr{}
|
| 146 | 146 |
if err := json.Unmarshal(b, &remoteErr); err != nil {
|
| 147 |
- return nil, &remoteError{method: serviceMethod, err: err.Error()}
|
|
| 147 |
+ return nil, fmt.Errorf("%s: %s", serviceMethod, err)
|
|
| 148 | 148 |
} |
| 149 | 149 |
if remoteErr.Err != "" {
|
| 150 |
- return nil, &remoteError{method: serviceMethod, err: remoteErr.Err}
|
|
| 150 |
+ return nil, fmt.Errorf("%s: %s", serviceMethod, remoteErr.Err)
|
|
| 151 | 151 |
} |
| 152 | 152 |
// old way... |
| 153 |
- return nil, &remoteError{method: serviceMethod, err: string(b)}
|
|
| 153 |
+ return nil, fmt.Errorf("%s: %s", serviceMethod, string(b))
|
|
| 154 | 154 |
} |
| 155 | 155 |
return resp.Body, nil |
| 156 | 156 |
} |