Skip to content

Latest commit

 

History

History
684 lines (527 loc) · 19.8 KB

File metadata and controls

684 lines (527 loc) · 19.8 KB

Warning

This is a work in progress.

RHDH 1.4 with Conditional Policy Permission

Currently, the newest RHDH version 1.4 has a GUI for permission management. It also supports conditional policy. However, it has some bugs. This document demonstrates how it works and provides a workaround to add conditional policy permission to RHDH 1.4.

Install RHSSO/Keycloak

We will use Keycloak to manage users and use it as the OAuth2 provider for RHDH.

oc new-project demo-keycloak



oc delete -f ${BASE_DIR}/data/install/keycloak-db-pvc.yaml -n demo-keycloak

cat << EOF > ${BASE_DIR}/data/install/keycloak-db-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgresql-db-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
EOF

oc create -f ${BASE_DIR}/data/install/keycloak-db-pvc.yaml -n demo-keycloak



oc delete -f ${BASE_DIR}/data/install/keycloak-db.yaml -n demo-keycloak

cat << EOF > ${BASE_DIR}/data/install/keycloak-db.yaml
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgresql-db
spec:
  serviceName: postgresql-db-service
  selector:
    matchLabels:
      app: postgresql-db
  replicas: 1
  template:
    metadata:
      labels:
        app: postgresql-db
    spec:
      containers:
        - name: postgresql-db
          image: postgres:15
          args: ["-c", "max_connections=1000"]
          volumeMounts:
            - mountPath: /data
              name: cache-volume
          env:
            - name: POSTGRES_USER
              value: testuser
            - name: POSTGRES_PASSWORD
              value: testpassword
            - name: PGDATA
              value: /data/pgdata
            - name: POSTGRES_DB
              value: keycloak
      volumes:
        - name: cache-volume
          persistentVolumeClaim:
            claimName: postgresql-db-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: postgres-db
spec:
  selector:
    app: postgresql-db
  type: LoadBalancer
  ports:
  - port: 5432
    targetPort: 5432

EOF

oc create -f ${BASE_DIR}/data/install/keycloak-db.yaml -n demo-keycloak



RHSSO_HOST="keycloak-demo-keycloak.apps.demo-01-rhsys.wzhlab.top"

cd ${BASE_DIR}/data/install/

openssl req -subj "/CN=$RHSSO_HOST/O=Test Keycloak./C=US" -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem

oc delete secret example-tls-secret -n demo-keycloak
oc create secret tls example-tls-secret --cert certificate.pem --key key.pem -n demo-keycloak



oc delete secret keycloak-db-secret -n demo-keycloak
oc create secret generic keycloak-db-secret -n demo-keycloak \
  --from-literal=username=testuser \
  --from-literal=password=testpassword



oc delete -f ${BASE_DIR}/data/install/keycloak.yaml -n demo-keycloak

cat << EOF > ${BASE_DIR}/data/install/keycloak.yaml
apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
metadata:
  name: example-kc
spec:
  instances: 1
  db:
    vendor: postgres
    host: postgres-db
    usernameSecret:
      name: keycloak-db-secret
      key: username
    passwordSecret:
      name: keycloak-db-secret
      key: password
  http:
    tlsSecret: example-tls-secret
  # ingress:
  #   className: openshift-default
  hostname:
    hostname: $RHSSO_HOST
  proxy:
    headers: xforwarded
EOF

oc create -f ${BASE_DIR}/data/install/keycloak.yaml -n demo-keycloak

# get the keycloak initial admin user and password
oc get secret example-kc-initial-admin -n demo-keycloak -o jsonpath='{.data.username}' | base64 --decode && echo
# temp-admin

oc get secret example-kc-initial-admin -n demo-keycloak -o jsonpath='{.data.password}' | base64 --decode && echo
# ef8636f3ba314481bf173e5ca2ac79a7


# in postgresql pod terminal
psql -U testuser -d keycloak
# Type "help" for help.

