Permission Service Configuration#

Goal: Deploy the Permission Service in Kubernetes. The service supports two modes: config-file mode (read-only, no database required) for quick testing, and Postgres mode for full policy management. This guide starts with config-file mode as the simplest way to get running, then covers upgrading to Postgres.

Service Ports#

Port

Protocol

Description

3000

HTTP/REST

REST API endpoint

3010

gRPC

gRPC API endpoint

Pulling the Permission Service Helm Chart#

Pull and unpack the Permission Service Helm chart from the NGC catalog. Replace {NGC_API_KEY} with your NGC API key.

# pull the chart from NGC
helm fetch https://helm.ngc.nvidia.com/nvidia/omniverse/charts/permission-service-{VERSION}.tgz --username='$oauthtoken' --password=${NGC_API_KEY}

# unpack the chart and cd into the directory
tar -xvf permission-service-{VERSION}.tgz
cd permission-service

Config File Mode#

Config-file mode is the simplest way to start the Permission Service. Policies are defined as YAML directly in Helm values and mounted into the container — no external database is required.

Setting Up Default Policies#

Create a permission-values.yaml file to configure the service in config-file mode with default policies.

# create and edit the local values file, we're using VS Code for this example
code permission-values.yaml

Add the image-pull secret and configure the database type:

image:
  pullSecrets:
    - name: ngcpull-secret

database:
  type: "config-file"

  init:
    policies:
      # Permit all requests — useful for initial testing
      - policy: "permit(principal, action, resource);"

permission-values.yaml

Policies are written in Cedar policy language. Each entry under database.init.policies must contain exactly one Cedar policy statement in the policy field. See the official Cedar documentation for the full language reference.

Note

In config-file mode, policy files are automatically reloaded when their modification time changes — no service restart is required.

Granting Administrative Permissions#

Access to the policy management and metadata APIs is gated by meta-permissions — Cedar actions on the reserved permissions service that every authenticated caller is evaluated against. If none of these actions are granted to anyone, the service starts up correctly but no caller can list, create, or delete policies, change service metadata, or inspect other users’ effective permissions.

Action

Protects

permissions:view

Reading policies through GET /v1beta/policies/ and GET /v1beta/policies/{id}, and authorizing requests whose principal differs from the caller.

permissions:edit

Creating and deleting policies through PUT /v1beta/policies/, PUT /v1beta/policies/batch/, and DELETE /v1beta/policies/{id}.

permissions:meta

Reading and writing service metadata through every endpoint under /v1beta/services/.

permissions:diagnostics

Calling POST /v1beta/diagnostics/authorize/.

Note

Meta-permissions are only enforced when authentication is enabled (auth.enabled: true). When authentication is disabled, every endpoint is reachable without a token and these actions are not evaluated.

Meta-permissions are granted the same way as any other permission — by writing Cedar policies that permit them for the principals who should have access. Seed those policies through database.init.policies alongside the application policies for the services you deploy. For example, to give the single user alice full administrative access:

database:
  init:
    policies:
      # Administrative access for one user
      - policy: 'permit(principal == Principal::"alice", action == Action::"permissions:view", resource);'
      - policy: 'permit(principal == Principal::"alice", action == Action::"permissions:edit", resource);'
      - policy: 'permit(principal == Principal::"alice", action == Action::"permissions:meta", resource);'
      - policy: 'permit(principal == Principal::"alice", action == Action::"permissions:diagnostics", resource);'

In practice, most deployments grant these actions to a group rather than a specific user, for example:

database:
  init:
    policies:
      # Administrative access for anyone whose token carries the "permission-admins" group claim
      - policy: >-
          permit(principal, action == Action::"permissions:view", resource)
          when { principal.groups.contains("permission-admins") };
      - policy: >-
          permit(principal, action == Action::"permissions:edit", resource)
          when { principal.groups.contains("permission-admins") };
      - policy: >-
          permit(principal, action == Action::"permissions:meta", resource)
          when { principal.groups.contains("permission-admins") };
      - policy: >-
          permit(principal, action == Action::"permissions:diagnostics", resource)
          when { principal.groups.contains("permission-admins") };

