mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-11-02 03:08:15 +00:00
Merge pull request #36816 from deads2k/api-43-front-proxy
Automatic merge from submit-queue plumb in front proxy group header Builds on https://github.com/kubernetes/kubernetes/pull/36662 and https://github.com/kubernetes/kubernetes/pull/36774, so only the last commit is unique. This completes the plumbing for front proxy header information and makes it possible to add just the front proxy header authenticator. WIP because I'm going to assess it in use downstream.
This commit is contained in:
@@ -33,23 +33,51 @@ import (
|
||||
type requestHeaderAuthRequestHandler struct {
|
||||
// nameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
|
||||
nameHeaders []string
|
||||
|
||||
// groupHeaders are the headers to check (case-insensitively) for group membership. All values of all headers will be added.
|
||||
groupHeaders []string
|
||||
|
||||
// extraHeaderPrefixes are the head prefixes to check (case-insensitively) for filling in
|
||||
// the user.Info.Extra. All values of all matching headers will be added.
|
||||
extraHeaderPrefixes []string
|
||||
}
|
||||
|
||||
func New(nameHeaders []string) (authenticator.Request, error) {
|
||||
headers := []string{}
|
||||
for _, headerName := range nameHeaders {
|
||||
func New(nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
|
||||
trimmedNameHeaders, err := trimHeaders(nameHeaders...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trimmedGroupHeaders, err := trimHeaders(groupHeaders...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trimmedExtraHeaderPrefixes, err := trimHeaders(extraHeaderPrefixes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &requestHeaderAuthRequestHandler{
|
||||
nameHeaders: trimmedNameHeaders,
|
||||
groupHeaders: trimmedGroupHeaders,
|
||||
extraHeaderPrefixes: trimmedExtraHeaderPrefixes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func trimHeaders(headerNames ...string) ([]string, error) {
|
||||
ret := []string{}
|
||||
for _, headerName := range headerNames {
|
||||
trimmedHeader := strings.TrimSpace(headerName)
|
||||
if len(trimmedHeader) == 0 {
|
||||
return nil, fmt.Errorf("empty header %q", headerName)
|
||||
}
|
||||
headers = append(headers, trimmedHeader)
|
||||
ret = append(ret, trimmedHeader)
|
||||
}
|
||||
|
||||
return &requestHeaderAuthRequestHandler{nameHeaders: headers}, nil
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func NewSecure(clientCA string, proxyClientNames []string, nameHeaders []string) (authenticator.Request, error) {
|
||||
headerAuthenticator, err := New(nameHeaders)
|
||||
func NewSecure(clientCA string, proxyClientNames []string, nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
|
||||
headerAuthenticator, err := New(nameHeaders, groupHeaders, extraHeaderPrefixes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -81,8 +109,27 @@ func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request)
|
||||
if len(name) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
groups := allHeaderValues(req.Header, a.groupHeaders)
|
||||
extra := newExtra(req.Header, a.extraHeaderPrefixes)
|
||||
|
||||
return &user.DefaultInfo{Name: name}, true, nil
|
||||
// clear headers used for authentication
|
||||
for _, headerName := range a.nameHeaders {
|
||||
req.Header.Del(headerName)
|
||||
}
|
||||
for _, headerName := range a.groupHeaders {
|
||||
req.Header.Del(headerName)
|
||||
}
|
||||
for k := range extra {
|
||||
for _, prefix := range a.extraHeaderPrefixes {
|
||||
req.Header.Del(prefix + k)
|
||||
}
|
||||
}
|
||||
|
||||
return &user.DefaultInfo{
|
||||
Name: name,
|
||||
Groups: groups,
|
||||
Extra: extra,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
func headerValue(h http.Header, headerNames []string) string {
|
||||
@@ -94,3 +141,38 @@ func headerValue(h http.Header, headerNames []string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func allHeaderValues(h http.Header, headerNames []string) []string {
|
||||
ret := []string{}
|
||||
for _, headerName := range headerNames {
|
||||
values, ok := h[headerName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, headerValue := range values {
|
||||
if len(headerValue) > 0 {
|
||||
ret = append(ret, headerValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func newExtra(h http.Header, headerPrefixes []string) map[string][]string {
|
||||
ret := map[string][]string{}
|
||||
|
||||
// we have to iterate over prefixes first in order to have proper ordering inside the value slices
|
||||
for _, prefix := range headerPrefixes {
|
||||
for headerName, vv := range h {
|
||||
if !strings.HasPrefix(strings.ToLower(headerName), strings.ToLower(prefix)) {
|
||||
continue
|
||||
}
|
||||
|
||||
extraKey := strings.ToLower(headerName[len(prefix):])
|
||||
ret[extraKey] = append(ret[extraKey], vv...)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -26,30 +26,36 @@ import (
|
||||
|
||||
func TestRequestHeader(t *testing.T) {
|
||||
testcases := map[string]struct {
|
||||
nameHeaders []string
|
||||
requestHeaders http.Header
|
||||
nameHeaders []string
|
||||
groupHeaders []string
|
||||
extraPrefixHeaders []string
|
||||
requestHeaders http.Header
|
||||
|
||||
expectedUser user.Info
|
||||
expectedOk bool
|
||||
}{
|
||||
"empty": {},
|
||||
"no match": {
|
||||
"user no match": {
|
||||
nameHeaders: []string{"X-Remote-User"},
|
||||
},
|
||||
"match": {
|
||||
"user match": {
|
||||
nameHeaders: []string{"X-Remote-User"},
|
||||
requestHeaders: http.Header{"X-Remote-User": {"Bob"}},
|
||||
expectedUser: &user.DefaultInfo{Name: "Bob"},
|
||||
expectedOk: true,
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "Bob",
|
||||
Groups: []string{},
|
||||
Extra: map[string][]string{},
|
||||
},
|
||||
expectedOk: true,
|
||||
},
|
||||
"exact match": {
|
||||
"user exact match": {
|
||||
nameHeaders: []string{"X-Remote-User"},
|
||||
requestHeaders: http.Header{
|
||||
"Prefixed-X-Remote-User-With-Suffix": {"Bob"},
|
||||
"X-Remote-User-With-Suffix": {"Bob"},
|
||||
},
|
||||
},
|
||||
"first match": {
|
||||
"user first match": {
|
||||
nameHeaders: []string{
|
||||
"X-Remote-User",
|
||||
"A-Second-X-Remote-User",
|
||||
@@ -59,19 +65,83 @@ func TestRequestHeader(t *testing.T) {
|
||||
"X-Remote-User": {"", "First header, second value"},
|
||||
"A-Second-X-Remote-User": {"Second header, first value", "Second header, second value"},
|
||||
"Another-X-Remote-User": {"Third header, first value"}},
|
||||
expectedUser: &user.DefaultInfo{Name: "Second header, first value"},
|
||||
expectedOk: true,
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "Second header, first value",
|
||||
Groups: []string{},
|
||||
Extra: map[string][]string{},
|
||||
},
|
||||
expectedOk: true,
|
||||
},
|
||||
"case-insensitive": {
|
||||
"user case-insensitive": {
|
||||
nameHeaders: []string{"x-REMOTE-user"}, // configured headers can be case-insensitive
|
||||
requestHeaders: http.Header{"X-Remote-User": {"Bob"}}, // the parsed headers are normalized by the http package
|
||||
expectedUser: &user.DefaultInfo{Name: "Bob"},
|
||||
expectedOk: true,
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "Bob",
|
||||
Groups: []string{},
|
||||
Extra: map[string][]string{},
|
||||
},
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
"groups none": {
|
||||
nameHeaders: []string{"X-Remote-User"},
|
||||
groupHeaders: []string{"X-Remote-Group"},
|
||||
requestHeaders: http.Header{
|
||||
"X-Remote-User": {"Bob"},
|
||||
},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "Bob",
|
||||
Groups: []string{},
|
||||
Extra: map[string][]string{},
|
||||
},
|
||||
expectedOk: true,
|
||||
},
|
||||
"groups all matches": {
|
||||
nameHeaders: []string{"X-Remote-User"},
|
||||
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
|
||||
requestHeaders: http.Header{
|
||||
"X-Remote-User": {"Bob"},
|
||||
"X-Remote-Group-1": {"one-a", "one-b"},
|
||||
"X-Remote-Group-2": {"two-a", "two-b"},
|
||||
},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "Bob",
|
||||
Groups: []string{"one-a", "one-b", "two-a", "two-b"},
|
||||
Extra: map[string][]string{},
|
||||
},
|
||||
expectedOk: true,
|
||||
},
|
||||
|
||||
"extra prefix matches case-insensitive": {
|
||||
nameHeaders: []string{"X-Remote-User"},
|
||||
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
|
||||
extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
|
||||
requestHeaders: http.Header{
|
||||
"X-Remote-User": {"Bob"},
|
||||
"X-Remote-Group-1": {"one-a", "one-b"},
|
||||
"X-Remote-Group-2": {"two-a", "two-b"},
|
||||
"X-Remote-extra-1-key1": {"alfa", "bravo"},
|
||||
"X-Remote-Extra-1-Key2": {"charlie", "delta"},
|
||||
"X-Remote-Extra-1-": {"india", "juliet"},
|
||||
"X-Remote-extra-2-": {"kilo", "lima"},
|
||||
"X-Remote-extra-2-Key1": {"echo", "foxtrot"},
|
||||
"X-Remote-Extra-2-key2": {"golf", "hotel"},
|
||||
},
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "Bob",
|
||||
Groups: []string{"one-a", "one-b", "two-a", "two-b"},
|
||||
Extra: map[string][]string{
|
||||
"key1": {"alfa", "bravo", "echo", "foxtrot"},
|
||||
"key2": {"charlie", "delta", "golf", "hotel"},
|
||||
"": {"india", "juliet", "kilo", "lima"},
|
||||
},
|
||||
},
|
||||
expectedOk: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, testcase := range testcases {
|
||||
auth, err := New(testcase.nameHeaders)
|
||||
auth, err := New(testcase.nameHeaders, testcase.groupHeaders, testcase.extraPrefixHeaders)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user