Skip to content

Commit 2ac8a47

Browse files
jamesonhyde-dockervvoland
authored andcommitted
Authz plugin security fixes for 0-length content and path validation
Signed-off-by: Jameson Hyde <[email protected]> fix comments (cherry picked from commit 9659c3a) Signed-off-by: Paweł Gronowski <[email protected]>
1 parent 2cfc2a5 commit 2ac8a47

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
@@ -8,6 +8,8 @@ import (
88
"io"
99
"mime"
1010
"net/http"
11+
"net/url"
12+
"regexp"
1113
"strings"
1214

1315
"github.com/containerd/log"
@@ -53,10 +55,23 @@ type Ctx struct {
5355
authReq *Request
5456
}
5557

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

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

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

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)