The principal.groups attribute used above, along with any other claim-based attributes referenced in policies, is populated from the caller’s bearer token claims at request time.

Warning

The fully-open starter policy permit(principal, action, resource); shown above grants all actions, including the four meta-permissions, to every authenticated caller. It is convenient for initial testing but should be replaced with scoped policies before exposing the service to real users.

Setting Up Service Metadata#

In addition to policies, the Helm value database.init.services seeds service metadata — the catalog of services that integrate with authorization, the actions each exposes, the resource types those actions operate on, and the per-service idClaim and per-resource-type evaluationPriority settings the service consumes at evaluation time. The chart renders the list into a ConfigMap and mounts it at /etc/permission-config/ so the service can load it on startup.

The example below registers a single storage-service with two actions and two resource types:

database:
  init:
    services:
      - name: "storage-service"
        principal:
          idClaim: "sub"
        actions:
          - "read"
          - "write"
        resourceTypes:
          - type: "object"
            evaluationPriority: "permit"
          - type: "folder"
            evaluationPriority: "permit"

Schema notes:

  • name is the service identifier; actions are referenced from Cedar policies as Action::"<name>:<action>".

  • principal.idClaim is optional. When omitted, the service falls back to the deployment-wide PRINCIPAL_ID_CLAIM (default sub). It names the bearer-token claim used as the principal id for requests targeting this service.

  • actions are plain action names; each becomes addressable as Action::"<service>:<name>" in Cedar.

  • resourceTypes[].type is the Cedar entity type used as <type>::"<id>" in policies.

  • resourceTypes[].evaluationPriority is optional and defaults to "forbid"; the alternative is "permit". It controls how competing permit/forbid policies are combined for resources of that type.

  • A service that registers actions only (no resource types) simply omits resourceTypes; Cedar policies for those actions leave the resource slot unconstrained.

In Postgres mode, seeded entries are written into the database on startup and can be changed afterwards through the metadata REST API under /v1beta/services/.

Limitations#

Config-file mode is read-only. All write operations (add_policy, update_policy, remove_policy, set_service_meta, etc.) return a “not supported” error. This mode is suitable for testing and static deployments where policies do not change at runtime.

To manage policies dynamically through the API, use Postgres mode.

Install the Permission Service (Config File Mode)#

Validate and install the chart:

# validate the chart
helm template . -f permission-values.yaml

# dry-run the install
helm upgrade --install permission-service . -f permission-values.yaml --namespace storage-apis --dry-run --debug

If everything looks good, install the Permission Service:

# install the permission service
helm upgrade --install permission-service . -f permission-values.yaml --namespace storage-apis

# validate the pod is running, this may take a few minutes to start up
kubectl get pods -n storage-apis

Verify the service is responding:

# test the permission service health endpoint
curl http://permission-service.storage-apis.svc.cluster.local:3000/health

Postgres Mode#

Postgres mode unlocks full policy management through the API: create, update, and delete policies and service metadata at runtime. Policies defined in database.init.policies are loaded as system policies on startup, and service metadata defined in database.init.services is written into the database on startup and can be changed afterwards through the metadata REST API exposed under /v1beta/services/.

Note

The meta-permissions that gate the policy management, metadata, and diagnostics APIs apply in Postgres mode as well. Seed them through database.init.policies on first deployment so that administrators can reach those APIs once the service is up; otherwise the APIs are inaccessible even with a valid bearer token.

Setting Up PostgreSQL with CloudNativePG#

CloudNativePG is a Kubernetes operator that manages the full lifecycle of PostgreSQL clusters. It provides a convenient and easy-to-set-up way to run PostgreSQL directly in a Kubernetes cluster.

Install the CloudNativePG Operator#

# add the CloudNativePG Helm repository
helm repo add cnpg https://cloudnative-pg.github.io/charts

# install the operator
helm upgrade --install cnpg-operator cnpg/cloudnative-pg \
  --namespace cnpg-system \
  --create-namespace

Verify the operator is running:

kubectl get pods -n cnpg-system

Create a PostgreSQL Cluster#

Create a Cluster custom resource to provision a PostgreSQL instance. Save the following as permission-pg-cluster.yaml:

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

  bootstrap:
    initdb:
      database: permissions
      owner: permission-service

  storage:
    size: 1Gi