# keycloak=# SHOW max_connections;
#  max_connections 
# -----------------
#  1000
# (1 row)

Based on the demo requirements, we need to create a realm named RHDH, which will be used for RHDH later.

Create a test user demo-user

Set password for the user

Make the password not expire.

Create a client for the RHDH, and set the redirect URL.

The redirect URL looks like this: https://<RHDH_URL>/api/auth/oidc/handler/frame

Copy the client secret, as it will be used later.

For RHDH 1.4, based on the official documentation:

When using client credentials, the access type must be set to confidential and service accounts must be enabled. You must also add the following roles from the realm-management client role:

query-groups query-users view-users

If you want to sync users and groups using the client, you need to configure the rhdh-client with these additional steps:

  1. Enable service account roles

  1. Add service account roles

Install RHDH

Create a new namespace for the RHDH deployment.

oc new-project demo-rhdh

There are currently two ways to install RHDH: using the operator or using a Helm chart. In this demonstration, we'll use the Helm chart.

Configure RHDH

For Keycloak:

  • Use Chrome to access the web UI
  • Get the metadata URL from the realm

Now we'll begin configuring RHDH using the CLI.

# for k8s plugin
# let the plugin access k8s resources
oc delete -f ${BASE_DIR}/data/install/role-rhdh.yaml

cat << EOF > ${BASE_DIR}/data/install/role-rhdh.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: backstage-read-only
rules:
  - apiGroups:
      - '*'
    resources:
      - pods
      - configmaps
      - services
      - deployments
      - replicasets
      - horizontalpodautoscalers
      - ingresses
      - statefulsets
      - limitranges
      - resourcequotas
      - daemonsets
      - pipelineruns
      - taskruns
      - routes
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - batch
    resources:
      - jobs
      - cronjobs
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - metrics.k8s.io
    resources:
      - pods
    verbs:
      - get
      - list
EOF
oc apply -f ${BASE_DIR}/data/install/role-rhdh.yaml


NAMESPACES="demo-rhdh"

oc delete -f ${BASE_DIR}/data/install/sa-rhdh.yaml
cat << EOF > ${BASE_DIR}/data/install/sa-rhdh.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: backstage-read-only-sa
  namespace: $NAMESPACES  # Replace with the appropriate namespace

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: backstage-read-only-binding
subjects:
  - kind: ServiceAccount
    name: backstage-read-only-sa
    namespace: $NAMESPACES  # Replace with the appropriate namespace
roleRef:
  kind: ClusterRole
  name: backstage-read-only
  apiGroup: rbac.authorization.k8s.io

EOF
oc create -f ${BASE_DIR}/data/install/sa-rhdh.yaml -n $NAMESPACES


# create pvc for rhdh plugin
oc delete -f ${BASE_DIR}/data/install/pvc-rhdh.yaml

cat << EOF > ${BASE_DIR}/data/install/pvc-rhdh.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: rhdh-plugin
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  # storageClassName: lvms-vg1
  volumeMode: Filesystem
EOF

oc apply -f ${BASE_DIR}/data/install/pvc-rhdh.yaml -n $NAMESPACES


# create token of the sa, and save to variable, expire date is 100 years
SA_TOKEN=`oc create token backstage-read-only-sa --duration=876000h -n $NAMESPACES`

# SECRET_NAME=$(oc get sa backstage-read-only-sa -n $NAMESPACES -o jsonpath='{.secrets[0].name}' )
# SA_TOKEN=$(oc get secret $SECRET_NAME -n $NAMESPACES -o jsonpath='{.data.token}'  | base64 --decode)
echo $SA_TOKEN

# check the expire date of the token
echo $SA_TOKEN | cut -d '.' -f2 | base64 -d | jq -r '.exp' | xargs -I {} date -d @{}
# Sun Jul 23 10:57:39 AM CST 2124

