---
# System prepended metadata

title: Setup RBAC in GKE Cluster

---

# Setup RBAC in GKE Cluster

## Create an Oauth App

For this guide we will be using Google as Auth Provider. You can follow the steps outlined in the official Backstage.io docs

https://backstage.io/docs/auth/google/provider

> **NOTE:**
> When creating a cluster using testbed we are given an url to access TAP-GUI in the form:
> `tap-gui.tap.<CLUSTER_NAME>.tapdemo.vmware.com/`

We need both that url and the localhost Authorized redirect URIsurls setup in the **Authorized JavaScript origins** and in the **Authorized redirect URIs**

#### Example

Authorized JavaScript origins: 
https://tap-gui.tap.mycluster.tapdemo.vmware.com
http://localhost:3000

Authorized redirect URIs:
https://tap-gui.tap.rbac.tapdemo.vmware.com/api/auth/google/handler/frame
http://localhost:7007/api/auth/google/handler/frame

## Enable Identity Service for GKE

This process usually takes several minutes:

```bash
gcloud container clusters update $CLUSTER_NAME --enable-identity-service --region $CLUSTER_REGION --project $CLUSTER_PROJECT
```

After it is done, several Kubernetes objects are created. We just need to focus on one.

## Configure Identity Service for GKE

### Get the default client config

```bash
kubectl get clientconfig default -n kube-public -o yaml > client-config.yaml
```

### Update the spec.authentication

By default the client-config.yaml doesn't have any authentication so we will need to add the section to the file.

```yaml=
apiVersion: authentication.gke.io/v2alpha1
kind: ClientConfig
metadata:
  creationTimestamp: "2023-04-03T15:57:37Z"
  generation: 1
  name: default
  namespace: kube-public
  resourceVersion: "143607"
  uid: SOME_UID
spec:
  certificateAuthorityData: ...
  internalServer: ""
  name: CLUSTER_NAME
  server: CLUSTER_URL
  # Add authentication section below this line:
  authentication:
  - name: oidc
    oidc:
      clientID: CLIENT_ID
      cloudConsoleRedirectURI: https://console.cloud.google.com/kubernetes/oidc
      extraParams: prompt=consent,access_type=offline
      issuerURI: https://accounts.google.com
      kubectlRedirectURI: http://localhost:7007/api/auth/google/handler/frame
      scopes: openid, email
      userClaim: email
  # end of authentication section 
status: {}
```

It is important that the kubectlRedirectURI matches the redirect URI that was configured when creating the Oauth application on the first step.

### Update the client config

```bash
kubectl apply -f client-config.yaml
```

### Get the login file

Create a copy of the client-config.yaml created in the previous step

```bash
cp client-config.yaml login-config.yaml
```

Open the login-config.yaml and adjust the spec.authentication entry with the Client Secret from the oauth app:

```yaml=
...
spec:
  certificateAuthorityData: ...
  internalServer: ""
  name: CLUSTER_NAME
  server: CLUSTER_URL
  authentication:
  - name: oidc
    oidc:
      clientID: CLIENT_ID
      # Add the CLIENT_SECRET here:
      clientSecret: CLIENT_SECRET
      cloudConsoleRedirectURI: https://console.cloud.google.com/kubernetes/oidc
      extraParams: prompt=consent,access_type=offline
      issuerURI: https://accounts.google.com
      kubectlRedirectURI: http://localhost:7007/api/auth/google/handler/frame
      scopes: openid, email
      userClaim: email
...
```

This file can be used later by developers to login to the cluster

## Create an RBAC policy for your cluster

To grant access to resources in a particular namespace, we need to create a Role and a RoleBinding.

### Create a new developer namespace

```bash
kubectl create namespace my-apps-1 
```

The previous command creates a new namespace called `my-apps-1`, however that namespace is still not ready to host workloads since it lacks the proper resources.
In order to provision those resources we must add the `apps.tanzu.vmware.com/tap-ns=""` label.

```bash
kubectl label namespaces my-apps-1 apps.tanzu.vmware.com/tap-ns=""
```

