-
Notifications
You must be signed in to change notification settings - Fork 4
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
✨ Add rudimentary user gating #3
base: main
Are you sure you want to change the base?
Conversation
The user gate is a rudimentary authorization filter that controls which authenticated users are authorized to make requests against CPS. It is dynamically configured by a secret containing rules in the kcp-system namespace of the root workspace. This filter is meant to be used along with another system that dynamically updates the secret during a user sign-up workflow ahead of general availability. It should not be used after a more general mechanism provided by CIAM is in place.
[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 |
/assign @hardys |
Summary of some previous discussions from the upstream PR There were concerns and suggestions from @sttts and @s-urbaniak about the proxy reading a secret from the root workspace for this. Personally I agree and think a rules file managed as part of the proxy deployment would be much simpler, but @csams explained that there is a requirement for systems which don't have access to the proxy deployment to update the rules. |
resyncPeriod = 10 * time.Minute | ||
|
||
UserGateNamespace = "kcp-system" | ||
UserGateResource = "usergate.kcp.dev" |
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 we add flags for the namespace and secret name as discussed upstream?
Not a blocker but it seems like it might be a mitigation for the reading-root-secrets concern if we can ensure RBAC limits access allowed from the proxy?
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.
Yep, will do.
/cc @s-urbaniak |
We'll need to merge #4 then rebase this so we can get the prow jobs to run |
@csams please can you rebase now #4 merged, note you'll need to add the new |
Logs *logs.Options | ||
} | ||
|
||
func NewOptions() *Options { |
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.
global comment: please godoc everything that's exported
} | ||
|
||
func (o *UserGate) AddFlags(fs *pflag.FlagSet) { | ||
fs.BoolVar(&o.Enabled, "enable-user-gating", false, "Enabled user gating.") |
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.
fs.BoolVar(&o.Enabled, "enable-user-gating", false, "Enabled user gating.") | |
fs.BoolVar(&o.Enabled, "enable-user-gating", o.Enabled, "Enable user gating.") |
|
||
func (o *UserGate) AddFlags(fs *pflag.FlagSet) { | ||
fs.BoolVar(&o.Enabled, "enable-user-gating", false, "Enabled user gating.") | ||
fs.StringVar(&o.DefaultRulesFile, "user-gating-rules", "", "Provide a YAML file containing user gating rules.") |
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.
fs.StringVar(&o.DefaultRulesFile, "user-gating-rules", "", "Provide a YAML file containing user gating rules.") | |
fs.StringVar(&o.DefaultRulesFile, "user-gating-rules", o.DefaultRulesFile, "Path to a YAML file containing user gating rules.") |
"os" | ||
"time" | ||
|
||
coreinformers "github.com/kcp-dev/client-go/informers" |
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.
coreinformers "github.com/kcp-dev/client-go/informers" | |
kcpinformers "github.com/kcp-dev/client-go/informers" |
"time" | ||
|
||
coreinformers "github.com/kcp-dev/client-go/informers" | ||
coreclient "github.com/kcp-dev/client-go/kubernetes" |
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.
coreclient "github.com/kcp-dev/client-go/kubernetes" | |
kcpkubernetesclientset "github.com/kcp-dev/client-go/kubernetes" |
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.
I'm seeing both kcpkubernetesclientset
and kcpkubernetesclient
in the kcp code - we should standardize. cc @stevekuznetsov
"sigs.k8s.io/yaml" | ||
) | ||
|
||
type RuleSetGetter func() (*RuleSet, bool) |
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.
Define this where it's used, i.e. not in this package
|
||
if rules, found := a.GetRuleSet(); found { | ||
if rules.UserNameIsAllowed(info.GetName()) { | ||
return authorizer.DecisionAllow |
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.
Call kaudit.AddAuditAnnotations
before returning any/all decisions to record why
}) | ||
} | ||
|
||
// Authorize whether the user is allowed to make the request |
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.
// Authorize whether the user is allowed to make the request | |
// Authorize determines whether the user is allowed to make the request. |
} | ||
|
||
decision := gate.Authorize(ctx, u) | ||
if decision != authorizer.DecisionDeny { |
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.
Do we want to allow NoOpinion to be allowed in? cc @s-urbaniak
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.
This is an interesting point, it seems like we should perhaps only have allowed_user_names
then deny all other users, or else this isn't really a gate to prevent access by undefined random users?
|
||
func (c *Controller) process(ctx context.Context, key string) error { | ||
logger := klog.FromContext(ctx) | ||
logger.WithValues("key", key).Info("updating usergate secret") |
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.
We're not really updating the secret?
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.
changed to "updating usergate ruleset"
GetRuleSet ruleset.RuleSetGetter | ||
} | ||
|
||
func WithUserGating(delegate http.Handler, failed http.Handler, getRuleSet ruleset.RuleSetGetter) 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.
I think this file naming would be more consistent with the upstream proxy if it was e.g pkg/proxy/filters/usergating.go
?
// Authorize whether the user is allowed to make the request | ||
func (a *UserGate) Authorize(ctx context.Context, info user.Info) authorizer.Decision { | ||
logger := klog.FromContext(ctx).WithValues("component", "user-gating") | ||
logger.V(1).Info("checking auth", "user", info.GetName()) |
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.
This logging is likely to be too noisy I think, should probably either be removed or put at a higher loglevel?
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.
I'll remove it in favor of ncdc's kaudit.AddAuditAnnotations
feedback.
"github.com/kcp-dev/client-go/informers" | ||
tenancyv1alpha1 "github.com/kcp-dev/kcp/pkg/apis/tenancy/v1alpha1" | ||
|
||
v1 "k8s.io/api/core/v1" |
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.
I think aliasing this to corev1
would be more consistent with upstream?
ctx := r.Context() | ||
u, ok := request.UserFrom(ctx) | ||
if !ok { | ||
delegate.ServeHTTP(w, r) |
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.
Similar to the comment below, do we want this to be allow-by-default or should this return failed if we can't identify the 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.
Do all authentication methods give a user.Info? Even if so, can we still gate them correctly? Thinking about service account tokens generated in 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.
From my testing yes, I've tested with token-file, SA token, client-cert and OIDC and they all result in a user.Info containing a username when authentication is successful
Even Anonymous auth sets a username AFAICS (although we probably don't want to enable that at the proxy)
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.
We'll need to test to confirm, but from my SA testing (where I just used the default SA token from the secret in the root workspace), I think we'll need to have a rule like ^system:serviceaccount:.*:*$
or similar
@csams: PR needs rebase. 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. |
@csams: The following tests failed, say
Full PR test history. Your PR dashboard. 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
The user gate is a rudimentary authorization filter that controls which authenticated users are authorized to make requests against CPS. It is dynamically configured by a secret containing rules in the
kcp-system
namespace of theroot
workspace.This filter is meant to be used along with another system that dynamically updates the secret during a user sign-up workflow ahead of general availability. It should not be used in its current form after a more general mechanism provided by CIAM is in place.
Follow on PRs will change the filter so that it injects groups and/or some form of entitlement information into the user info (after parsed from the JWT), which should dovetail into how we want CIAM to control whether users are allowed to access CPS.