# get env variable for backstage
OCP_NAME="demo-01-rhsys"
OCP_BASE_URL="demo-01-rhsys.wzhlab.top"
OCP_API="https://api.$OCP_BASE_URL:6443"

# GITLAB_BASE_HOST="gitlab-demo-gitlab.apps.$OCP_BASE_URL"
# GITLAB_BASE_URL="https://gitlab-demo-gitlab.apps.$OCP_BASE_URL"
# GITLAB_PAT="<your gitlab personal access token>"
# AUTH_GITLAB_CLIENT_ID="you gitlab client id"
# AUTH_GITLAB_CLIENT_SECRET="you gitlab client secret"

AUTH_KEYCLOAK_CLIENT_ID="rhdh-client"
AUTH_KEYCLOAK_CLIENT_SECRET="<your keycloak client secret>"
KEYCLOAK_BASE_URL="https://keycloak-demo-keycloak.apps.$OCP_BASE_URL"
KEYCLOAK_REALM="RHDH"
KEYCLOAK_PROMPT="auto"

SESSION_SECRET=`openssl rand -hex 32`
# GITHUB_TOKEN="<your github personal access token>"

# ARGOCD_NS="demo-gitops"
# ARGOCD_INSTANCE_NAME="argocd"
# # no ending "/"
# ARGOCD_URL="https://$ARGOCD_INSTANCE_NAME-$ARGOCD_NS.apps.$OCP_BASE_URL"
# # ARGOCD_SECRET="$ARGOCD_INSTANCE_NAME-cluster"
# # ARGOCD_PASSWORD=`oc get secret $ARGOCD_SECRET -n $ARGOCD_NS -o jsonpath='{.data.admin\.password}' | base64 --decode`
# ARGOCD_USER="alice"
# ARGOCD_PASSWORD="redhadocp"
# ARGOCD_TOKEN="<your argocd token>"

# JFROG_URL="http://192.168.50.17:8082"
# JFROG_TOKEN="<your jfrog token>"


# create secret based on env variable
oc delete secret wzh-rhdh-credentials -n $NAMESPACES
oc create secret generic wzh-rhdh-credentials -n $NAMESPACES \
--from-literal=OCP_NAME=$OCP_NAME \
--from-literal=OCP_BASE_URL=$OCP_BASE_URL \
--from-literal=OCP_API=$OCP_API \
--from-literal=AUTH_KEYCLOAK_CLIENT_ID=$AUTH_KEYCLOAK_CLIENT_ID \
--from-literal=AUTH_KEYCLOAK_CLIENT_SECRET=$AUTH_KEYCLOAK_CLIENT_SECRET \
--from-literal=KEYCLOAK_BASE_URL=$KEYCLOAK_BASE_URL \
--from-literal=KEYCLOAK_REALM=$KEYCLOAK_REALM \
--from-literal=KEYCLOAK_PROMPT=$KEYCLOAK_PROMPT \
--from-literal=SESSION_SECRET=$SESSION_SECRET \
--from-literal=SA_TOKEN=$SA_TOKEN


# create app config
oc delete configmap app-config-rhdh -n $NAMESPACES