Apply the resource:

kubectl apply -f permission-pg-cluster.yaml

# wait for the cluster to become ready
kubectl get cluster -n storage-apis

CloudNativePG automatically creates a secret named permission-pg-app in the same namespace containing the connection credentials (host, port, user, password, dbname). You can inspect it:

kubectl get secret permission-pg-app -n storage-apis -o yaml

Prepare Secrets for the Permission Service#

The Permission Service expects a secret containing POSTGRES_PASSWORD. You can create one from the CloudNativePG-generated credentials:

# extract the password from the CNPG-generated secret
PGPASSWORD=$(kubectl get secret permission-pg-app -n storage-apis -o jsonpath='{.data.password}' | base64 -d)

# create the permission service secret
kubectl create secret generic permission-service-secret \
  --from-literal=POSTGRES_PASSWORD="$PGPASSWORD" \
  --namespace storage-apis

Alternatively, if you manage PostgreSQL outside of CloudNativePG, create the secret directly with your database password:

kubectl create secret generic permission-service-secret \
  --from-literal=POSTGRES_PASSWORD="{YOUR_POSTGRES_PASSWORD}" \
  --namespace storage-apis

CloudNativePG Alternatives#

Any PostgreSQL-compatible database works with the Permission Service. If you prefer not to run PostgreSQL inside the cluster, consider a cloud-managed service:

When using a managed service, provide the connection details (host, port, user, dbname, sslMode) in the Helm values and create a Kubernetes secret with the password as described above.

Deploy in Postgres Mode#

Create or update permission-values.yaml with Postgres settings. Replace the host with the service name of your PostgreSQL instance (CloudNativePG creates a service named permission-pg-rw by default):

image:
  pullSecrets:
    - name: ngcpull-secret

database:
  type: "postgres"

  init:
    policies:
      # System policies loaded on startup — optional
      - policy: "permit(principal, action, resource);"

  postgres:
    host: "permission-pg-rw"
    port: 5432
    user: "permission-service"
    dbname: "permissions"
    sslMode: ""
    connections: 16
    workerThreads: 32

secret:
  name: permission-service-secret
  create: false

permission-values.yaml

Note

Set secret.create: false when you have already created the permission-service-secret manually. If secret.create is true (the default), the chart creates the secret from database.postgres.password in the values file.

Validate and install:

# validate the chart
helm template . -f permission-values.yaml

# dry-run the install
helm upgrade --install permission-service . -f permission-values.yaml --namespace storage-apis --dry-run --debug

If everything looks good, install the Permission Service:

# install the permission service
helm upgrade --install permission-service . -f permission-values.yaml --namespace storage-apis

# validate the pod is running
kubectl get pods -n storage-apis

Verify the service is responding:

# test the permission service health endpoint
curl http://permission-service.storage-apis.svc.cluster.local:3000/health

Ingress Access#

To expose the Permission Service outside the cluster, enable the httpProxy section in your values file. The chart creates Contour HTTPProxy resources for both REST and gRPC endpoints.

httpProxy:
  enabled: true
  fqdn:
    host: "permissions"
    domain: "{DNS_URL}"

permission-values.yaml

Redeploy the Permission Service with the updated values:

helm upgrade --install permission-service . -f permission-values.yaml --namespace storage-apis

Verify the HTTPProxy resources were created:

kubectl get httpproxy -n storage-apis

For full details on setting up Contour as a load balancer, configuring DNS records, and enabling TLS, see the Ingress Configuration guide.

Clean Up#

To clean up: uninstall the Helm release, delete secrets, and remove the namespace.

# uninstall the permission service
helm uninstall permission-service -n storage-apis

# delete the secrets
kubectl delete secret permission-service-secret -n storage-apis

# if using CloudNativePG, delete the cluster
kubectl delete cluster permission-pg -n storage-apis

# delete the namespace
kubectl delete namespace storage-apis

# validate the namespace was deleted
kubectl get namespace storage-apis

References#

What’s Next?#

Continue your deployment by configuring the Discovery Service so clients can find and connect to all services via a single endpoint.

See the official Cedar documentation for the complete language specification.