Skip to content
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

feat(openshift): add configuration for proxy SubjectAccessReview #132

Merged
merged 20 commits into from
May 29, 2024

Conversation

andrewazores
Copy link
Member

@andrewazores andrewazores commented Apr 17, 2024

Based on #124
See https://github.com/openshift/oauth-proxy/?tab=readme-ov-file#limiting-access-to-users

Adds configuration parameters to customize the SubjectAccessReview performed by the OpenShift OAuth Proxy. All requests going through the proxy via OpenShift SSO (not htpasswd Basic ones, if they are enabled) must be able to pass this SAR, which the proxy does before forwarding the request upstream. The default setting is to require clients have the ability to create pods/exec within the Helm's .Release.Namespace.

@andrewazores andrewazores added feat New feature or request safe-to-test labels Apr 17, 2024
@andrewazores
Copy link
Member Author

@ebaron one shortcoming so far is that this only allows for a single namespace/resource/verb tuple. The proxy supports supplying a list to this --openshift-sar flag, in which case all of the criteria must be fulfilled. I'm trying to figure out the Helm/Sprig/Go template system enough to make the values.yaml have this as one default object in a list of access definitions, but while also being able to do the "" -> .Release.Name substitution, but it's difficult.

Another approach that seems easier to implement but messier for the configuration interface would be something like a .Values.openshiftOauthProxy.access.override string parameter, which defaults to empty, but if it is set then it alone is passed to --openshift-sar. Then the user can craft their own SubjectAccessReview JSON string and pass that as an override.

@andrewazores andrewazores marked this pull request as ready for review April 17, 2024 15:25
@andrewazores andrewazores requested a review from ebaron April 17, 2024 15:25
@andrewazores
Copy link
Member Author

andrewazores commented Apr 17, 2024

Sorted it out - I made the new access config field into a list containing the default .Release.Namespace/pods/exec/create tuple. ".Release.Namespace" is used as a literal string in this case. Later, when the template engine is processing the OpenShift OAuth Proxy container, the access list field is rendered into a JSON string, and then that whole string is treated as a template - so the embedded {{ .Release.Namespace }} gets replaced. You can use --set to override this and use a value containing no template variables for expansion, too, in case you need to deploy into some other specific namespace(s): --set openshiftOauthProxy.access[0].namespace=foo --set openshiftOauthProxy.access[0].resource=pods --set openshiftOauthProxy.access[0].verb=get produces --openshift-sar=[{"namespace":"foo","resource":"pods","verb":"get"}]

@andrewazores
Copy link
Member Author

Last commit enables ex. https -v --follow --auth-type=bearer --auth=$(oc whoami -t) https://cryostat-cryostat3.apps-crc.testing. Without it, the only working auth flow is to interactively log in with a browser and allow the auth proxy to supply a cookie. Now that Bearer tokens are supported (again), programmatic API access through the proxy is reasonable - this also enables clients like the Cryostat Agent to continue working as they did before.

Copy link
Member

@tthvo tthvo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some questions out of my curiosity :D

Comment on lines 207 to 211
tokenReview:
## @param openshiftOauthProxy.tokenReview.group The OpenShift resource group that the TokenReview will be performed for
group: ""
## @param openshiftOauthProxy.tokenReview.resource The OpenShift resource that the TokenReview will be performed for
resource: "pods/exec"
## @param openshiftOauthProxy.tokenReview.verb The OpenShift resource verb that the TokenReview will be performed for
verb: "create"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@tthvo tthvo Apr 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value of the flag is a JSON map of path prefixes to v1beta1.ResourceAttributes, and the longest path prefix is checked. If no path matches the request, authentication and authorization are skipped.

https://docs.openshift.com/container-platform/4.15/rest_api/authorization_apis/subjectaccessreview-authorization-k8s-io-v1.html#spec-resourceattributes

Should other options configurable? I suppose namespace should be at least configurable, otherwise "" (empty) means all namespaces, which might not be possible for regular users.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess conceptually the token reviews should also be a list like the subject access reviews, and this list should actually be the list of namespaces that Cryostat is watching plus its installation namespace. This seems difficult to implement in the Helm chart at least, and is rather different from how we did access control in previous Cryostat versions, so for now I think it's enough to use the installation namespace for RBAC checks.

Copy link
Member

@tthvo tthvo Apr 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh makes sense! Also, I am a bit unsure of --openshift-delegate-urls supports key -> JSON array or must it be a single JSON object per path? Its rather odd if its just a single object.

I guess conceptually the token reviews should also be a list like the subject access reviews, and this list should actually be the list of namespaces that Cryostat is watching plus its installation namespace.

