solo-io / gloo-portal-issues

Public tracker for issues related to Gloo Portal
https://docs.solo.io/gloo-portal/latest/
1 stars 3 forks source link

Portal integration with OIDC is broken #182

Closed bcollard closed 2 years ago

bcollard commented 2 years ago

Describe the bug Given a Portal CR configured with OIDC, I am expecting a login button in the upper right corner of a Portal page to take me to the login page, where I can click "connect with some OIDC provider"

To Reproduce

export GLOO_PORTAL_VERSION=1.3.0-beta6
#export GLOO_PORTAL_VERSION=1.2.8
export GLOO_VERSION=1.12.13

# DOMAIN NAMES
export PORTAL_FQDN=portal.mydomain.com
export API_FQDN=api.mydomain.com

################
# Gloo EDGE
################
helm repo update
helm upgrade -i gloo glooe/gloo-ee --namespace gloo-system --version ${GLOO_VERSION} \
  --create-namespace --set-string license_key="$LICENSE_KEY"

kubectl -n gloo-system wait po --for condition=Ready --timeout -1s --all

##########
# GLOO PORTAL
##########
cat << EOF > portal-values.yaml
glooEdge:
  enabled: true
licenseKey:
  secretRef:
    name: license
    namespace: gloo-system
    key: license-key
EOF

helm repo update
helm install gloo-portal gloo-portal/gloo-portal -n gloo-portal --values portal-values.yaml --version=${GLOO_PORTAL_VERSION} --create-namespace

kubectl -n gloo-portal wait pod --all --for condition=Ready --timeout -1s

##########
# KEYCLOAK
##########
kubectl create -f https://raw.githubusercontent.com/keycloak/keycloak-quickstarts/12.0.4/kubernetes-examples/keycloak.yaml
kubectl rollout status deploy/keycloak

# Get Keycloak URL and token
KEYCLOAK_URL=http://$(kubectl get service keycloak -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8080/auth
KEYCLOAK_TOKEN=$(curl -s -d "client_id=admin-cli" -d "username=admin" -d "password=admin" -d "grant_type=password" "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" | jq -r .access_token)
GLOO_GW_IP=$(glooctl proxy address | cut -d':' -f1)

# Create an initial token to register the client
read -r client token <<<$(curl -s -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"expiration": 0, "count": 1}' $KEYCLOAK_URL/admin/realms/master/clients-initial-access | jq -r '[.id, .token] | @tsv')

# Register the client
read -r id secret <<<$(curl -X POST -d "{ \"clientId\": \"${client}\" }" -H "Content-Type:application/json" -H "Authorization: bearer ${token}" ${KEYCLOAK_URL}/realms/master/clients-registrations/default| jq -r '[.id, .secret] | @tsv')

# Add allowed redirect URIs
curl -v -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X PUT -H "Content-Type: application/json" -d '{"serviceAccountsEnabled": true, "authorizationServicesEnabled": true, "redirectUris": ["https://'${PORTAL_FQDN}'/callback", "http://'${PORTAL_FQDN}'/callback", "http://'${GLOO_GW_IP}'/callback"]}' $KEYCLOAK_URL/admin/realms/master/clients/${id}

# Add the group attribute in the JWT token returned by Keycloak
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"name": "group", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", "config": {"claim.name": "group", "jsonType.label": "String", "user.attribute": "group", "id.token.claim": "true", "access.token.claim": "true"}}' $KEYCLOAK_URL/admin/realms/master/clients/${id}/protocol-mappers/models

# create groups "users" and "execs"
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"name": "users"}' $KEYCLOAK_URL/admin/realms/master/groups
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"name": "execs"}' $KEYCLOAK_URL/admin/realms/master/groups

# Create first user "user1", group: users, mail address: user1@solo.io
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"username": "user1", "email": "user1@solo.io", "enabled": true, "groups": ["users"], "attributes": {"group": "users"}, "credentials": [{"type": "password", "value": "password", "temporary": false}]}' $KEYCLOAK_URL/admin/realms/master/users

