Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
f35e3d07
Commit
f35e3d07
authored
Jul 03, 2018
by
Jake Sanders
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Escape illegal characters in remote extra keys
Signed-off-by:
Jake Sanders
<
jsand@google.com
>
parent
2a54f83e
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
309 additions
and
8 deletions
+309
-8
requestheader.go
...pkg/authentication/request/headerrequest/requestheader.go
+10
-1
requestheader_test.go
...uthentication/request/headerrequest/requestheader_test.go
+31
-0
impersonation.go
...c/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go
+10
-1
impersonation_test.go
....io/apiserver/pkg/endpoints/filters/impersonation_test.go
+34
-0
round_trippers.go
staging/src/k8s.io/client-go/transport/round_trippers.go
+109
-2
round_trippers_test.go
...ing/src/k8s.io/client-go/transport/round_trippers_test.go
+115
-4
No files found.
staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader.go
View file @
f35e3d07
...
@@ -21,6 +21,7 @@ import (
...
@@ -21,6 +21,7 @@ import (
"fmt"
"fmt"
"io/ioutil"
"io/ioutil"
"net/http"
"net/http"
"net/url"
"strings"
"strings"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/sets"
...
@@ -160,6 +161,14 @@ func allHeaderValues(h http.Header, headerNames []string) []string {
...
@@ -160,6 +161,14 @@ func allHeaderValues(h http.Header, headerNames []string) []string {
return
ret
return
ret
}
}
func
unescapeExtraKey
(
encodedKey
string
)
string
{
key
,
err
:=
url
.
PathUnescape
(
encodedKey
)
// Decode %-encoded bytes.
if
err
!=
nil
{
return
encodedKey
// Always record extra strings, even if malformed/unencoded.
}
return
key
}
func
newExtra
(
h
http
.
Header
,
headerPrefixes
[]
string
)
map
[
string
][]
string
{
func
newExtra
(
h
http
.
Header
,
headerPrefixes
[]
string
)
map
[
string
][]
string
{
ret
:=
map
[
string
][]
string
{}
ret
:=
map
[
string
][]
string
{}
...
@@ -170,7 +179,7 @@ func newExtra(h http.Header, headerPrefixes []string) map[string][]string {
...
@@ -170,7 +179,7 @@ func newExtra(h http.Header, headerPrefixes []string) map[string][]string {
continue
continue
}
}
extraKey
:=
strings
.
ToLower
(
headerName
[
len
(
prefix
)
:
]
)
extraKey
:=
unescapeExtraKey
(
strings
.
ToLower
(
headerName
[
len
(
prefix
)
:
])
)
ret
[
extraKey
]
=
append
(
ret
[
extraKey
],
vv
...
)
ret
[
extraKey
]
=
append
(
ret
[
extraKey
],
vv
...
)
}
}
}
}
...
...
staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_test.go
View file @
f35e3d07
...
@@ -152,6 +152,37 @@ func TestRequestHeader(t *testing.T) {
...
@@ -152,6 +152,37 @@ func TestRequestHeader(t *testing.T) {
},
},
expectedOk
:
true
,
expectedOk
:
true
,
},
},
"escaped extra keys"
:
{
nameHeaders
:
[]
string
{
"X-Remote-User"
},
groupHeaders
:
[]
string
{
"X-Remote-Group"
},
extraPrefixHeaders
:
[]
string
{
"X-Remote-Extra-"
},
requestHeaders
:
http
.
Header
{
"X-Remote-User"
:
{
"Bob"
},
"X-Remote-Group"
:
{
"one-a"
,
"one-b"
},
"X-Remote-Extra-Alpha"
:
{
"alphabetical"
},
"X-Remote-Extra-Alph4num3r1c"
:
{
"alphanumeric"
},
"X-Remote-Extra-Percent%20encoded"
:
{
"percent encoded"
},
"X-Remote-Extra-Almost%zzpercent%xxencoded"
:
{
"not quite percent encoded"
},
"X-Remote-Extra-Example.com%2fpercent%2520encoded"
:
{
"url with double percent encoding"
},
"X-Remote-Extra-Example.com%2F%E4%BB%8A%E6%97%A5%E3%81%AF"
:
{
"url with unicode"
},
"X-Remote-Extra-Abc123!#$+.-_*
\\
^`~|'"
:
{
"header key legal characters"
},
},
expectedUser
:
&
user
.
DefaultInfo
{
Name
:
"Bob"
,
Groups
:
[]
string
{
"one-a"
,
"one-b"
},
Extra
:
map
[
string
][]
string
{
"alpha"
:
{
"alphabetical"
},
"alph4num3r1c"
:
{
"alphanumeric"
},
"percent encoded"
:
{
"percent encoded"
},
"almost%zzpercent%xxencoded"
:
{
"not quite percent encoded"
},
"example.com/percent%20encoded"
:
{
"url with double percent encoding"
},
"example.com/今日は"
:
{
"url with unicode"
},
"abc123!#$+.-_*
\\
^`~|'"
:
{
"header key legal characters"
},
},
},
expectedOk
:
true
,
},
}
}
for
k
,
testcase
:=
range
testcases
{
for
k
,
testcase
:=
range
testcases
{
...
...
staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation.go
View file @
f35e3d07
...
@@ -20,6 +20,7 @@ import (
...
@@ -20,6 +20,7 @@ import (
"errors"
"errors"
"fmt"
"fmt"
"net/http"
"net/http"
"net/url"
"strings"
"strings"
"github.com/golang/glog"
"github.com/golang/glog"
...
@@ -146,6 +147,14 @@ func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime.
...
@@ -146,6 +147,14 @@ func WithImpersonation(handler http.Handler, a authorizer.Authorizer, s runtime.
})
})
}
}
func
unescapeExtraKey
(
encodedKey
string
)
string
{
key
,
err
:=
url
.
PathUnescape
(
encodedKey
)
// Decode %-encoded bytes.
if
err
!=
nil
{
return
encodedKey
// Always record extra strings, even if malformed/unencoded.
}
return
key
}
// buildImpersonationRequests returns a list of objectreferences that represent the different things we're requesting to impersonate.
// buildImpersonationRequests returns a list of objectreferences that represent the different things we're requesting to impersonate.
// Also includes a map[string][]string representing user.Info.Extra
// Also includes a map[string][]string representing user.Info.Extra
// Each request must be authorized against the current user before switching contexts.
// Each request must be authorized against the current user before switching contexts.
...
@@ -175,7 +184,7 @@ func buildImpersonationRequests(headers http.Header) ([]v1.ObjectReference, erro
...
@@ -175,7 +184,7 @@ func buildImpersonationRequests(headers http.Header) ([]v1.ObjectReference, erro
}
}
hasUserExtra
=
true
hasUserExtra
=
true
extraKey
:=
strings
.
ToLower
(
headerName
[
len
(
authenticationv1
.
ImpersonateUserExtraHeaderPrefix
)
:
]
)
extraKey
:=
unescapeExtraKey
(
strings
.
ToLower
(
headerName
[
len
(
authenticationv1
.
ImpersonateUserExtraHeaderPrefix
)
:
])
)
// make a separate request for each extra value they're trying to set
// make a separate request for each extra value they're trying to set
for
_
,
value
:=
range
values
{
for
_
,
value
:=
range
values
{
...
...
staging/src/k8s.io/apiserver/pkg/endpoints/filters/impersonation_test.go
View file @
f35e3d07
...
@@ -70,6 +70,10 @@ func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorized auth
...
@@ -70,6 +70,10 @@ func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorized auth
return
authorizer
.
DecisionAllow
,
""
,
nil
return
authorizer
.
DecisionAllow
,
""
,
nil
}
}
if
len
(
user
.
GetGroups
())
>
1
&&
(
user
.
GetGroups
()[
1
]
==
"escaped-scopes"
||
user
.
GetGroups
()[
1
]
==
"almost-escaped-scopes"
)
{
return
authorizer
.
DecisionAllow
,
""
,
nil
}
if
len
(
user
.
GetGroups
())
>
1
&&
user
.
GetGroups
()[
1
]
==
"extra-setter-particular-scopes"
&&
if
len
(
user
.
GetGroups
())
>
1
&&
user
.
GetGroups
()[
1
]
==
"extra-setter-particular-scopes"
&&
a
.
GetVerb
()
==
"impersonate"
&&
a
.
GetResource
()
==
"userextras"
&&
a
.
GetSubresource
()
==
"scopes"
&&
a
.
GetName
()
==
"scope-a"
{
a
.
GetVerb
()
==
"impersonate"
&&
a
.
GetResource
()
==
"userextras"
&&
a
.
GetSubresource
()
==
"scopes"
&&
a
.
GetName
()
==
"scope-a"
{
return
authorizer
.
DecisionAllow
,
""
,
nil
return
authorizer
.
DecisionAllow
,
""
,
nil
...
@@ -225,6 +229,36 @@ func TestImpersonationFilter(t *testing.T) {
...
@@ -225,6 +229,36 @@ func TestImpersonationFilter(t *testing.T) {
expectedCode
:
http
.
StatusOK
,
expectedCode
:
http
.
StatusOK
,
},
},
{
{
name
:
"percent-escaped-userextras"
,
user
:
&
user
.
DefaultInfo
{
Name
:
"dev"
,
Groups
:
[]
string
{
"wheel"
,
"escaped-scopes"
},
},
impersonationUser
:
"system:admin"
,
impersonationUserExtras
:
map
[
string
][]
string
{
"example.com%2fescaped%e1%9b%84scopes"
:
{
"scope-a"
,
"scope-b"
}},
expectedUser
:
&
user
.
DefaultInfo
{
Name
:
"system:admin"
,
Groups
:
[]
string
{
"system:authenticated"
},
Extra
:
map
[
string
][]
string
{
"example.com/escapedᛄscopes"
:
{
"scope-a"
,
"scope-b"
}},
},
expectedCode
:
http
.
StatusOK
,
},
{
name
:
"almost-percent-escaped-userextras"
,
user
:
&
user
.
DefaultInfo
{
Name
:
"dev"
,
Groups
:
[]
string
{
"wheel"
,
"almost-escaped-scopes"
},
},
impersonationUser
:
"system:admin"
,
impersonationUserExtras
:
map
[
string
][]
string
{
"almost%zzpercent%xxencoded"
:
{
"scope-a"
,
"scope-b"
}},
expectedUser
:
&
user
.
DefaultInfo
{
Name
:
"system:admin"
,
Groups
:
[]
string
{
"system:authenticated"
},
Extra
:
map
[
string
][]
string
{
"almost%zzpercent%xxencoded"
:
{
"scope-a"
,
"scope-b"
}},
},
expectedCode
:
http
.
StatusOK
,
},
{
name
:
"allowed-users-impersonation"
,
name
:
"allowed-users-impersonation"
,
user
:
&
user
.
DefaultInfo
{
user
:
&
user
.
DefaultInfo
{
Name
:
"dev"
,
Name
:
"dev"
,
...
...
staging/src/k8s.io/client-go/transport/round_trippers.go
View file @
f35e3d07
...
@@ -129,7 +129,7 @@ func SetAuthProxyHeaders(req *http.Request, username string, groups []string, ex
...
@@ -129,7 +129,7 @@ func SetAuthProxyHeaders(req *http.Request, username string, groups []string, ex
}
}
for
key
,
values
:=
range
extra
{
for
key
,
values
:=
range
extra
{
for
_
,
value
:=
range
values
{
for
_
,
value
:=
range
values
{
req
.
Header
.
Add
(
"X-Remote-Extra-"
+
key
,
value
)
req
.
Header
.
Add
(
"X-Remote-Extra-"
+
headerKeyEscape
(
key
)
,
value
)
}
}
}
}
}
}
...
@@ -246,7 +246,7 @@ func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Respons
...
@@ -246,7 +246,7 @@ func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Respons
}
}
for
k
,
vv
:=
range
rt
.
impersonate
.
Extra
{
for
k
,
vv
:=
range
rt
.
impersonate
.
Extra
{
for
_
,
v
:=
range
vv
{
for
_
,
v
:=
range
vv
{
req
.
Header
.
Add
(
ImpersonateUserExtraHeaderPrefix
+
k
,
v
)
req
.
Header
.
Add
(
ImpersonateUserExtraHeaderPrefix
+
headerKeyEscape
(
k
)
,
v
)
}
}
}
}
...
@@ -422,3 +422,110 @@ func (rt *debuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, e
...
@@ -422,3 +422,110 @@ func (rt *debuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, e
func
(
rt
*
debuggingRoundTripper
)
WrappedRoundTripper
()
http
.
RoundTripper
{
func
(
rt
*
debuggingRoundTripper
)
WrappedRoundTripper
()
http
.
RoundTripper
{
return
rt
.
delegatedRoundTripper
return
rt
.
delegatedRoundTripper
}
}
func
legalHeaderByte
(
b
byte
)
bool
{
return
int
(
b
)
<
len
(
legalHeaderKeyBytes
)
&&
legalHeaderKeyBytes
[
b
]
}
func
shouldEscape
(
b
byte
)
bool
{
// url.PathUnescape() returns an error if any '%' is not followed by two
// hexadecimal digits, so we'll intentionally encode it.
return
!
legalHeaderByte
(
b
)
||
b
==
'%'
}
func
headerKeyEscape
(
key
string
)
string
{
buf
:=
strings
.
Builder
{}
for
i
:=
0
;
i
<
len
(
key
);
i
++
{
b
:=
key
[
i
]
if
shouldEscape
(
b
)
{
// %-encode bytes that should be escaped:
// https://tools.ietf.org/html/rfc3986#section-2.1
fmt
.
Fprintf
(
&
buf
,
"%%%02X"
,
b
)
continue
}
buf
.
WriteByte
(
b
)
}
return
buf
.
String
()
}
// legalHeaderKeyBytes was copied from net/http/lex.go's isTokenTable.
// See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators
var
legalHeaderKeyBytes
=
[
127
]
bool
{
'%'
:
true
,
'!'
:
true
,
'#'
:
true
,
'$'
:
true
,
'&'
:
true
,
'\'
'
:
true
,
'*'
:
true
,
'+'
:
true
,
'-'
:
true
,
'.'
:
true
,
'0'
:
true
,
'1'
:
true
,
'2'
:
true
,
'3'
:
true
,
'4'
:
true
,
'5'
:
true
,
'6'
:
true
,
'7'
:
true
,
'8'
:
true
,
'9'
:
true
,
'A'
:
true
,
'B'
:
true
,
'C'
:
true
,
'D'
:
true
,
'E'
:
true
,
'F'
:
true
,
'G'
:
true
,
'H'
:
true
,
'I'
:
true
,
'J'
:
true
,
'K'
:
true
,
'L'
:
true
,
'M'
:
true
,
'N'
:
true
,
'O'
:
true
,
'P'
:
true
,
'Q'
:
true
,
'R'
:
true
,
'S'
:
true
,
'T'
:
true
,
'U'
:
true
,
'W'
:
true
,
'V'
:
true
,
'X'
:
true
,
'Y'
:
true
,
'Z'
:
true
,
'^'
:
true
,
'_'
:
true
,
'`'
:
true
,
'a'
:
true
,
'b'
:
true
,
'c'
:
true
,
'd'
:
true
,
'e'
:
true
,
'f'
:
true
,
'g'
:
true
,
'h'
:
true
,
'i'
:
true
,
'j'
:
true
,
'k'
:
true
,
'l'
:
true
,
'm'
:
true
,
'n'
:
true
,
'o'
:
true
,
'p'
:
true
,
'q'
:
true
,
'r'
:
true
,
's'
:
true
,
't'
:
true
,
'u'
:
true
,
'v'
:
true
,
'w'
:
true
,
'x'
:
true
,
'y'
:
true
,
'z'
:
true
,
'|'
:
true
,
'~'
:
true
,
}
staging/src/k8s.io/client-go/transport/round_trippers_test.go
View file @
f35e3d07
...
@@ -18,6 +18,7 @@ package transport
...
@@ -18,6 +18,7 @@ package transport
import
(
import
(
"net/http"
"net/http"
"net/url"
"reflect"
"reflect"
"strings"
"strings"
"testing"
"testing"
...
@@ -125,6 +126,32 @@ func TestImpersonationRoundTripper(t *testing.T) {
...
@@ -125,6 +126,32 @@ func TestImpersonationRoundTripper(t *testing.T) {
ImpersonateUserExtraHeaderPrefix
+
"Second"
:
{
"B"
,
"b"
},
ImpersonateUserExtraHeaderPrefix
+
"Second"
:
{
"B"
,
"b"
},
},
},
},
},
{
name
:
"escape handling"
,
impersonationConfig
:
ImpersonationConfig
{
UserName
:
"user"
,
Extra
:
map
[
string
][]
string
{
"test.example.com/thing.thing"
:
{
"A"
,
"a"
},
},
},
expected
:
map
[
string
][]
string
{
ImpersonateUserHeader
:
{
"user"
},
ImpersonateUserExtraHeaderPrefix
+
`Test.example.com%2fthing.thing`
:
{
"A"
,
"a"
},
},
},
{
name
:
"double escape handling"
,
impersonationConfig
:
ImpersonationConfig
{
UserName
:
"user"
,
Extra
:
map
[
string
][]
string
{
"test.example.com/thing.thing%20another.thing"
:
{
"A"
,
"a"
},
},
},
expected
:
map
[
string
][]
string
{
ImpersonateUserHeader
:
{
"user"
},
ImpersonateUserExtraHeaderPrefix
+
`Test.example.com%2fthing.thing%2520another.thing`
:
{
"A"
,
"a"
},
},
},
}
}
for
_
,
tc
:=
range
tcs
{
for
_
,
tc
:=
range
tcs
{
...
@@ -159,9 +186,10 @@ func TestImpersonationRoundTripper(t *testing.T) {
...
@@ -159,9 +186,10 @@ func TestImpersonationRoundTripper(t *testing.T) {
func
TestAuthProxyRoundTripper
(
t
*
testing
.
T
)
{
func
TestAuthProxyRoundTripper
(
t
*
testing
.
T
)
{
for
n
,
tc
:=
range
map
[
string
]
struct
{
for
n
,
tc
:=
range
map
[
string
]
struct
{
username
string
username
string
groups
[]
string
groups
[]
string
extra
map
[
string
][]
string
extra
map
[
string
][]
string
expectedExtra
map
[
string
][]
string
}{
}{
"allfields"
:
{
"allfields"
:
{
username
:
"user"
,
username
:
"user"
,
...
@@ -170,6 +198,34 @@ func TestAuthProxyRoundTripper(t *testing.T) {
...
@@ -170,6 +198,34 @@ func TestAuthProxyRoundTripper(t *testing.T) {
"one"
:
{
"alpha"
,
"bravo"
},
"one"
:
{
"alpha"
,
"bravo"
},
"two"
:
{
"charlie"
,
"delta"
},
"two"
:
{
"charlie"
,
"delta"
},
},
},
expectedExtra
:
map
[
string
][]
string
{
"one"
:
{
"alpha"
,
"bravo"
},
"two"
:
{
"charlie"
,
"delta"
},
},
},
"escaped extra"
:
{
username
:
"user"
,
groups
:
[]
string
{
"groupA"
,
"groupB"
},
extra
:
map
[
string
][]
string
{
"one"
:
{
"alpha"
,
"bravo"
},
"example.com/two"
:
{
"charlie"
,
"delta"
},
},
expectedExtra
:
map
[
string
][]
string
{
"one"
:
{
"alpha"
,
"bravo"
},
"example.com%2ftwo"
:
{
"charlie"
,
"delta"
},
},
},
"double escaped extra"
:
{
username
:
"user"
,
groups
:
[]
string
{
"groupA"
,
"groupB"
},
extra
:
map
[
string
][]
string
{
"one"
:
{
"alpha"
,
"bravo"
},
"example.com/two%20three"
:
{
"charlie"
,
"delta"
},
},
expectedExtra
:
map
[
string
][]
string
{
"one"
:
{
"alpha"
,
"bravo"
},
"example.com%2ftwo%2520three"
:
{
"charlie"
,
"delta"
},
},
},
},
}
{
}
{
rt
:=
&
testRoundTripper
{}
rt
:=
&
testRoundTripper
{}
...
@@ -210,9 +266,64 @@ func TestAuthProxyRoundTripper(t *testing.T) {
...
@@ -210,9 +266,64 @@ func TestAuthProxyRoundTripper(t *testing.T) {
actualExtra
[
extraKey
]
=
append
(
actualExtra
[
key
],
values
...
)
actualExtra
[
extraKey
]
=
append
(
actualExtra
[
key
],
values
...
)
}
}
}
}
if
e
,
a
:=
tc
.
extra
,
actualExtra
;
!
reflect
.
DeepEqual
(
e
,
a
)
{
if
e
,
a
:=
tc
.
ex
pectedEx
tra
,
actualExtra
;
!
reflect
.
DeepEqual
(
e
,
a
)
{
t
.
Errorf
(
"%s expected %v, got %v"
,
n
,
e
,
a
)
t
.
Errorf
(
"%s expected %v, got %v"
,
n
,
e
,
a
)
continue
continue
}
}
}
}
}
}
// TestHeaderEscapeRoundTrip tests to see if foo == url.PathUnescape(headerEscape(foo))
// This behavior is important for client -> API server transmission of extra values.
func
TestHeaderEscapeRoundTrip
(
t
*
testing
.
T
)
{
t
.
Parallel
()
testCases
:=
[]
struct
{
name
string
key
string
}{
{
name
:
"alpha"
,
key
:
"alphabetical"
,
},
{
name
:
"alphanumeric"
,
key
:
"alph4num3r1c"
,
},
{
name
:
"percent encoded"
,
key
:
"percent%20encoded"
,
},
{
name
:
"almost percent encoded"
,
key
:
"almost%zzpercent%xxencoded"
,
},
{
name
:
"illegal char & percent encoding"
,
key
:
"example.com/percent%20encoded"
,
},
{
name
:
"weird unicode stuff"
,
key
:
"example.com/ᛒᚥᛏᛖᚥᚢとロビン"
,
},
{
name
:
"header legal chars"
,
key
:
"abc123!#$+.-_*
\\
^`~|'"
,
},
{
name
:
"legal path, illegal header"
,
key
:
"@=:"
,
},
}
for
_
,
tc
:=
range
testCases
{
t
.
Run
(
tc
.
name
,
func
(
t
*
testing
.
T
)
{
escaped
:=
headerKeyEscape
(
tc
.
key
)
unescaped
,
err
:=
url
.
PathUnescape
(
escaped
)
if
err
!=
nil
{
t
.
Fatalf
(
"url.PathUnescape(%q) returned error: %v"
,
escaped
,
err
)
}
if
tc
.
key
!=
unescaped
{
t
.
Errorf
(
"url.PathUnescape(headerKeyEscape(%q)) returned %q, wanted %q"
,
tc
.
key
,
unescaped
,
tc
.
key
)
}
})
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment