-
Notifications
You must be signed in to change notification settings - Fork 391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ PoC: Implement user warrants #3156
base: main
Are you sure you want to change the base?
Conversation
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
4b188ca
to
2ca96c3
Compare
logger.V(4).Info("authorization step", | ||
"reason", reason, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this not be auditReasonMsg
? We add this into annotations but remove from logs? Intentional?
|
||
// withWarrantsInChainCallingDelegateWithOriginalUser could be named shorter, but then it would | ||
// look innocent which it isn't. It produces one level of the chain with the wrapper called | ||
// for the base and all warrents. The delegate though is called only once with the original user. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/warrents/warrants
Groups: []string{bootstrap.SystemKcpAdminGroup}, | ||
// Add a warrant of a fake local service account giving full access | ||
warrant := validation.Warrant{ | ||
User: "system:serviceaccount:default:rest", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just of interest, what is somebody creates an account like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
totally possible. Same as today: you can create any kind of client cert faking a service account or oidc user or whatever you like. In other words, control over an authn provider is always critical.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But don't think you can impersonate this in any way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Impersonation is a good point. Please double check that this is properly e2e tested. Escalating permissions due to impersonation in a workspace must not be possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deal. will try to spin some tests scenarios for this manually too.
In general just a questions. looks good, |
@@ -44,7 +44,7 @@ const ( | |||
|
|||
// NewMaximalPermissionPolicyAuthorizer returns an authorizer that first checks if the request is for a | |||
// bound resource or not. If the resource is bound it checks the maximal permission policy of the underlying API export. | |||
func NewMaximalPermissionPolicyAuthorizer(kubeInformers, globalKubeInformers kcpkubernetesinformers.SharedInformerFactory, kcpInformers, globalKcpInformers kcpinformers.SharedInformerFactory, delegate authorizer.Authorizer) authorizer.Authorizer { | |||
func NewMaximalPermissionPolicyAuthorizer(kubeInformers, globalKubeInformers kcpkubernetesinformers.SharedInformerFactory, kcpInformers, globalKcpInformers kcpinformers.SharedInformerFactory) func(delegate authorizer.Authorizer) authorizer.Authorizer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just mental note:
func NewMaximalPermissionPolicyAuthorizer(
kubeInformers, globalKubeInformers kcpkubernetesinformers.SharedInformerFactory,
kcpInformers, globalKcpInformers kcpinformers.SharedInformerFactory,
) func(delegate authorizer.Authorizer) authorizer.Authorizer {
easier to read. Took me a second to absorb the signature.
impersonatedClient, err := kcpkubernetesclientset.NewForConfig(impersonationConfig) | ||
require.NoError(t, err) | ||
|
||
t.Log("As user-2 with system:masters, we should be able to read secrets") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As user-1 with impersonation of user-2 with system:masters, we should be able to read secrets
?
|
||
t.Logf("Check SelfSubjectReview as user-2 with system:masters") | ||
req := &authenticationv1.SelfSubjectReview{} | ||
resp, err := impersonatedClient.Cluster(wsPath).AuthenticationV1().SelfSubjectReviews().Create(ctx, req, metav1.CreateOptions{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would impersonation not return system:masters group? Why its not expecting it?
@@ -44,25 +44,27 @@ const ( | |||
WorkspaceAccessNotPermittedReason = "workspace access not permitted" | |||
) | |||
|
|||
func NewWorkspaceContentAuthorizer(localInformers, globalInformers kcpkubernetesinformers.SharedInformerFactory, localLogicalClusterLister, globalLogicalClusterLister corev1alpha1listers.LogicalClusterClusterLister, delegate authorizer.Authorizer) authorizer.Authorizer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Im starting to wonder if we should duplicate this code (2 content authorizer implementations) and make all this warrant code feature-flag gated? But might be a lot of if-else statements, not sure if this worth the effort.
My inner me - says - lets just go.
But OSS me - says - better safe than sorry.
|
||
// protect status updates to apiexport and apibinding | ||
systemCRDAuth := authz.NewSystemCRDAuthorizer(maxPermissionPolicyAuth) | ||
systemCRDAuth = authz.NewDecorator("03-systemcrd", systemCRDAuth).AddAuditLogging().AddAnonymization().AddReasonAnnotation() | ||
chain = withWarrantsInChainCallingDelegateWithOriginalUser(authz.NewSystemCRDAuthorizer, chain) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mjudeikis to feature gate it, we could have a noop withWarrantsInChainCallingDelegateWithOriginalUser
.
59654d8
to
8ad6d50
Compare
8ad6d50
to
cca5a21
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe service accounts could go into separate pr too?
Service accounts from foreign workspaces are authenticated as user `system:kcp:serviceaccount:<logicalcluster>:<ns>:<serviceaccount>` | ||
and under that name can be granted access to workspaces and other permissions. The original service account name is | ||
appended as a warrant (see next section) in order to match the same (cluster) role bindings: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mental note:
this could be nice test too in e2e stack.
// WithServiceAccountRewrite replaces the user info of the context for service | ||
// accounts by representing them as globally valid kcp service account representation | ||
// with a warrant in the old format (to match kube-like (cluster) role bindings). | ||
func WithServiceAccountRewrite(handler http.Handler) http.Handler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can service accounts be split too to separate PR? Just enable service account for cross-workspaces?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will try. Have started the rebase now.
case !isUser && !isServiceAccountFromCluster: | ||
// service accounts from other workspaces cannot access | ||
case isServiceAccount && isForeign: | ||
// Service accounts from other workspaces might conflict with local service accounts by name. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still true if we rewrite service account names to be globally unique?
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
…for the target workspace Signed-off-by: Dr. Stefan Schimanski <[email protected]>
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
cca5a21
to
d853530
Compare
@sttts: The following tests failed, say
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
Summary
Needs kcp-dev/kubernetes#145.
From Slack:
🧵 long ago @sur presented a problem (if I remember right) in our current auth model of serviceaccounts only being valid locally, and with that the inability to create workspaces through an APIExport virtual workspace. The steps are these:
system:masters
user with a fakesystem:kcp:admin
serviceaccount (S1) for L1 namedsystem:serviceaccount:default:rest
.Now assume a modified system that
The steps from above would turn into:
system:masters
user as U with a warrant for a fakesystem:kcp:admin
serviceaccount (S1) for L1 namedsystem:serviceaccount:default:rest
.By giving use permission to everybody, tThis goes through.denies access because S1 is a foreign service accountgrants access because U with warrant S1 is admin in L2.Now assume in addition that U is actually a controller serviceaccount S0 in workspace root. Step (7) would 💥 because S0 is foreign for L2. We further modify the system to authenticate serviceaccount as
system:kcp:serviceaccount:<logicalcluster>:<ns>:<name>
with a warrant forsystem:serviceaccount:<ns>:<name>
restricted to their defining logicalcluster.Now, U becomes
system:kcp:serviceaccount:root:default:controller
with warrantsystem:serviceaccount:default:controller
restricted to root. With that (4) becomessystem:kcp:serviceaccount:root:default:controller
with warrantsystem:serviceaccount:default:controller
restricted to root with warrant S1 as owner on the workspace object, to be used by initialization later.I.e. the user has two warrants. With that (7) becomes:
system:kcp:serviceaccount:root:default:controller
with the TWO warrants including S1 is admin in L2.Related issue(s)
Fixes #
Release Notes