# Create second user "user2", group: users, mail address: user1@example.com
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"username": "user2", "email": "user2@example.com", "enabled": true, "groups": ["users"], "attributes": {"group": "users"}, "credentials": [{"type": "password", "value": "password", "temporary": false}]}' $KEYCLOAK_URL/admin/realms/master/users

# Create third user "exec1", group: execs, mail address: exec1@solo.io
curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"username": "exec1", "email": "exec1@solo.io", "enabled": true, "groups": ["execs"], "attributes": {"group": "execs"}, "credentials": [{"type": "password", "value": "password", "temporary": false}]}' $KEYCLOAK_URL/admin/realms/master/users

#############
# APIDOCS
############

for i in {1..2}; do
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: petstore-v$i
spec:
  replicas: 1
  selector:
    matchLabels:
      app: petstore
      version: v$i
  template:
    metadata:
      labels:
        app: petstore
        version: v$i
    spec:
      containers:
        - name: petstore
          image: swaggerapi/petstore
          # env:
          #   - name: SWAGGER_BASE_PATH
          #     value: /
          imagePullPolicy: Always
          ports:
            - name: http
              containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: petstore-v$i
spec:
  ports:
    - name: http
      port: 8080
      targetPort: http
      protocol: TCP
  selector:
    app: petstore
    version: v$i
EOF
done

for i in petstore-openapi-v1-pets petstore-openapi-v1-users petstore-openapi-v2-full; do
cat <<EOF | kubectl apply -f -
apiVersion: portal.gloo.solo.io/v1beta1
kind: APIDoc
metadata:
  name: $i
  namespace: default
spec:
  openApi:
    content:
      fetchUrl: https://raw.githubusercontent.com/solo-io/workshops/master/gloo-portal/openapi-specs/$i.json
EOF
done

cat << EOF | kubectl apply -f -
apiVersion: portal.gloo.solo.io/v1beta1
kind: APIProduct
metadata:
  name: petstore-product
  namespace: default
  labels:
    app: petstore
spec:
  displayInfo: 
    title: Petstore Product
    description: Fabulous API product for the Petstore
  # ---------------- This API offers 2 usage plans ---------------------
  usagePlans:
    - basic2
  # --------------------------------------------------------------------
  versions:
  - name: v1
    apis:
      - apiDoc:
          name: petstore-openapi-v1-pets
          namespace: default
      - apiDoc:
          name: petstore-openapi-v1-users
          namespace: default
    gatewayConfig:
      route:
        inlineRoute:
          backends:
            - upstream:
                name: default-petstore-v1-8080
                namespace: gloo-system
  - name: v2
    apis:
    - apiDoc:
        name: petstore-openapi-v2-full
        namespace: default
    gatewayConfig:
      route:
        inlineRoute:
          backends:
            - upstream:
                name: default-petstore-v2-8080
                namespace: gloo-system
EOF

####################
# ENVIRONMENT
####################

cat << EOF > env.yaml
apiVersion: portal.gloo.solo.io/v1beta1
kind: Environment
metadata:
  name: dev
  namespace: default
spec:
  domains:
    - ${API_FQDN} # the domain name where the API will be exposed
  displayInfo:
    description: This environment is meant for developers to deploy and test their APIs.
    displayName: Development
  basePath: /ecommerce # a global basepath for our APIs
  apiProducts: # we will select our APIProduct using a selector and the 2 version of it
    - namespaces:
      - "*" 
      labels:
      - key: app
        operator: In
        values:
        - petstore
      versions:
        names:
        - v1
        - v2
      basePath: "{%version%}" # this will dynamically prefix the API with the version names
      # ------------------------ UPDATE -----------------------------
      usagePlans:
        - basic2
      # -------------------------------------------------------------
  gatewayConfig:
    disableRoutes: false # we actually want to expose the APIs on a Gateway (optional)

  parameters:
    usagePlans:
      # ------------------------- NEW --------------------------------
      basic2:
        authPolicy:
          apiKey: {}
        displayName: api-keys based plan
        rateLimit:
          requestsPerUnit: 5
          unit: MINUTE
      # --------------------------------------------------------------
