Skip to content

Commit

Permalink
feat(server): Enable RBAC for SSO. Closes argoproj#3525 (argoproj#4198)
Browse files Browse the repository at this point in the history
Co-authored-by: Vlad Losev <[email protected]>
  • Loading branch information
alexec and vladlosev committed Oct 27, 2020
1 parent e409164 commit 334d134
Show file tree
Hide file tree
Showing 30 changed files with 515 additions and 114 deletions.
6 changes: 6 additions & 0 deletions api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2570,6 +2570,12 @@
"io.argoproj.workflow.v1alpha1.GetUserInfoResponse": {
"type": "object",
"properties": {
"groups": {
"type": "array",
"items": {
"type": "string"
}
},
"issuer": {
"type": "string"
},
Expand Down
66 changes: 66 additions & 0 deletions docs/argo-server-sso.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,69 @@ kubectl delete secret sso
The old key will be in the memory the any running Argo Server, and they will therefore accept and user with token encrypted using the old key. Every Argo Server MUST be restarted.

All users will need to log in again. Sorry.


## SSO RBAC

> v2.12 and after
You can optionally add RBAC to SSO. This allows you to give different users different access levels. Except for `client` auth mode, all users of the Argo Server must ultimately use a service account. So we allow you to define rules that map a user (maybe using their OIDC groups) to a service account by annotating the service account.

RBAC config is installation-level, so any changes will need to be made by the team that installed Argo. Many complex rules will be burdensome on that team.

Firstly, enable the `rbac:` setting in [workflow-controller-configmap.yaml](workflow-controller-configmap.yaml). You almost certainly want to be able configure RBAC using groups, so add `scopes:` to the SSO settings:

```yaml
sso:
# ...
scopes:
- groups
rbac:
enabled: true
```

!!! Note
Not all OIDC provider support the groups scope. Please speak to your provider about their options.

To configure a service account to be used, annotate it:

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
annotations:
# The rule is an expression used to determine if this service account
# should be used.
# * `groups` - an array of the OIDC groups
# * `iss` - the issuer ("argo-server")
# * `sub` - the subject (typically the username)
# Must evaluate to a boolean.
# If you want an account to be the default to use, this rule can be "true".
# Details of the expression language are available in
# https://github.com/antonmedv/expr/blob/master/docs/Language-Definition.md.
workflows.argoproj.io/rbac-rule: "'admin' in groups"
# The precedence is used to determine which service account to use whe
# Precedence is an integer. It may be negative. If omitted, it defaults to "0".
# Numerically higher values have higher precedence (not lower, which maybe
# counter-intuitive to you).
# If two rules match and have the same precedence, then which one used will
# be arbitrary.
workflows.argoproj.io/rbac-rule-precedence: "1"
```


If no rule matches, we deny the user access.

!!! Tip
You'll probably want to configure a default account to use if no other rule matches, e.g. a read-only account, you can do this as follows:

```yaml
metadata:
name: read-only
annotations:
workflows.argoproj.io/rbac-rule: "true"
workflows.argoproj.io/rbac-rule-precedence: "0"
```

The precedence must be the lowest of all your service accounts.
7 changes: 6 additions & 1 deletion docs/workflow-controller-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,12 @@ data:
# be in the form <argo-server-root-url>/oauth2/callback. It must be
# browser-accessible.
redirectUrl: https://argo-server/oauth2/callback
# Additional scopes to request. Typically needed for SSO RBAC. >= v2.12
scopes:
- groups
# RBAC Config. >= v2.12
rbac:
enabled: false
# workflowRequirements restricts the Workflows that the controller will process.
# Current options:
# referenceOnly: Only Workflows using "workflowTemplateRef" will be processed. This allows the administrator of the controller
Expand Down
3 changes: 2 additions & 1 deletion manifests/quick-start/sso/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ resources:
- dex

patchesStrategicMerge:
- overlays/workflow-controller-configmap.yaml
- overlays/workflow-controller-configmap.yaml
- overlays/argo-server-sa.yaml
7 changes: 7 additions & 0 deletions manifests/quick-start/sso/overlays/argo-server-sa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: argo-server
annotations:
workflows.argoproj.io/rbac-rule: "'authors' in groups"
workflows.argoproj.io/rbac-rule-precedence: "1"
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
apiVersion: v1
data:
sso : |
sso: |
issuer: http:https://dex:5556/dex
clientId:
name: argo-server-sso
Expand All @@ -9,6 +9,10 @@ data:
name: argo-server-sso
key: clientSecret
redirectUrl: http:https://localhost:2746/oauth2/callback
scopes:
- groups
rbac:
enabled: true
kind: ConfigMap
metadata:
name: workflow-controller-configmap
2 changes: 1 addition & 1 deletion pkg/apiclient/argo-kube-client.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func newArgoKubeClient(clientConfig clientcmd.ClientConfig, instanceIDService in
if err != nil {
return nil, nil, err
}
gatekeeper, err := auth.NewGatekeeper(auth.Modes{auth.Server: true}, wfClient, kubeClient, restConfig, nil)
gatekeeper, err := auth.NewGatekeeper(auth.Modes{auth.Server: true}, wfClient, kubeClient, restConfig, nil, auth.DefaultClientForAuthorization, "unused")
if err != nil {
return nil, nil, err
}
Expand Down
112 changes: 84 additions & 28 deletions pkg/apiclient/info/info.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/apiclient/info/info.proto
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ message GetUserInfoRequest {
message GetUserInfoResponse {
string issuer = 1;
string subject = 2;
repeated string groups = 3;
}

service InfoService {
Expand Down
12 changes: 6 additions & 6 deletions server/apiserver/argoserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ type argoServer struct {
managedNamespace string
kubeClientset *kubernetes.Clientset
wfClientSet *versioned.Clientset
authenticator auth.Gatekeeper
gatekeeper auth.Gatekeeper
oAuth2Service sso.Interface
configController config.Controller
stopCh chan struct{}
Expand Down Expand Up @@ -104,7 +104,7 @@ func NewArgoServer(opts ArgoServerOpts) (*argoServer, error) {
} else {
log.Info("SSO disabled")
}
gatekeeper, err := auth.NewGatekeeper(opts.AuthModes, opts.WfClientSet, opts.KubeClientset, opts.RestConfig, ssoIf)
gatekeeper, err := auth.NewGatekeeper(opts.AuthModes, opts.WfClientSet, opts.KubeClientset, opts.RestConfig, ssoIf, auth.DefaultClientForAuthorization, opts.Namespace)
if err != nil {
return nil, err
}
Expand All @@ -116,7 +116,7 @@ func NewArgoServer(opts ArgoServerOpts) (*argoServer, error) {
managedNamespace: opts.ManagedNamespace,
wfClientSet: opts.WfClientSet,
kubeClientset: opts.KubeClientset,
authenticator: gatekeeper,
gatekeeper: gatekeeper,
oAuth2Service: ssoIf,
configController: configController,
stopCh: make(chan struct{}),
Expand Down Expand Up @@ -159,7 +159,7 @@ func (as *argoServer) Run(ctx context.Context, port int, browserOpenFunc func(st
wfArchive = sqldb.NewWorkflowArchive(session, persistence.GetClusterName(), as.managedNamespace, instanceIDService)
}
eventRecorderManager := events.NewEventRecorderManager(as.kubeClientset)
artifactServer := artifacts.NewArtifactServer(as.authenticator, hydrator.New(offloadRepo), wfArchive, instanceIDService)
artifactServer := artifacts.NewArtifactServer(as.gatekeeper, hydrator.New(offloadRepo), wfArchive, instanceIDService)
eventServer := event.NewController(instanceIDService, eventRecorderManager, as.eventQueueSize, as.eventWorkerCount)
grpcServer := as.newGRPCServer(instanceIDService, offloadRepo, wfArchive, eventServer, config.Links)
httpServer := as.newHTTPServer(ctx, port, artifactServer)
Expand Down Expand Up @@ -218,13 +218,13 @@ func (as *argoServer) newGRPCServer(instanceIDService instanceid.Service, offloa
grpc_logrus.UnaryServerInterceptor(serverLog),
grpcutil.PanicLoggerUnaryServerInterceptor(serverLog),
grpcutil.ErrorTranslationUnaryServerInterceptor,
as.authenticator.UnaryServerInterceptor(),
as.gatekeeper.UnaryServerInterceptor(),
)),
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_logrus.StreamServerInterceptor(serverLog),
grpcutil.PanicLoggerStreamServerInterceptor(serverLog),
grpcutil.ErrorTranslationStreamServerInterceptor,
as.authenticator.StreamServerInterceptor(),
as.gatekeeper.StreamServerInterceptor(),
)),
}

Expand Down
Loading

0 comments on commit 334d134

Please sign in to comment.