User Info Service Configuration#

Overview#

The User Info Service is a centralized, read-only identity service for Omniverse Storage APIs. It gives every other service a single place to answer questions such as “who is this user?”, “what groups do they belong to?”, and “which user or group matches this name?” — without each service integrating with the organization’s identity provider on its own.

Deployment#

The following deployment guide is maintained in the User Info Service repository and included here so it stays in sync with the service implementation.

Service Ports#

Port

Protocol

Description

8080

HTTP/REST

REST API endpoint

50051

gRPC

gRPC API endpoint

The health and readiness endpoints are available on the REST port at /healthz and /readyz.

Prerequisites#

Before deploying the service, prepare:

  • A Kubernetes cluster with Helm 3 and kubectl.

  • An NGC API key that can pull the Helm chart and container images.

  • A Microsoft Entra app registration with a tenant ID, client ID, and either a client secret or Azure Workload Identity federation.

  • Microsoft Graph application permissions with tenant administrator consent:

    • User.Read.All

    • Group.Read.All

    • GroupMember.Read.All when group membership queries or synchronization are enabled.

  • A PostgreSQL database for the recommended synchronized directory deployment.

The service uses the OAuth 2.0 client credentials flow and requests https://graph.microsoft.com/.default. Configure application permissions, rather than delegated permissions, because no signed-in user participates in this flow.

Pulling the User Info Service Helm Chart#

Pull and unpack the User Info Service Helm chart from the NGC catalog. Replace {NGC_API_KEY} and {VERSION} with the values for your deployment.

helm fetch https://helm.ngc.nvidia.com/nvidia/omniverse/charts/microsoft-graph-userinfo-service-{VERSION}.tgz --username='$oauthtoken' --password=${NGC_API_KEY}
tar -xvf microsoft-graph-userinfo-service-{VERSION}.tgz
cd microsoft-graph-userinfo-service

Creating the Namespace#

This guide uses the storage-apis namespace.

kubectl create namespace storage-apis

Ensure that an image-pull secret named ngcpull-secret exists in this namespace before installing the chart.

Preparing Microsoft Entra Credentials#

For client-secret authentication, create a Kubernetes Secret containing the app registration’s client secret:

kubectl create secret generic microsoft-graph-userinfo-service-az-credentials \
  --from-literal=client-secret="{AZURE_CLIENT_SECRET}" \
  --namespace storage-apis

Keep the tenant ID and client ID for the Helm values file. The chart reads the client secret from the Secret and does not require it to be stored in the values file.

For Azure Workload Identity, set providers.entra.authMode to workload-identity, annotate the chart’s service account for the federated identity, and omit the client-secret Secret. The workload identity setup itself depends on the Kubernetes cluster and Entra tenant configuration.

Setting Up PostgreSQL#

The synchronized directory deployment requires a PostgreSQL snapshot store shared by the writer and all readers. The writer synchronizes directory data into the store, and readers periodically reload the stored snapshot into memory.

Alternative: In-Cluster PostgreSQL with CloudNativePG#

CloudNativePG is a Kubernetes operator that manages PostgreSQL clusters.

Install the CloudNativePG Operator#
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm upgrade --install cnpg-operator cnpg/cloudnative-pg \
  --namespace cnpg-system \
  --create-namespace
kubectl get pods -n cnpg-system
Create a PostgreSQL Cluster#

Save the following manifest as userinfo-pg-cluster.yaml:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: userinfo-pg
  namespace: storage-apis
spec:
  instances: 1

  bootstrap:
    initdb:
      database: graph-userinfo
      owner: microsoft_graph_userinfo_service

  storage:
    size: 10Gi

Apply the manifest and wait for the cluster to become ready:

kubectl apply -f userinfo-pg-cluster.yaml
kubectl get cluster userinfo-pg -n storage-apis

CloudNativePG creates the userinfo-pg-app Secret containing the generated application credentials and the userinfo-pg-rw Service for read-write database connections.

Preparing the PostgreSQL Secret#

The Helm chart expects a Secret key containing the PostgreSQL password. Copy the generated password into the Secret name and key used by the chart:

PGPASSWORD=$(kubectl get secret userinfo-pg-app -n storage-apis -o jsonpath='{.data.password}' | base64 -d)
kubectl create secret generic microsoft-graph-userinfo-service-pg-credentials \
  --from-literal=password="$PGPASSWORD" \
  --namespace storage-apis

If PostgreSQL is managed outside CloudNativePG, create the Secret directly:

kubectl create secret generic microsoft-graph-userinfo-service-pg-credentials \
  --from-literal=password="{POSTGRES_PASSWORD}" \
  --namespace storage-apis

Any PostgreSQL-compatible deployment can be used. Set its host, port, database, user, and TLS mode in the Helm values.

Synchronized Directory Deployment#

Create userinfo-values.yaml with the Entra and PostgreSQL configuration. Replace the tenant and client IDs with the values from the Entra app registration.

image:
  pullSecrets:
    - name: ngcpull-secret