If JSON array is supported in token review, could we default its mapped values for each path prefix to SubjectAccessReview's rules and leave the token review config empty by default?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a bit hard to follow but when I was reviewing its sources earlier it did look like it only expected a single object here, not an array. But I haven't actually tried it to see if it accepts it and works, so it's probably worth giving a shot - at least with a singleton array to start with.

If it works then I would actually think of using the token review to define the list of required permissions, and then the accessReview to simply assign that same list to each of the path prefixes. It seems like that would be easier to accomplish with the Helm chart templates and filters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If JSON array is supported in token review, could we default its mapped values for each path prefix to SubjectAccessReview's rules and leave the token review config empty by default?

If it works then I would actually think of using the token review to define the list of required permissions, and then the accessReview to simply assign that same list to each of the path prefixes. It seems like that would be easier to accomplish with the Helm chart templates and filters.

If JSON array is supported in token review, could we default its mapped values for each path prefix to SubjectAccessReview's rules and leave the token review config empty by default?

Oh sorry haha I meant to say the same you explained there but with tokenReview still available but left as null in values.yaml. But it does sound simpler to just copy from accessReview.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this:

diff --git a/charts/cryostat/templates/openshiftOauthProxy.tpl b/charts/cryostat/templates/openshiftOauthProxy.tpl
index 80a982d..df926b7 100644
--- a/charts/cryostat/templates/openshiftOauthProxy.tpl
+++ b/charts/cryostat/templates/openshiftOauthProxy.tpl
@@ -17,7 +17,7 @@
     - --tls-key=/etc/tls/private/tls.key
     - --proxy-prefix=/oauth2
     - --openshift-sar={{ tpl ( .Values.openshiftOauthProxy.accessReview | toJson ) . }}
-    - --openshift-delegate-urls={"/api":{{ tpl ( .Values.openshiftOauthProxy.tokenReview | toJson ) . }}, "/storage":{{ tpl ( .Values.openshiftOauthProxy.tokenReview | toJson ) . }}, "/grafana":{{ tpl ( .Values.openshiftOauthProxy.tokenReview | toJson ) . }} }
+    - --openshift-delegate-urls={"/api":[{{ tpl ( .Values.openshiftOauthProxy.tokenReview | toJson ) . }}], "/storage":[{{ tpl ( .Values.openshiftOauthProxy.tokenReview | toJson ) . }}], "/grafana":[{{ tpl ( .Values.openshiftOauthProxy.tokenReview | toJson ) . }}] }
     {{- if .Values.authentication.basicAuth.enabled }}
     - --htpasswd-file=/etc/openshift_oauth_proxy/basicauth/{{ .Values.authentication.basicAuth.filename }}
     {{- end }}

and it broke the auth proxy:

$ oc logs -f pod/cryostat-68ff4cffc9-kltmp
Defaulted container "cryostat-authproxy" out of: cryostat-authproxy, cryostat, cryostat-db, cryostat-storage, cryostat-grafana, cryostat-jfr-datasource
2024/04/19 20:46:25 main.go:145: resources must be a JSON map of paths to authorizationv1beta1.ResourceAttributes: json: cannot unmarshal array into Go value of type v1.ResourceAttributes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh that's unfortunate :(( I think we would have to keep the current implementation meanwhile then...

@@ -16,6 +16,8 @@
- --tls-cert=/etc/tls/private/tls.crt
- --tls-key=/etc/tls/private/tls.key
- --proxy-prefix=/oauth2
- --openshift-sar={{ tpl ( .Values.openshiftOauthProxy.accessReview | toJson ) . }}
- --openshift-delegate-urls={"/":{{ .Values.openshiftOauthProxy.tokenReview | toJson }}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to restrict the path prefix to /api since its usage its mostly allow non-browser access or access from service account?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about that, but this would mean that it's only authorizing on requests that will be passed to the Cryostat upstream, and not to requests that go to Grafana or S3 or any other future containers that may be configured to be reachable through the proxy. I guess these could all have different token reviews, but it seemed simplest to just keep them everything guarded equally. If there is a case for expanding the configuration options later we can always add that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, another option would be to use the same token review for the paths /api, /storage, and /grafana. This way all three current upstream containers are guarded with the same RBAC, but notable /health (which also goes to the upstream Cryostat container) would become unguarded, which seems like it makes sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me! It would be nice to have the key as regular expression but not sure if that's supported in ocp-oauth :D

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking at the sources earlier and it doesn't look it supports anything other than string prefixes, unfortunately.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh opps.. I guess I can have a look and see if I can help them on that haha :D

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repo activity is very dead and looks like it is on maintenance mode, so I'm not sure if that's worth the time. Not at least without talking to someone from that team first and verifying if they'd actually accept such a contribution upstream.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you who I should contact? I am unsure who are the maintainers there :D Probably not anytime soon tho until I finish some tasks here :D

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I will try contacting them sometimes soon! Thank youu!!

Comment on lines 2 to 20
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "cryostat.fullname" . }}
labels:
{{- include "cryostat.labels" . | nindent 4 }}
rules:
- apiGroups:
- "authorization.k8s.io"
resources:
- subjectaccessreviews
verbs:
- create
- apiGroups:
- "authentication.k8s.io"
resources:
- tokenreviews
verbs:
- create
Copy link
Member

