Skip to content

Commit 9659c3a

Browse files
Authz plugin security fixes for 0-length content and path validation
Signed-off-by: Jameson Hyde <[email protected]> fix comments
1 parent 178a055 commit 9659c3a

2 files changed

Lines changed: 80 additions & 7 deletions

File tree

pkg/authorization/authz.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"io"
88
"mime"
99
"net/http"
10+
"net/url"
11+
"regexp"
1012
"strings"
1113

1214
"github.com/docker/docker/pkg/ioutils"
@@ -52,10 +54,23 @@ type Ctx struct {
5254
authReq *Request
5355
}
5456

57+
func isChunked(r *http.Request) bool {
58+
//RFC 7230 specifies that content length is to be ignored if Transfer-Encoding is chunked
59+
if strings.ToLower(r.Header.Get("Transfer-Encoding")) == "chunked" {
60+
return true
61+
}
62+
for _, v := range r.TransferEncoding {
63+
if 0 == strings.Compare(strings.ToLower(v), "chunked") {
64+
return true
65+
}
66+
}
67+
return false
68+
}
69+
5570
// AuthZRequest authorized the request to the docker daemon using authZ plugins
5671
func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
5772
var body []byte
58-
if sendBody(ctx.requestURI, r.Header) && r.ContentLength > 0 && r.ContentLength < maxBodySize {
73+
if sendBody(ctx.requestURI, r.Header) && (r.ContentLength > 0 || isChunked(r)) && r.ContentLength < maxBodySize {
5974
var err error
6075
body, r.Body, err = drainBody(r.Body)
6176
if err != nil {
@@ -108,7 +123,6 @@ func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error {
108123
if sendBody(ctx.requestURI, rm.Header()) {
109124
ctx.authReq.ResponseBody = rm.RawBody()
110125
}
111-
112126
for _, plugin := range ctx.plugins {
113127
logrus.Debugf("AuthZ response using plugin %s", plugin.Name())
114128

@@ -146,10 +160,26 @@ func drainBody(body io.ReadCloser) ([]byte, io.ReadCloser, error) {
146160
return nil, newBody, err
147161
}
148162

163+
func isAuthEndpoint(urlPath string) (bool, error) {
164+
// eg www.test.com/v1.24/auth/optional?optional1=something&optional2=something (version optional)
165+
matched, err := regexp.MatchString(`^[^\/]+\/(v\d[\d\.]*\/)?auth.*`, urlPath)
166+
if err != nil {
167+
return false, err
168+
}
169+
return matched, nil
170+
}
171+
149172
// sendBody returns true when request/response body should be sent to AuthZPlugin
150-
func sendBody(url string, header http.Header) bool {
173+
func sendBody(inURL string, header http.Header) bool {
174+
u, err := url.Parse(inURL)
175+
// Assume no if the URL cannot be parsed - an empty request will still be forwarded to the plugin and should be rejected
176+
if err != nil {
177+
return false
178+
}
179+
151180
// Skip body for auth endpoint
152-
if strings.HasSuffix(url, "/auth") {
181+
isAuth, err := isAuthEndpoint(u.Path)
182+
if isAuth || err != nil {
153183
return false
154184
}
155185

pkg/authorization/authz_unix_test.go

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ func TestDrainBody(t *testing.T) {
174174

175175
func TestSendBody(t *testing.T) {
176176
var (
177-
url = "nothing.com"
178177
testcases = []struct {
178+
url string
179179
contentType string
180180
expected bool
181181
}{
@@ -219,15 +219,58 @@ func TestSendBody(t *testing.T) {
219219
contentType: "",
220220
expected: false,
221221
},
222+
{
223+
url: "nothing.com/auth",
224+
contentType: "",
225+
expected: false,
226+
},
227+
{
228+
url: "nothing.com/auth",
229+
contentType: "application/json;charset=UTF8",
230+
expected: false,
231+
},
232+
{
233+
url: "nothing.com/auth?p1=test",
234+
contentType: "application/json;charset=UTF8",
235+
expected: false,
236+
},
237+
{
238+
url: "nothing.com/test?p1=/auth",
239+
contentType: "application/json;charset=UTF8",
240+
expected: true,
241+
},
242+
{
243+
url: "nothing.com/something/auth",
244+
contentType: "application/json;charset=UTF8",
245+
expected: true,
246+
},
247+
{
248+
url: "nothing.com/auth/test",
249+
contentType: "application/json;charset=UTF8",
250+
expected: false,
251+
},
252+
{
253+
url: "nothing.com/v1.24/auth/test",
254+
contentType: "application/json;charset=UTF8",
255+
expected: false,
256+
},
257+
{
258+
url: "nothing.com/v1/auth/test",
259+
contentType: "application/json;charset=UTF8",
260+
expected: false,
261+
},
222262
}
223263
)
224264

225265
for _, testcase := range testcases {
226266
header := http.Header{}
227267
header.Set("Content-Type", testcase.contentType)
268+
if testcase.url == "" {
269+
testcase.url = "nothing.com"
270+
}
228271

229-
if b := sendBody(url, header); b != testcase.expected {
230-
t.Fatalf("Unexpected Content-Type; Expected: %t, Actual: %t", testcase.expected, b)
272+
if b := sendBody(testcase.url, header); b != testcase.expected {
273+
t.Fatalf("sendBody failed: url: %s, content-type: %s; Expected: %t, Actual: %t", testcase.url, testcase.contentType, testcase.expected, b)
231274
}
232275
}
233276
}

0 commit comments

Comments
 (0)