cat << EOF > ${BASE_DIR}/data/install/app-config-rhdh.yaml
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: app-config-rhdh
data:
  app-config-rhdh.yaml: |
    app:
      title: WZH Developer Hub

    auth:
      # environment: production
      # using development, will give you guest login options :)
      environment: development
      session:
        secret: \${SESSION_SECRET}
      providers:
        oidc:
          # production:
          development:
            clientId: \${AUTH_KEYCLOAK_CLIENT_ID}
            clientSecret: \${AUTH_KEYCLOAK_CLIENT_SECRET}
            metadataUrl: \${KEYCLOAK_BASE_URL}/realms/\${KEYCLOAK_REALM}/.well-known/openid-configuration
            prompt: \${KEYCLOAK_PROMPT} # recommended to use auto
            # Uncomment for additional configuration options 
            # callbackUrl: \${KEYCLOAK_CALLBACK_URL} 
            # tokenEndpointAuthMethod: \${KEYCLOAK_TOKEN_ENDPOINT_METHOD} 
            # tokenSignedResponseAlg: \${KEYCLOAK_SIGNED_RESPONSE_ALG} 
            # scope: \${KEYCLOAK_SCOPE}  
            # If you are using the keycloak-backend plugin, use the preferredUsernameMatchingUserEntityName resolver to avoid a login error.
            signIn:
              resolvers:
                - resolver: preferredUsernameMatchingUserEntityName
        guest:
          dangerouslyAllowOutsideDevelopment: true
          userEntityRef: user:default/static-demo-admin

    signInPage: oidc

    catalog:
      rules:
        - allow: [Component, System, API, Resource, Location, Template]

      locations:
        - target: https://github.com/wangzheng422/docker_env/blob/dev/redhat/ocp4/4.16/files/org.yaml
          type: url
          rules:
            - allow: [Group, User]
        - target: https://github.com/nepdemo/rhdh-book1-templates/blob/wzh/quarkus-with-angular/template.yaml
          type: url
          rules:
            - allow: [Template]
        - target: https://github.com/nepdemo/rhdh-book1-templates/blob/wzh/nestjs-with-postgres/template.yaml
          type: url
          rules:
            - allow: [Template]

      providers:
        keycloakOrg:
          default:
            baseUrl: \${KEYCLOAK_BASE_URL}
            loginRealm: \${KEYCLOAK_REALM}
            realm: \${KEYCLOAK_REALM}
            clientId: \${AUTH_KEYCLOAK_CLIENT_ID}
            clientSecret: \${AUTH_KEYCLOAK_CLIENT_SECRET}
            schedule: # optional; same options as in TaskScheduleDefinition
              # supports cron, ISO duration, "human duration" as used in code
              frequency: { minutes: 1 }
              # supports ISO duration, "human duration" as used in code
              timeout: { minutes: 1 }
              initialDelay: { seconds: 15 }

    kubernetes:
      serviceLocatorMethod:
        type: "multiTenant"
      clusterLocatorMethods:
        - type: "config"
          clusters:
            - name: \${OCP_NAME}
              url: \${OCP_API}
              authProvider: "serviceAccount"
              skipTLSVerify: true
              serviceAccountToken: \${SA_TOKEN}
      customResources:
        - group: 'tekton.dev'
          apiVersion: 'v1'
          plural: 'pipelineruns'
        - group: 'tekton.dev'
          apiVersion: 'v1'
          plural: 'taskruns'
        - group: 'route.openshift.io'
          apiVersion: 'v1'
          plural: 'routes'

    permission:
      enabled: true
      rbac:
        admin:
          users:
            - name: user:default/static-demo-admin
        pluginsWithPermission:
          - catalog
          - scaffolder
          - permission
          - topology
          - kubernetes

    enabled:
      kubernetes: true
      # techdocs: true
      # argocd: true
      # sonarqube: false
      keycloak: true
      keycloakOrg: true
      # ocm: true
      # github: false
      # githubOrg: false
      # gitlab: true
      # jenkins: false
      permission: true
EOF

oc create -f ${BASE_DIR}/data/install/app-config-rhdh.yaml -n $NAMESPACES


oc scale deployment redhat-developer-hub --replicas=0 -n $NAMESPACES

oc scale deployment redhat-developer-hub --replicas=1 -n $NAMESPACES

Expand Root Schema → Backstage chart schema → Backstage parameters → Extra app configuration files to inline into command arguments

upstream:
  backstage:
    extraAppConfig:
      - configMapRef: app-config-rhdh
        filename: app-config-rhdh.yaml
    # ... other Red Hat Developer Hub Hel

In addition to the above configuration, you can enable built-in plugins (which are disabled by default) by adding the following to the Helm config. Simply switch to YAML view.