providers:
  entra:
    tenantId: "{AZURE_TENANT_ID}"
    clientId: "{AZURE_CLIENT_ID}"
    authMode: "client-secret"
    clientSecret:
      secretRef:
        name: "microsoft-graph-userinfo-service-az-credentials"
        key: "client-secret"

  cache:
    enabled: true
    users:
      enabled: true
    groups:
      enabled: true
    memberships:
      enabled: false
    syncInterval: "5m"

    postgres:
      enabled: true
      host: "userinfo-pg-rw"
      port: 5432
      dbname: "graph-userinfo"
      user: "microsoft_graph_userinfo_service"
      password:
        secretRef:
          name: "microsoft-graph-userinfo-service-pg-credentials"
          key: "password"
      sslMode: "disable"

writer:
  enabled: true

reader:
  enabled: true
  replicaCount: 1
  reloadInterval: "60s"

userinfo-values.yaml

The writer always runs as one replica. Scale read capacity with reader.replicaCount, or set autoscaling.enabled to hand the reader count to an externally managed autoscaler.

Membership synchronization is disabled in this example because it can substantially increase initial synchronization time and memory use for large directories. Enable it when the membership endpoints must be served from the local snapshot:

providers:
  cache:
    memberships:
      enabled: true

Increase writer.resources, reader.resources, and the readiness probe budget when synchronizing a large directory.

Install the Synchronized Directory Deployment#

Validate and dry-run the chart:

helm template . -f userinfo-values.yaml
helm upgrade --install userinfo-service . \
  -f userinfo-values.yaml \
  --namespace storage-apis \
  --dry-run \
  --debug

Install the service:

helm upgrade --install userinfo-service . \
  -f userinfo-values.yaml \
  --namespace storage-apis
kubectl get pods -n storage-apis

The writer becomes ready after it can synchronize and persist directory data. Readers become ready after loading a snapshot from PostgreSQL. The Kubernetes Service routes API traffic only to reader pods.

Reader-Only Live Graph Mode#

For initial testing without PostgreSQL, disable the writer and cache. The reader sends each directory request to Microsoft Graph.

image:
  pullSecrets:
    - name: ngcpull-secret

providers:
  entra:
    tenantId: "{AZURE_TENANT_ID}"
    clientId: "{AZURE_CLIENT_ID}"
    authMode: "client-secret"
    clientSecret:
      secretRef:
        name: "microsoft-graph-userinfo-service-az-credentials"
        key: "client-secret"

  cache:
    enabled: false
    postgres:
      enabled: false

writer:
  enabled: false

reader:
  enabled: true

userinfo-values.yaml

Install this mode with the same validation and helm upgrade --install commands used for the synchronized directory deployment. This mode removes the PostgreSQL dependency but makes API availability and latency dependent on Microsoft Graph.

Verifying the Deployment#

Check the workloads and probe the in-cluster service:

kubectl get deployments,pods,service -n storage-apis
kubectl run userinfo-health-check \
  --rm \
  --restart=Never \
  --namespace storage-apis \
  --image=curlimages/curl \
  -- http://userinfo-service.storage-apis.svc.cluster.local:8080/healthz
kubectl run userinfo-readiness-check \
  --rm \
  --restart=Never \
  --namespace storage-apis \
  --image=curlimages/curl \
  -- http://userinfo-service.storage-apis.svc.cluster.local:8080/readyz

A successful /healthz response confirms that the process is running. A successful /readyz response confirms that the reader can serve directory data.

Running the Helm Smoke Test#

The chart includes an optional Helm test that exercises the REST and gRPC APIs. Enable it in userinfo-values.yaml:

smokeTest:
  enabled: true

After upgrading the release, run:

helm test userinfo-service -n storage-apis --logs

If an authentication gateway protects the service, also configure serviceIdentity with credentials that produce a token for the User Info Service audience.

Permission Service Integration#

Authorization through the Permission Service is disabled by default. To enable it, configure the Permission Service base URL and the service name used in authorization requests:

permission:
  enabled: true
  url: "http://permission-service.storage-apis.svc.cluster.local:3000/"
  serviceName: "userinfo"
  cacheTtl: "5m"
  cacheMaxCapacity: 10000

The Permission Service must contain policies and service metadata for the configured service name before this integration is enabled. If the Permission Service cannot evaluate a request, the User Info Service fails closed. Health, readiness, and current-user requests are exempt from permission checks.

Ingress Access#

Enable the chart’s httpProxy configuration to expose the REST API through Contour HTTPProxy:

httpProxy:
  enabled: true
  fqdn:
    host: "userinfo"
    domain: "{DNS_DOMAIN}"
  tls:
    enabled: true
    secretName: "{TLS_SECRET_NAME}"

Upgrade the release and verify the HTTPProxy:

helm upgrade --install userinfo-service . \
  -f userinfo-values.yaml \
  --namespace storage-apis
kubectl get httpproxy -n storage-apis

The chart’s HTTPProxy routes the REST port only. Exposing gRPC outside the cluster requires a separate ingress or load-balancer route that supports HTTP/2 and targets port 50051.

Clean Up#

Remove only resources created by this guide:

helm uninstall userinfo-service -n storage-apis
kubectl delete secret microsoft-graph-userinfo-service-az-credentials -n storage-apis
kubectl delete secret microsoft-graph-userinfo-service-pg-credentials -n storage-apis
kubectl delete cluster userinfo-pg -n storage-apis
kubectl delete namespace storage-apis

References#

What’s Next?#

After the deployment is ready, configure client authentication and authorization, then validate the REST and gRPC interfaces against your directory data.

For more information about the User Info Service, read the following documentation:

User Info Service