EOF

kubectl apply -f env.yaml

###############
# TRY the APIs
###############

# GET one of the /pet endpoints, on the version 1
curl -s $(glooctl proxy url)/ecommerce/v1/api/pet/1 -H "Host: ${API_FQDN}" -v
# should return 401 because no api-key was provided

#############################
# Secure Portal with OIDC
#############################
KEYCLOAK_URL=http://$(kubectl get service keycloak -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8080/auth
KEYCLOAK_TOKEN=$(curl -d "client_id=admin-cli" -d "username=admin" -d "password=admin" -d "grant_type=password" "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" | jq -r .access_token)

KEYCLOAK_ID=$(curl -s -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -H "Content-Type: application/json"  "$KEYCLOAK_URL/admin/realms/master/clients"  | jq -r '.[] | select(.redirectUris[0] == "https://'${PORTAL_FQDN}'/callback") | .id')
KEYCLOAK_CLIENT=$(curl -s -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -H "Content-Type: application/json"  "$KEYCLOAK_URL/admin/realms/master/clients"  | jq -r '.[] | select(.redirectUris[0] == "https://'${PORTAL_FQDN}'/callback") | .clientId')
KEYCLOAK_SECRET=$(curl -s -H "Authorization: bearer ${KEYCLOAK_TOKEN}" -H "Content-Type: application/json"  $KEYCLOAK_URL/admin/realms/master/clients/$KEYCLOAK_ID/client-secret | jq -r .value)

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: petstore-portal-oidc-secret
  namespace: default
data:
  client_secret: $(echo $KEYCLOAK_SECRET | base64)
EOF

cat <<EOF | kubectl apply -f -
apiVersion: portal.gloo.solo.io/v1beta1
kind: Portal
metadata:
  name: ecommerce-portal
  namespace: default
spec:
  displayName: E-commerce Portal 2
  description: The Gloo Portal for the Petstore API and much more!
  banner:
    fetchUrl: https://i.imgur.com/EXbBN1a.jpg
  favicon:
    fetchUrl: https://i.imgur.com/QQwlQG3.png
  primaryLogo:
    fetchUrl: https://i.imgur.com/hjgPMNP.png
  customStyling: {}
  staticPages: []

  domains:
  - ${PORTAL_FQDN}

  publishedEnvironments:
  - name: dev
    namespace: default

  allApisPublicViewable: true

  # ------------------- NEW ---------------------
  oidcAuth:
    clientId: "${KEYCLOAK_CLIENT}"
    clientSecret:
      name: petstore-portal-oidc-secret
      namespace: default
      key: client_secret # this is the k8s secret we have created above
    groupClaimKey: group # we will use the 'group' claim in the 'id_token' to associate the user with a group
    issuer: ${KEYCLOAK_URL}/realms/master
  # ---------------------------------------------
  portalUrlPrefix: "http://${PORTAL_FQDN}/"
EOF

cat << EOF | kubectl apply -f -
apiVersion: portal.gloo.solo.io/v1beta1
kind: Group
metadata:
  name: users
  namespace: default
spec:
  displayName: corporate users
  accessLevel:
    portals:
    - name: ecommerce-portal
      namespace: default
    apis:
    - environments:
        names:
          - dev
        namespaces:
          - '*'
      # ------------------ Enforce basic auth usage plan ----------------
      usagePlans:
        - basic2
      # -----------------------------------------------------------------
      products:
        namespaces:
        - '*'
  oidcGroup:
    groupName: users # this represents the group name in the IdP (Keycloak)
EOF

Additional context Add any other context about the problem here, e.g.

bcollard commented 2 years ago

dup of https://github.com/solo-io/dev-portal/issues/2372