You can verify that the namespace now has the proper resources by running:
```bash
kubectl get secrets,serviceaccount,rolebinding,pods,workload,configmap -n my-apps-1
```

### Create a new Role

In this step we will create a Role (unlike ClusterRoles that grant access to a particular cluster, Roles grant access to resources on a particular namespace) that is scoped to the previously created namespace.

Create a my-apps-1-role.yaml and update it with the following content

```yaml=
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: my-apps-1
  name: my-apps-1-role
rules:
- apiGroups: ['']
  resources: ['pods', 'pods/log', 'services', 'configmaps', 'limitranges']
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['metrics.k8s.io']
  resources: ['pods']
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['apps']
  resources: ['deployments', 'replicasets', 'statefulsets', 'daemonsets']
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['autoscaling']
  resources: ['horizontalpodautoscalers']
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['networking.k8s.io']
  resources: ['ingresses']
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['networking.internal.knative.dev']
  resources: ['serverlessservices']
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [ 'autoscaling.internal.knative.dev' ]
  resources: [ 'podautoscalers' ]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['serving.knative.dev']
  resources:
  - configurations
  - revisions
  - routes
  - services
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['carto.run']
  resources:
  - clusterconfigtemplates
  - clusterdeliveries
  - clusterdeploymenttemplates
  - clusterimagetemplates
  - clusterruntemplates
  - clustersourcetemplates
  - clustersupplychains
  - clustertemplates
  - deliverables
  - runnables
  - workloads
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['source.toolkit.fluxcd.io']
  resources:
  - gitrepositories
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['source.apps.tanzu.vmware.com']
  resources:
  - imagerepositories
  - mavenartifacts
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['conventions.apps.tanzu.vmware.com']
  resources:
  - podintents
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['kpack.io']
  resources:
  - images
  - builds
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['scanning.apps.tanzu.vmware.com']
  resources:
  - sourcescans
  - imagescans
  - scanpolicies
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['tekton.dev']
  resources:
  - taskruns
  - pipelineruns
  - pipelines
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ['kappctrl.k14s.io']
  resources:
  - apps
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [ 'batch' ]
  resources: [ 'jobs', 'cronjobs' ]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
```

Create the role

```bash
kubectl apply -f my-apps-1-role.yaml
```

### Create a RoleBinding

A role binding grants the permissions defined in a role to a user or set of users.

Create a my-apps-1-rolebinding.yaml and update it with the following content

```yaml=
# Here we are granting the specific user USER_ID access the permissions
# defined in the my-apps-1-role which are limited to the my-apps-1 namespace.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: my-apps-1-rolebinding
  namespace: my-apps-1
subjects:
- kind: User
  name: USER_ID
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: my-apps-1-role
  apiGroup: rbac.authorization.k8s.io
```

Create the rolebinding

```bash
kubectl apply -f my-apps-1-rolebinding.yaml
```

> **Note:** 
> USER_ID is the id returned by the oauth provider (usually an email but could be a username). For Google Auth is the email of the user.

## Verify on the CLI

To verify that the permissions are properly set we need to do the following:

**Install kubectl-oidc**
```bash
gcloud components install kubectl-oidc
```

**Login into the cluster**
```
kubectl oidc login --cluster=CLUSTER_NAME --login-config=login-config.yaml
```

After running the login command you will be taken to the browser to login using the oauth provider that was configured in the client-config.yaml.
If everything was setup correctly you should see a message in the browser saying something similar to:
> Authentication successful. Please close this window.

And in the console: 
```bash
2023/04/03 14:48:44 Started webserver on localhost:7007.
2023/04/03 14:48:44 Attempting to open http://127.0.0.1:7007/login in default browser.
2023/04/03 14:48:49 OIDC Authentication successful.
```

Now if you try to view resources on namespaces other than my-apps-1 you will get an error message:

```bash
$ kubectl get pods
Error from server (Forbidden): pods is forbidden: User "YOUR_USER_ID" cannot list resource "pods" in API group "" in the namespace "default"
```

