mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 20:53:33 +00:00
allow auth proxy to set groups and extra
This commit is contained in:
parent
44f00e1019
commit
c4e2e19e51
@ -88,6 +88,9 @@ func New(config AuthenticatorConfig) (authenticator.Request, *spec.SecurityDefin
|
|||||||
config.RequestHeaderConfig.ClientCA,
|
config.RequestHeaderConfig.ClientCA,
|
||||||
config.RequestHeaderConfig.AllowedClientNames,
|
config.RequestHeaderConfig.AllowedClientNames,
|
||||||
config.RequestHeaderConfig.UsernameHeaders,
|
config.RequestHeaderConfig.UsernameHeaders,
|
||||||
|
// TODO add wiring after options are refactored in 1.6
|
||||||
|
[]string{},
|
||||||
|
[]string{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -33,23 +33,51 @@ import (
|
|||||||
type requestHeaderAuthRequestHandler struct {
|
type requestHeaderAuthRequestHandler struct {
|
||||||
// nameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
|
// nameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
|
||||||
nameHeaders []string
|
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) {
|
func New(nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
|
||||||
headers := []string{}
|
trimmedNameHeaders, err := trimHeaders(nameHeaders...)
|
||||||
for _, headerName := range 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)
|
trimmedHeader := strings.TrimSpace(headerName)
|
||||||
if len(trimmedHeader) == 0 {
|
if len(trimmedHeader) == 0 {
|
||||||
return nil, fmt.Errorf("empty header %q", headerName)
|
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) {
|
func NewSecure(clientCA string, proxyClientNames []string, nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
|
||||||
headerAuthenticator, err := New(nameHeaders)
|
headerAuthenticator, err := New(nameHeaders, groupHeaders, extraHeaderPrefixes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -81,8 +109,27 @@ func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request)
|
|||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
return nil, false, nil
|
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 {
|
func headerValue(h http.Header, headerNames []string) string {
|
||||||
@ -94,3 +141,38 @@ func headerValue(h http.Header, headerNames []string) string {
|
|||||||
}
|
}
|
||||||
return ""
|
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) {
|
func TestRequestHeader(t *testing.T) {
|
||||||
testcases := map[string]struct {
|
testcases := map[string]struct {
|
||||||
nameHeaders []string
|
nameHeaders []string
|
||||||
requestHeaders http.Header
|
groupHeaders []string
|
||||||
|
extraPrefixHeaders []string
|
||||||
|
requestHeaders http.Header
|
||||||
|
|
||||||
expectedUser user.Info
|
expectedUser user.Info
|
||||||
expectedOk bool
|
expectedOk bool
|
||||||
}{
|
}{
|
||||||
"empty": {},
|
"empty": {},
|
||||||
"no match": {
|
"user no match": {
|
||||||
nameHeaders: []string{"X-Remote-User"},
|
nameHeaders: []string{"X-Remote-User"},
|
||||||
},
|
},
|
||||||
"match": {
|
"user match": {
|
||||||
nameHeaders: []string{"X-Remote-User"},
|
nameHeaders: []string{"X-Remote-User"},
|
||||||
requestHeaders: http.Header{"X-Remote-User": {"Bob"}},
|
requestHeaders: http.Header{"X-Remote-User": {"Bob"}},
|
||||||
expectedUser: &user.DefaultInfo{Name: "Bob"},
|
expectedUser: &user.DefaultInfo{
|
||||||
expectedOk: true,
|
Name: "Bob",
|
||||||
|
Groups: []string{},
|
||||||
|
Extra: map[string][]string{},
|
||||||
|
},
|
||||||
|
expectedOk: true,
|
||||||
},
|
},
|
||||||
"exact match": {
|
"user exact match": {
|
||||||
nameHeaders: []string{"X-Remote-User"},
|
nameHeaders: []string{"X-Remote-User"},
|
||||||
requestHeaders: http.Header{
|
requestHeaders: http.Header{
|
||||||
"Prefixed-X-Remote-User-With-Suffix": {"Bob"},
|
"Prefixed-X-Remote-User-With-Suffix": {"Bob"},
|
||||||
"X-Remote-User-With-Suffix": {"Bob"},
|
"X-Remote-User-With-Suffix": {"Bob"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"first match": {
|
"user first match": {
|
||||||
nameHeaders: []string{
|
nameHeaders: []string{
|
||||||
"X-Remote-User",
|
"X-Remote-User",
|
||||||
"A-Second-X-Remote-User",
|
"A-Second-X-Remote-User",
|
||||||
@ -59,19 +65,83 @@ func TestRequestHeader(t *testing.T) {
|
|||||||
"X-Remote-User": {"", "First header, second value"},
|
"X-Remote-User": {"", "First header, second value"},
|
||||||
"A-Second-X-Remote-User": {"Second header, first value", "Second header, second value"},
|
"A-Second-X-Remote-User": {"Second header, first value", "Second header, second value"},
|
||||||
"Another-X-Remote-User": {"Third header, first value"}},
|
"Another-X-Remote-User": {"Third header, first value"}},
|
||||||
expectedUser: &user.DefaultInfo{Name: "Second header, first value"},
|
expectedUser: &user.DefaultInfo{
|
||||||
expectedOk: true,
|
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
|
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
|
requestHeaders: http.Header{"X-Remote-User": {"Bob"}}, // the parsed headers are normalized by the http package
|
||||||
expectedUser: &user.DefaultInfo{Name: "Bob"},
|
expectedUser: &user.DefaultInfo{
|
||||||
expectedOk: true,
|
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 {
|
for k, testcase := range testcases {
|
||||||
auth, err := New(testcase.nameHeaders)
|
auth, err := New(testcase.nameHeaders, testcase.groupHeaders, testcase.extraPrefixHeaders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user