Browse code

authZ: more fixes

- 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>

Antonio Murdaca authored on 2015/12/18 20:34:19
Showing 6 changed files
... ...
@@ -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
 	}