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
432e6ecd
Commit
432e6ecd
authored
Aug 18, 2016
by
deads2k
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
allow impersonating user.Info.Extra
parent
d12efc47
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
168 additions
and
26 deletions
+168
-26
types.go
pkg/apis/authentication/types.go
+6
-0
handler_impersonation.go
pkg/apiserver/handler_impersonation.go
+63
-20
handler_impersonation_test.go
pkg/apiserver/handler_impersonation_test.go
+97
-6
user.go
pkg/auth/user/user.go
+2
-0
No files found.
pkg/apis/authentication/types.go
View file @
432e6ecd
...
@@ -28,6 +28,12 @@ const (
...
@@ -28,6 +28,12 @@ const (
// ImpersonateGroupHeader is used to impersonate a particular group during an API server request.
// ImpersonateGroupHeader is used to impersonate a particular group during an API server request.
// It can be repeated multipled times for multiple groups.
// It can be repeated multipled times for multiple groups.
ImpersonateGroupHeader
=
"Impersonate-Group"
ImpersonateGroupHeader
=
"Impersonate-Group"
// ImpersonateUserExtraHeaderPrefix is a prefix for any header used to impersonate an entry in the
// extra map[string][]string for user.Info. The key will be every after the prefix.
// It can be repeated multipled times for multiple map keys and the same key can be repeated multiple
// times to have multiple elements in the slice under a single key
ImpersonateUserExtraHeaderPrefix
=
"Impersonate-Extra-"
)
)
// +genclient=true
// +genclient=true
...
...
pkg/apiserver/handler_impersonation.go
View file @
432e6ecd
...
@@ -17,7 +17,9 @@ limitations under the License.
...
@@ -17,7 +17,9 @@ limitations under the License.
package
apiserver
package
apiserver
import
(
import
(
"fmt"
"net/http"
"net/http"
"strings"
"github.com/golang/glog"
"github.com/golang/glog"
...
@@ -32,20 +34,17 @@ import (
...
@@ -32,20 +34,17 @@ import (
// WithImpersonation is a filter that will inspect and check requests that attempt to change the user.Info for their requests
// WithImpersonation is a filter that will inspect and check requests that attempt to change the user.Info for their requests
func
WithImpersonation
(
handler
http
.
Handler
,
requestContextMapper
api
.
RequestContextMapper
,
a
authorizer
.
Authorizer
)
http
.
Handler
{
func
WithImpersonation
(
handler
http
.
Handler
,
requestContextMapper
api
.
RequestContextMapper
,
a
authorizer
.
Authorizer
)
http
.
Handler
{
return
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
req
*
http
.
Request
)
{
return
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
req
*
http
.
Request
)
{
requestedUser
:=
req
.
Header
.
Get
(
authenticationapi
.
ImpersonateUserHeader
)
impersonationRequests
,
err
:=
buildImpersonationRequests
(
req
.
Header
)
if
len
(
requestedUser
)
==
0
{
if
err
!=
nil
{
if
len
(
req
.
Header
[
authenticationapi
.
ImpersonateGroupHeader
])
>
0
{
glog
.
V
(
4
)
.
Infof
(
"%v"
,
err
)
glog
.
V
(
4
)
.
Infof
(
"attempt to impersonate groups without impersonating a user: %v"
,
req
.
Header
[
authenticationapi
.
ImpersonateGroupHeader
])
forbidden
(
w
,
req
)
forbidden
(
w
,
req
)
return
return
}
}
if
len
(
impersonationRequests
)
==
0
{
handler
.
ServeHTTP
(
w
,
req
)
handler
.
ServeHTTP
(
w
,
req
)
return
return
}
}
impersonationRequests
:=
buildImpersonationRequests
(
requestedUser
,
req
.
Header
[
authenticationapi
.
ImpersonateGroupHeader
])
ctx
,
exists
:=
requestContextMapper
.
Get
(
req
)
ctx
,
exists
:=
requestContextMapper
.
Get
(
req
)
if
!
exists
{
if
!
exists
{
forbidden
(
w
,
req
)
forbidden
(
w
,
req
)
...
@@ -65,6 +64,7 @@ func WithImpersonation(handler http.Handler, requestContextMapper api.RequestCon
...
@@ -65,6 +64,7 @@ func WithImpersonation(handler http.Handler, requestContextMapper api.RequestCon
// and group information
// and group information
username
:=
""
username
:=
""
groups
:=
[]
string
{}
groups
:=
[]
string
{}
userExtra
:=
map
[
string
][]
string
{}
for
_
,
impersonationRequest
:=
range
impersonationRequests
{
for
_
,
impersonationRequest
:=
range
impersonationRequests
{
actingAsAttributes
:=
&
authorizer
.
AttributesRecord
{
actingAsAttributes
:=
&
authorizer
.
AttributesRecord
{
User
:
requestor
,
User
:
requestor
,
...
@@ -92,8 +92,15 @@ func WithImpersonation(handler http.Handler, requestContextMapper api.RequestCon
...
@@ -92,8 +92,15 @@ func WithImpersonation(handler http.Handler, requestContextMapper api.RequestCon
actingAsAttributes
.
Resource
=
"groups"
actingAsAttributes
.
Resource
=
"groups"
groups
=
append
(
groups
,
impersonationRequest
.
Name
)
groups
=
append
(
groups
,
impersonationRequest
.
Name
)
case
authenticationapi
.
Kind
(
"UserExtra"
)
:
extraKey
:=
impersonationRequest
.
FieldPath
extraValue
:=
impersonationRequest
.
Name
actingAsAttributes
.
Resource
=
"userextras"
actingAsAttributes
.
Subresource
=
extraKey
userExtra
[
extraKey
]
=
append
(
userExtra
[
extraKey
],
extraValue
)
default
:
default
:
glog
.
V
(
4
)
.
Infof
(
"unknown impersonation request type: %v"
,
impersonationRequest
)
glog
.
V
(
4
)
.
Infof
(
"unknown impersonation request type: %v
\n
"
,
impersonationRequest
)
forbidden
(
w
,
req
)
forbidden
(
w
,
req
)
return
return
}
}
...
@@ -109,7 +116,7 @@ func WithImpersonation(handler http.Handler, requestContextMapper api.RequestCon
...
@@ -109,7 +116,7 @@ func WithImpersonation(handler http.Handler, requestContextMapper api.RequestCon
newUser
:=
&
user
.
DefaultInfo
{
newUser
:=
&
user
.
DefaultInfo
{
Name
:
username
,
Name
:
username
,
Groups
:
groups
,
Groups
:
groups
,
Extra
:
map
[
string
][]
string
{}
,
Extra
:
userExtra
,
}
}
requestContextMapper
.
Update
(
req
,
api
.
WithUser
(
ctx
,
newUser
))
requestContextMapper
.
Update
(
req
,
api
.
WithUser
(
ctx
,
newUser
))
...
@@ -121,19 +128,55 @@ func WithImpersonation(handler http.Handler, requestContextMapper api.RequestCon
...
@@ -121,19 +128,55 @@ func WithImpersonation(handler http.Handler, requestContextMapper api.RequestCon
}
}
// 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.
// Each request must be authorized against the current user before switching contexts
// Also includes a map[string][]string representing user.Info.Extra
func
buildImpersonationRequests
(
requestedUser
string
,
requestedGroups
[]
string
)
[]
api
.
ObjectReference
{
// Each request must be authorized against the current user before switching contexts.
func
buildImpersonationRequests
(
headers
http
.
Header
)
([]
api
.
ObjectReference
,
error
)
{
impersonationRequests
:=
[]
api
.
ObjectReference
{}
impersonationRequests
:=
[]
api
.
ObjectReference
{}
if
namespace
,
name
,
err
:=
serviceaccount
.
SplitUsername
(
requestedUser
);
err
==
nil
{
requestedUser
:=
headers
.
Get
(
authenticationapi
.
ImpersonateUserHeader
)
impersonationRequests
=
append
(
impersonationRequests
,
api
.
ObjectReference
{
Kind
:
"ServiceAccount"
,
Namespace
:
namespace
,
Name
:
name
})
hasUser
:=
len
(
requestedUser
)
>
0
}
else
{
if
hasUser
{
impersonationRequests
=
append
(
impersonationRequests
,
api
.
ObjectReference
{
Kind
:
"User"
,
Name
:
requestedUser
})
if
namespace
,
name
,
err
:=
serviceaccount
.
SplitUsername
(
requestedUser
);
err
==
nil
{
impersonationRequests
=
append
(
impersonationRequests
,
api
.
ObjectReference
{
Kind
:
"ServiceAccount"
,
Namespace
:
namespace
,
Name
:
name
})
}
else
{
impersonationRequests
=
append
(
impersonationRequests
,
api
.
ObjectReference
{
Kind
:
"User"
,
Name
:
requestedUser
})
}
}
}
for
_
,
group
:=
range
requestedGroups
{
hasGroups
:=
false
for
_
,
group
:=
range
headers
[
authenticationapi
.
ImpersonateGroupHeader
]
{
hasGroups
=
true
impersonationRequests
=
append
(
impersonationRequests
,
api
.
ObjectReference
{
Kind
:
"Group"
,
Name
:
group
})
impersonationRequests
=
append
(
impersonationRequests
,
api
.
ObjectReference
{
Kind
:
"Group"
,
Name
:
group
})
}
}
return
impersonationRequests
hasUserExtra
:=
false
for
headerName
,
values
:=
range
headers
{
if
!
strings
.
HasPrefix
(
headerName
,
authenticationapi
.
ImpersonateUserExtraHeaderPrefix
)
{
continue
}
hasUserExtra
=
true
extraKey
:=
strings
.
ToLower
(
headerName
[
len
(
authenticationapi
.
ImpersonateUserExtraHeaderPrefix
)
:
])
// make a separate request for each extra value they're trying to set
for
_
,
value
:=
range
values
{
impersonationRequests
=
append
(
impersonationRequests
,
api
.
ObjectReference
{
Kind
:
"UserExtra"
,
// we only parse out a group above, but the parsing will fail if there isn't SOME version
// using the internal version will help us fail if anyone starts using it
APIVersion
:
authenticationapi
.
SchemeGroupVersion
.
String
(),
Name
:
value
,
// ObjectReference doesn't have a subresource field. FieldPath is close and available, so we'll use that
// TODO fight the good fight for ObjectReference to refer to resources and subresources
FieldPath
:
extraKey
,
})
}
}
if
(
hasGroups
||
hasUserExtra
)
&&
!
hasUser
{
return
nil
,
fmt
.
Errorf
(
"requested %v without impersonating a user"
,
impersonationRequests
)
}
return
impersonationRequests
,
nil
}
}
pkg/apiserver/handler_impersonation_test.go
View file @
432e6ecd
...
@@ -62,17 +62,31 @@ func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorized bool
...
@@ -62,17 +62,31 @@ func (impersonateAuthorizer) Authorize(a authorizer.Attributes) (authorized bool
return
true
,
""
,
nil
return
true
,
""
,
nil
}
}
if
len
(
user
.
GetGroups
())
>
1
&&
user
.
GetGroups
()[
1
]
==
"extra-setter-scopes"
&&
a
.
GetVerb
()
==
"impersonate"
&&
a
.
GetResource
()
==
"userextras"
&&
a
.
GetSubresource
()
==
"scopes"
{
return
true
,
""
,
nil
}
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"
{
return
true
,
""
,
nil
}
if
len
(
user
.
GetGroups
())
>
1
&&
user
.
GetGroups
()[
1
]
==
"extra-setter-project"
&&
a
.
GetVerb
()
==
"impersonate"
&&
a
.
GetResource
()
==
"userextras"
&&
a
.
GetSubresource
()
==
"project"
{
return
true
,
""
,
nil
}
return
false
,
"deny by default"
,
nil
return
false
,
"deny by default"
,
nil
}
}
func
TestImpersonationFilter
(
t
*
testing
.
T
)
{
func
TestImpersonationFilter
(
t
*
testing
.
T
)
{
testCases
:=
[]
struct
{
testCases
:=
[]
struct
{
name
string
name
string
user
user
.
Info
user
user
.
Info
impersonationUser
string
impersonationUser
string
impersonationGroups
[]
string
impersonationGroups
[]
string
expectedUser
user
.
Info
impersonationUserExtras
map
[
string
][]
string
expectedCode
int
expectedUser
user
.
Info
expectedCode
int
}{
}{
{
{
name
:
"not-impersonating"
,
name
:
"not-impersonating"
,
...
@@ -107,6 +121,17 @@ func TestImpersonationFilter(t *testing.T) {
...
@@ -107,6 +121,17 @@ func TestImpersonationFilter(t *testing.T) {
expectedCode
:
http
.
StatusForbidden
,
expectedCode
:
http
.
StatusForbidden
,
},
},
{
{
name
:
"impersonating-extra-without-user"
,
user
:
&
user
.
DefaultInfo
{
Name
:
"tester"
,
},
impersonationUserExtras
:
map
[
string
][]
string
{
"scopes"
:
{
"scope-a"
}},
expectedUser
:
&
user
.
DefaultInfo
{
Name
:
"tester"
,
},
expectedCode
:
http
.
StatusForbidden
,
},
{
name
:
"disallowed-group"
,
name
:
"disallowed-group"
,
user
:
&
user
.
DefaultInfo
{
user
:
&
user
.
DefaultInfo
{
Name
:
"dev"
,
Name
:
"dev"
,
...
@@ -136,6 +161,66 @@ func TestImpersonationFilter(t *testing.T) {
...
@@ -136,6 +161,66 @@ func TestImpersonationFilter(t *testing.T) {
expectedCode
:
http
.
StatusOK
,
expectedCode
:
http
.
StatusOK
,
},
},
{
{
name
:
"disallowed-userextra-1"
,
user
:
&
user
.
DefaultInfo
{
Name
:
"dev"
,
Groups
:
[]
string
{
"wheel"
},
},
impersonationUser
:
"system:admin"
,
impersonationGroups
:
[]
string
{
"some-group"
},
impersonationUserExtras
:
map
[
string
][]
string
{
"scopes"
:
{
"scope-a"
}},
expectedUser
:
&
user
.
DefaultInfo
{
Name
:
"dev"
,
Groups
:
[]
string
{
"wheel"
},
},
expectedCode
:
http
.
StatusForbidden
,
},
{
name
:
"disallowed-userextra-2"
,
user
:
&
user
.
DefaultInfo
{
Name
:
"dev"
,
Groups
:
[]
string
{
"wheel"
,
"extra-setter-project"
},
},
impersonationUser
:
"system:admin"
,
impersonationGroups
:
[]
string
{
"some-group"
},
impersonationUserExtras
:
map
[
string
][]
string
{
"scopes"
:
{
"scope-a"
}},
expectedUser
:
&
user
.
DefaultInfo
{
Name
:
"dev"
,
Groups
:
[]
string
{
"wheel"
,
"extra-setter-project"
},
},
expectedCode
:
http
.
StatusForbidden
,
},
{
name
:
"disallowed-userextra-3"
,
user
:
&
user
.
DefaultInfo
{
Name
:
"dev"
,
Groups
:
[]
string
{
"wheel"
,
"extra-setter-particular-scopes"
},
},
impersonationUser
:
"system:admin"
,
impersonationGroups
:
[]
string
{
"some-group"
},
impersonationUserExtras
:
map
[
string
][]
string
{
"scopes"
:
{
"scope-a"
,
"scope-b"
}},
expectedUser
:
&
user
.
DefaultInfo
{
Name
:
"dev"
,
Groups
:
[]
string
{
"wheel"
,
"extra-setter-particular-scopes"
},
},
expectedCode
:
http
.
StatusForbidden
,
},
{
name
:
"allowed-userextras"
,
user
:
&
user
.
DefaultInfo
{
Name
:
"dev"
,
Groups
:
[]
string
{
"wheel"
,
"extra-setter-scopes"
},
},
impersonationUser
:
"system:admin"
,
impersonationUserExtras
:
map
[
string
][]
string
{
"scopes"
:
{
"scope-a"
,
"scope-b"
}},
expectedUser
:
&
user
.
DefaultInfo
{
Name
:
"system:admin"
,
Groups
:
[]
string
{},
Extra
:
map
[
string
][]
string
{
"scopes"
:
{
"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"
,
...
@@ -238,6 +323,12 @@ func TestImpersonationFilter(t *testing.T) {
...
@@ -238,6 +323,12 @@ func TestImpersonationFilter(t *testing.T) {
for
_
,
group
:=
range
tc
.
impersonationGroups
{
for
_
,
group
:=
range
tc
.
impersonationGroups
{
req
.
Header
.
Add
(
authenticationapi
.
ImpersonateGroupHeader
,
group
)
req
.
Header
.
Add
(
authenticationapi
.
ImpersonateGroupHeader
,
group
)
}
}
for
extraKey
,
values
:=
range
tc
.
impersonationUserExtras
{
for
_
,
value
:=
range
values
{
req
.
Header
.
Add
(
authenticationapi
.
ImpersonateUserExtraHeaderPrefix
+
extraKey
,
value
)
}
}
resp
,
err
:=
http
.
DefaultClient
.
Do
(
req
)
resp
,
err
:=
http
.
DefaultClient
.
Do
(
req
)
if
err
!=
nil
{
if
err
!=
nil
{
t
.
Errorf
(
"%s: unexpected error: %v"
,
tc
.
name
,
err
)
t
.
Errorf
(
"%s: unexpected error: %v"
,
tc
.
name
,
err
)
...
...
pkg/auth/user/user.go
View file @
432e6ecd
...
@@ -36,6 +36,8 @@ type Info interface {
...
@@ -36,6 +36,8 @@ type Info interface {
// This is a map[string][]string because it needs to be serializeable into
// This is a map[string][]string because it needs to be serializeable into
// a SubjectAccessReviewSpec.authorization.k8s.io for proper authorization
// a SubjectAccessReviewSpec.authorization.k8s.io for proper authorization
// delegation flows
// delegation flows
// In order to faithfully round-trip through an impersonation flow, these keys
// MUST be lowercase.
GetExtra
()
map
[
string
][]
string
GetExtra
()
map
[
string
][]
string
}
}
...
...
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