However if you scope the query to the my-apps-1 namespace everything should work properly
```bash
$ kubectl get pods -n my-apps-1
No resources found in my-apps-1 namespace.
```

## Create a workload in the new developer namespace

tanzu apps workload create <WORKLOAD_NAME> --namespace my-apps-1 --git-branch main --git-repo https://github.com/jcospina/node-express --label apps.tanzu.vmware.com/has-tests=true --label app.kubernetes.io/part-of=node-express-accelerator --type web --yes

> **Note:**
> You can use whatever repo you like but feel free to use https://github.com/jcospina/node-express

After running the command you'll see an output such as

```bash
Create workload:
      1 + |---
      2 + |apiVersion: carto.run/v1alpha1
      3 + |kind: Workload
      4 + |metadata:
      5 + |  labels:
      6 + |    app.kubernetes.io/part-of: node-express-accelerator
      7 + |    apps.tanzu.vmware.com/has-tests: "true"
      8 + |    apps.tanzu.vmware.com/workload-type: web
      9 + |  name: node-workload
     10 + |  namespace: my-apps-1
     11 + |spec:
     12 + |  source:
     13 + |    git:
     14 + |      ref:
     15 + |        branch: main
     16 + |      url: https://github.com/jcospina/node-express

Created workload "node-workload"

To see logs:   "tanzu apps workload tail node-workload --namespace my-apps-1"
To get status: "tanzu apps workload get node-workload --namespace my-apps-1"
```

> **Note:**
> Verify that the `namespace` property matches the developer namespace created in the previous steps.

Now if you query for workloads inside the my-apps-1 namespace you should see the newly created workload

```bash
$ kubectl get workloads -n my-apps-1
NAME            SOURCE                                     SUPPLYCHAIN               READY   REASON                 AGE
node-workload   https://github.com/jcospina/node-express   source-test-scan-to-url   False   HealthyConditionRule   2m2s
```

> **Note:**
> By default the testbed cluster don't have a testing pipeline or scan policy configured so there will be errors on the workload.

## Create a workload in another namespace

When logging in to the cluster using `kubectl oidc` a new context is created in your kubeconfig. Since in this document the owner of the cluster is the same namespace scoped user, we can switch contexts back to the admin in order to do operations otherwise restricted.

List the current-contexts

```bash
$ kubectl config get-contexts
CURRENT   NAME                                                    CLUSTER                                                 AUTHINFO                                                NAMESPACE
          <DEFAULT_CONTEXT>                                       <CLUSTER_NAME>                                          <AUTHINFO>
*         rbac-rbac-anthos-default-user                           rbac                                                    rbac-anthos-default-user
```

You will notice that the new context will have a * under CURRENT and we want to go back to the default context.

```bash
$ kubectl config use-context <DEFAULT_CONTEXT>
Switched to context "<DEFAULT_CONTEXT>". 
```

Now that we are in the default context and we can access all the cluster resources, we'll create a new workload in the my-apps namespace:

```bash
tanzu apps workload create <MY_APPS_WORKLOAD> --namespace my-apps --git-branch main --git-repo https://github.com/jcospina/node-express --label apps.tanzu.vmware.com/has-tests=true --label app.kubernetes.io/part-of=node-express-accelerator --type web --yes
```

If you now query the cluster for the list of workloads across all namespaces you should see two:

```bash
$ kubectl get workloads -A
NAMESPACE   NAME               SOURCE                                     SUPPLYCHAIN               READY   REASON                 AGE
my-apps-1   node-workload      https://github.com/jcospina/node-express   source-test-scan-to-url   False   HealthyConditionRule   35m
my-apps     my-apps-workload   https://github.com/jcospina/node-express   source-test-scan-to-url   False   HealthyConditionRule   47s
```

## Adding OIDC to TAP-GUI
So far we have configured the cluster to use OIDC as an authentication method and added a new role scoped to the my-apps-1 namespace. Now we will configure TAP GUI to connect to the cluster and use OIDC as well.

### Get the tap-values.yaml

Get the tap-values.yaml

