- Return 403 (forbidden) when request is denied in authorization flows
(including integration test)
- Fix #22428
- Close #22431
Signed-off-by: Liron Levin <liron@twistlock.com>
| ... | ... |
@@ -3,7 +3,6 @@ package daemon |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"net" |
| 6 |
- "net/http" |
|
| 7 | 6 |
"strings" |
| 8 | 7 |
|
| 9 | 8 |
netsettings "github.com/docker/docker/daemon/network" |
| ... | ... |
@@ -97,7 +96,7 @@ func (daemon *Daemon) getAllNetworks() []libnetwork.Network {
|
| 97 | 97 |
func (daemon *Daemon) CreateNetwork(create types.NetworkCreateRequest) (*types.NetworkCreateResponse, error) {
|
| 98 | 98 |
if runconfig.IsPreDefinedNetwork(create.Name) {
|
| 99 | 99 |
err := fmt.Errorf("%s is a pre-defined network and cannot be created", create.Name)
|
| 100 |
- return nil, errors.NewErrorWithStatusCode(err, http.StatusForbidden) |
|
| 100 |
+ return nil, errors.NewRequestForbiddenError(err) |
|
| 101 | 101 |
} |
| 102 | 102 |
|
| 103 | 103 |
var warning string |
| ... | ... |
@@ -221,7 +220,7 @@ func (daemon *Daemon) DeleteNetwork(networkID string) error {
|
| 221 | 221 |
|
| 222 | 222 |
if runconfig.IsPreDefinedNetwork(nw.Name()) {
|
| 223 | 223 |
err := fmt.Errorf("%s is a pre-defined network and cannot be removed", nw.Name())
|
| 224 |
- return errors.NewErrorWithStatusCode(err, http.StatusForbidden) |
|
| 224 |
+ return errors.NewRequestForbiddenError(err) |
|
| 225 | 225 |
} |
| 226 | 226 |
|
| 227 | 227 |
if err := nw.Delete(); err != nil {
|
| ... | ... |
@@ -28,6 +28,12 @@ func NewBadRequestError(err error) error {
|
| 28 | 28 |
return NewErrorWithStatusCode(err, http.StatusBadRequest) |
| 29 | 29 |
} |
| 30 | 30 |
|
| 31 |
+// NewRequestForbiddenError creates a new API error |
|
| 32 |
+// that has the 403 HTTP status code associated to it. |
|
| 33 |
+func NewRequestForbiddenError(err error) error {
|
|
| 34 |
+ return NewErrorWithStatusCode(err, http.StatusForbidden) |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 31 | 37 |
// NewRequestNotFoundError creates a new API error |
| 32 | 38 |
// that has the 404 HTTP status code associated to it. |
| 33 | 39 |
func NewRequestNotFoundError(err error) error {
|
| ... | ... |
@@ -21,6 +21,9 @@ import ( |
| 21 | 21 |
"github.com/docker/docker/pkg/integration/checker" |
| 22 | 22 |
"github.com/docker/docker/pkg/plugins" |
| 23 | 23 |
"github.com/go-check/check" |
| 24 |
+ "net" |
|
| 25 |
+ "net/http/httputil" |
|
| 26 |
+ "net/url" |
|
| 24 | 27 |
) |
| 25 | 28 |
|
| 26 | 29 |
const ( |
| ... | ... |
@@ -272,6 +275,27 @@ func (s *DockerAuthzSuite) TestAuthZPluginDenyRequest(c *check.C) {
|
| 272 | 272 |
c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage))
|
| 273 | 273 |
} |
| 274 | 274 |
|
| 275 |
+// TestAuthZPluginApiDenyResponse validates that when authorization plugin deny the request, the status code is forbidden |
|
| 276 |
+func (s *DockerAuthzSuite) TestAuthZPluginApiDenyResponse(c *check.C) {
|
|
| 277 |
+ err := s.d.Start("--authorization-plugin=" + testAuthZPlugin)
|
|
| 278 |
+ c.Assert(err, check.IsNil) |
|
| 279 |
+ s.ctrl.reqRes.Allow = false |
|
| 280 |
+ s.ctrl.resRes.Msg = unauthorizedMessage |
|
| 281 |
+ |
|
| 282 |
+ daemonURL, err := url.Parse(s.d.sock()) |
|
| 283 |
+ |
|
| 284 |
+ conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10) |
|
| 285 |
+ c.Assert(err, check.IsNil) |
|
| 286 |
+ client := httputil.NewClientConn(conn, nil) |
|
| 287 |
+ req, err := http.NewRequest("GET", "/version", nil)
|
|
| 288 |
+ c.Assert(err, check.IsNil) |
|
| 289 |
+ resp, err := client.Do(req) |
|
| 290 |
+ |
|
| 291 |
+ c.Assert(err, check.IsNil) |
|
| 292 |
+ c.Assert(resp.StatusCode, checker.Equals, http.StatusForbidden) |
|
| 293 |
+ c.Assert(err, checker.IsNil) |
|
| 294 |
+} |
|
| 295 |
+ |
|
| 275 | 296 |
func (s *DockerAuthzSuite) TestAuthZPluginDenyResponse(c *check.C) {
|
| 276 | 297 |
err := s.d.Start("--authorization-plugin=" + testAuthZPlugin)
|
| 277 | 298 |
c.Assert(err, check.IsNil) |
| ... | ... |
@@ -85,7 +85,7 @@ func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
|
| 85 | 85 |
} |
| 86 | 86 |
|
| 87 | 87 |
if !authRes.Allow {
|
| 88 |
- return fmt.Errorf("authorization denied by plugin %s: %s", plugin.Name(), authRes.Msg)
|
|
| 88 |
+ return newAuthorizationError(plugin.Name(), authRes.Msg) |
|
| 89 | 89 |
} |
| 90 | 90 |
} |
| 91 | 91 |
|
| ... | ... |
@@ -110,7 +110,7 @@ func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error {
|
| 110 | 110 |
} |
| 111 | 111 |
|
| 112 | 112 |
if !authRes.Allow {
|
| 113 |
- return fmt.Errorf("authorization denied by plugin %s: %s", plugin.Name(), authRes.Msg)
|
|
| 113 |
+ return newAuthorizationError(plugin.Name(), authRes.Msg) |
|
| 114 | 114 |
} |
| 115 | 115 |
} |
| 116 | 116 |
|
| ... | ... |
@@ -163,3 +163,17 @@ func headers(header http.Header) map[string]string {
|
| 163 | 163 |
} |
| 164 | 164 |
return v |
| 165 | 165 |
} |
| 166 |
+ |
|
| 167 |
+// authorizationError represents an authorization deny error |
|
| 168 |
+type authorizationError struct {
|
|
| 169 |
+ error |
|
| 170 |
+} |
|
| 171 |
+ |
|
| 172 |
+// HTTPErrorStatusCode returns the authorization error status code (forbidden) |
|
| 173 |
+func (e authorizationError) HTTPErrorStatusCode() int {
|
|
| 174 |
+ return http.StatusForbidden |
|
| 175 |
+} |
|
| 176 |
+ |
|
| 177 |
+func newAuthorizationError(plugin, msg string) authorizationError {
|
|
| 178 |
+ return authorizationError{error: fmt.Errorf("authorization denied by plugin %s: %s", plugin, msg)}
|
|
| 179 |
+} |