First, get the digest of the plugin:

npm view @wangzheng422/backstage-plugin-scaffolder-backend-module-wzh-custom-actions-dynamic@0.1.9 dist.integrity
# sha512-qglFOgfep5ACQwjVmB3m+GeiOixz5JcrF/0MBiAWTbCGdp0XKIG03owGn+MDo2uxSJLSGmmRYipCQv10Um1/lA==


npm view @wangzheng422/backstage-plugin-scaffolder-backend-module-dummy-wzh-actions-dynamic@0.1.1 dist.integrity
# sha512-d8SGXRkjJExz2mQbzg8+gF3yOIUrgeYgX8+AJ0RR7eaQ46fvYKqiyRLdKjRGwjLVTdkX0PK8NU6C344VyamVUw==
global:
  dynamic:
    plugins:
      # rbac
      - package: ./dynamic-plugins/dist/backstage-community-plugin-rbac
        disabled: false

      # keycloak
      - package: ./dynamic-plugins/dist/backstage-community-plugin-catalog-backend-module-keycloak-dynamic
        disabled: false

      # for teckton
      - package: ./dynamic-plugins/dist/backstage-community-plugin-tekton
        disabled: false

      # for k8s
      - package: ./dynamic-plugins/dist/backstage-plugin-kubernetes-backend-dynamic
        disabled: false

      # for topology, which integrate ocp webui
      # https://janus-idp.io/plugins/topology/
      - package: ./dynamic-plugins/dist/backstage-community-plugin-topology
        disabled: false

      # for custom actions demo, wrap with dynamic plugin
      # custom action demo ok
      # - package: "@wangzheng422/backstage-plugin-scaffolder-backend-module-wzh-custom-actions-dynamic@0.1.9"
      #   disabled: false
      #   integrity: sha512-qglFOgfep5ACQwjVmB3m+GeiOixz5JcrF/0MBiAWTbCGdp0XKIG03owGn+MDo2uxSJLSGmmRYipCQv10Um1/lA==

In summary, you can patch the Helm config like this:

global:
  dynamic:
    plugins:
      ......
  # patch the base url
  clusterRouterBase: apps.demo-01-rhsys.wzhlab.top
upstream:
  backstage:
    # patch for app config
    extraAppConfig:
      - configMapRef: app-config-rhdh
        filename: app-config-rhdh.yaml
    # patch for secrets
    extraEnvVarsSecrets:
      - wzh-rhdh-credentials
    extraEnvVars:
      # for https self certificate
      - name: NODE_TLS_REJECT_UNAUTHORIZED
        value: '0'
      # enable debug output
      - name: LOG_LEVEL
        value: debug
    # extraVolumes:
    #   # patch for static pvc
    #   - name: dynamic-plugins-root
    #     persistentVolumeClaim:
    #       claimName: rhdh-plugin

Testing with Conditional Policy

User and Group Requirements and Workaround

Currently, there are bugs in RHDH 1.4 where conditional policy doesn't work if a user doesn't belong to a group. For example, catalog entities won't display if conditional policy is configured.

To work around this issue, we need to set up several users and assign them to groups.

Conditional Policy

  1. During login, you'll see two login options as configured. One option (guest) is for testing admin access - you can log in without any password, which is ideal when something is broken and you need to access the system to troubleshoot. The other option is for normal user login testing. After initial configuration, the guest login should be removed.

  1. We log in using the guest option, which is linked to the admin user and role. In the policy section, we can see that a built-in policy has already been created and it is not editable.

You can see the two policies that have been created.

  1. We can create additional policies with access rules. When opening a rule, we can see it contains name, description, user/group, and permission settings.

When editing the permission, you can select which target plugin resources to control and what kinds of actions users can perform.

For conditions, you can control which specific resources users or groups can access.

Results

After permissions are granted, users will be able to see (or not see) resources in the catalog based on the configured policies.

End