```
kubectl get secret tap-tap-install-values -n tap-install -o json | jq -r '.data | to_entries[] | select(.key | test("^tap-values.*\\.yaml$")) | .value' | base64 -d > tap-values.yaml
```

> **Note:**
> For the previous command to work you need to have [jq](https://stedolan.github.io/jq/) installed.

The tap-values.yaml looks like this:

```yaml=
accelerator:
    ingress:
        include: true
    domain: tap.rbac.tapdemo.vmware.com
    server:
        service_type: ClusterIP
buildservice:
    kp_default_repository: tapacr.azurecr.io/rbac/build-service
    kp_default_repository_secret:
        name: registry-credentials
        namespace: my-apps
ceip_policy_disclosed: true
contour:
    envoy:
        service:
            type: LoadBalancer
        hostPorts:
            http: 0
            https: 0
grype:
    namespace: my-apps
    targetImagePullSecret: registry-credentials
learningcenter:
    ingressDomain: learning-center.tap.rbac.tapdemo.vmware.com
ootb_supply_chain_basic:
    gitops:
        ssh_secret: ""
    registry:
        repository: <CLUSTER_NAME>/apps-03-04-2023-14-57-55-511246857
        server: tapacr.azurecr.io
ootb_supply_chain_testing:
    gitops:
        ssh_secret: ""
    registry:
        repository: <CLUSTER_NAME>/apps-03-04-2023-14-57-55-511246857
        server: tapacr.azurecr.io
ootb_supply_chain_testing_scanning:
    gitops:
        ssh_secret: ""
    registry:
        repository: <CLUSTER_NAME>/apps-03-04-2023-14-57-55-511246857
        server: tapacr.azurecr.io
profile: full
supply_chain: testing_scanning
cnrs:
    ingress_issuer: ""
    domain_name: tap.<CLUSTER_NAME>.tapdemo.vmware.com
tap_gui:
    ingressEnabled: true
    ingressDomain: tap.<CLUSTER_NAME>.tapdemo.vmware.com
    service_type: ClusterIP
    app_config:
        proxy:
            /metadata-store:
                target: https://metadata-store-app.metadata-store:8443/api/v1
                changeOrigin: true
                secure: false
                headers:
                    Authorization: Bearer ...
                    X-Custom-Source: project-star
source_controller:
    ca-cert-data: null
metadata_store:
    ns_for_export_app_cert: '*'
tap_telemetry:
    installed_for_vmware_internal_use: "true"
appsso:
    domain_name: tap.<CLUSTER_NAME>.tapdemo.vmware.com
```

### Update the tap-values.yaml

All updates will be done under the tap_gui section.


Add an auth section and a kubernetes section under app_config in tap-values.yaml
```yaml=
...
tap_gui:
    ingressEnabled: true
    ingressDomain: tap.<CLUSTER_NAME>.tapdemo.vmware.com
    service_type: ClusterIP
    app_config:
        proxy:
            /metadata-store:
                target: https://metadata-store-app.metadata-store:8443/api/v1
                changeOrigin: true
                secure: false
                headers:
                    Authorization: Bearer ...
                    X-Custom-Source: project-star
        auth:
            environment: development
            providers:
                google:
                    development:
                        clientId: CLIENT_ID
                        clientSecret: CLIENT_SECRET
        kubernetes:
            clusterLocatorMethods:
            - type: config
              clusters:
              - name: DISPLAY_NAME
                url: CLUSTER_URL
                authProvider: google
                skipTLSVerify: true
                skipMetricsLookup: true
...
```

Where CLIENT_ID and CLIENT_SECRET are the values obtained from the oauth app, and CLUSTER_URL is the URL from the ``client-config.yaml file``.

Save and update the tap-values:

```bash
tanzu package installed update tap -p tap.tanzu.vmware.com -v $TAP_VERSION --values-file tap-values.yaml -n tap-install
```

Where $TAP_VERSION can be obtained after running the following:
```
TAP_VERSION=$(kubectl get pkgi tap -n tap-install -o jsonpath="{.status.version}")
```