@tthvo tthvo Apr 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the built-in cluterrole system:auth-delegator on openshift since it specifies the same rules? Maybe we can add a check if auth.openshift.enabled too?

$ oc get clusterrole/system:auth-delegator -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  creationTimestamp: "2023-08-24T02:53:01Z"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: system:auth-delegator
  resourceVersion: "101"
  uid: 55530dfd-424d-4d17-805c-b3f9b4bd2ae3
rules:
- apiGroups:
  - authentication.k8s.io
  resources:
  - tokenreviews
  verbs:
  - create
- apiGroups:
  - authorization.k8s.io
  resources:
  - subjectaccessreviews
  verbs:
  - create

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good ideas, I'll try that out.

@andrewazores
Copy link
Member Author

Fun and easy test to validate all this.

By default:

image

image


Giving developer account a RoleBinding for the pre-existing edit Role, which includes create pods/exec:

image

image

image

Now the user has access:

image

If I delete the RoleBinding while the user is logged in:

image

And then log out and back in again:

image

Comment on lines 114 to 127
| `openshiftOauthProxy.subjectAccessReview[0].group` | The OpenShift resource group that the SubjectAccessReview will be performed for | `""` |
| `openshiftOauthProxy.subjectAccessReview[0].resource` | The OpenShift resource that the SubjectAccessReview will be performed for | `pods` |
| `openshiftOauthProxy.subjectAccessReview[0].subresource` | The OpenShift subresource that the SubjectAccessReview will be performed for | `exec` |
| `openshiftOauthProxy.subjectAccessReview[0].name` | The OpenShift resource name that the SubjectAccessReview will be performed for | `""` |
| `openshiftOauthProxy.subjectAccessReview[0].namespace` | The OpenShift Namespace that the SubjectAccessReview will be performed for. | `{{ .Release.Namespace }}` |
| `openshiftOauthProxy.subjectAccessReview[0].verb` | The OpenShift resource verb that the SubjectAccessReview will be performed for | `create` |
| `openshiftOauthProxy.subjectAccessReview[0].version` | The OpenShift resource version that the SubjectAccessReview will be performed for | `""` |
| `openshiftOauthProxy.tokenAccessReview.group` | The OpenShift resource group that the TokenAccessReview will be performed for. See https://github.com/openshift/oauth-proxy/?tab=readme-ov-file#delegate-authentication-and-authorization-to-openshift-for-infrastructure | `""` |
| `openshiftOauthProxy.tokenAccessReview.resource` | The OpenShift resource that the TokenAccessReview will be performed for. | `pods` |
| `openshiftOauthProxy.tokenAccessReview.subresource` | The OpenShift resource that the TokenAccessReview will be performed for. | `exec` |
| `openshiftOauthProxy.tokenAccessReview.name` | The OpenShift resource name that the TokenAccessReview will be performed for. | `""` |
| `openshiftOauthProxy.tokenAccessReview.namespace` | The OpenShift namespace that the TokenAccessReview will be performed for. | `{{ .Release.Namespace }}` |
| `openshiftOauthProxy.tokenAccessReview.verb` | The OpenShift resource name that the TokenAccessReview will be performed for. | `create` |
| `openshiftOauthProxy.tokenAccessReview.version` | The OpenShift resource version that the TokenAccessReview will be performed for. | `""` |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we combine the tokenAccessReview into subjectAccessReview to match the operator's implementation?
https://github.com/cryostatio/cryostat-operator/blob/b300689a645e3741ccd5468e09acf20b5b9fb4d2/api/v1beta2/cryostat_types.go#L484-L496

Comment on lines 19 to 20
- --openshift-sar=[{{ tpl ( .Values.openshiftOauthProxy.accessReview | toJson ) . }}]
- --openshift-delegate-urls={"/":{{ tpl ( .Values.openshiftOauthProxy.accessReview | toJson ) . }}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to make this bit optional? This would allow OpenShift OAuth to work for namespace admins (who can't create ClusterRoleBindings), just without an SAR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. I'll try it out.

Copy link
Member

@ebaron ebaron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@ebaron ebaron merged commit bb0864b into cryostatio:cryostat3 May 29, 2024
2 checks passed
@andrewazores andrewazores deleted the openshift-sar branch May 30, 2024 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat New feature or request safe-to-test
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants