diff --git a/.env.sample b/.env.sample index 60e96060..c41e15b3 100644 --- a/.env.sample +++ b/.env.sample @@ -88,6 +88,8 @@ HYDRA_VERSION=v25.4.0-distroless # Ory Keto Configuration KETO_VERSION=v25.4.0-distroless +KETO_READ_URL=http://localhost:4466 +KETO_WRITE_URL=http://localhost:4467 # KETO_READ_PORT=4466 # Internal only # KETO_WRITE_PORT=4467 # Internal only diff --git a/compose.ory.yaml b/compose.ory.yaml index 5e9d5f9d..c3412eb0 100644 --- a/compose.ory.yaml +++ b/compose.ory.yaml @@ -121,6 +121,9 @@ services: keto: image: oryd/keto:${KETO_VERSION:-v25.4.0} container_name: ory_keto + ports: + - "4466:4466" # Read API + - "4467:4467" # Write API environment: - DSN=postgres://${ORY_POSTGRES_USER}:${ORY_POSTGRES_PASSWORD}@postgres_ory:5432/${KETO_DB:-ory_keto}?sslmode=disable&max_conns=20 volumes: diff --git a/docker-compose.yaml b/docker-compose.yaml index a96bb77a..8fc9d634 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -23,6 +23,8 @@ services: - KRATOS_ADMIN_URL=${KRATOS_ADMIN_URL:-http://kratos:4434} - HYDRA_ADMIN_URL=${HYDRA_ADMIN_URL:-http://hydra:4445} - HYDRA_PUBLIC_URL=${HYDRA_PUBLIC_URL:-http://hydra:4444} + - KETO_READ_URL=${KETO_READ_URL:-http://keto:4466} + - KETO_WRITE_URL=${KETO_WRITE_URL:-http://keto:4467} - DB_HOST=postgres - CLICKHOUSE_HOST=clickhouse - CLICKHOUSE_PORT=${CLICKHOUSE_PORT_NATIVE:-9000} diff --git a/docker/ory/keto/namespaces.ts b/docker/ory/keto/namespaces.ts index 60e349aa..dc7268e1 100644 --- a/docker/ory/keto/namespaces.ts +++ b/docker/ory/keto/namespaces.ts @@ -2,6 +2,12 @@ import { Namespace, Subject, Context, SubjectSet } from "@ory/keto-definitions" class User implements Namespace {} +class TenantGroup implements Namespace { + related: { + admins: User[] + } +} + class UserGroup implements Namespace { related: { members: User[] @@ -19,17 +25,20 @@ class Tenant implements Namespace { admins: User[] members: User[] parent: Tenant[] + parent_group: TenantGroup[] } permits = { view: (ctx: Context): boolean => this.related.members.includes(ctx.subject) || this.related.admins.includes(ctx.subject) || - this.related.parent.traverse((p) => p.permits.view(ctx)), + this.related.parent.traverse((p) => p.permits.view(ctx)) || + this.related.parent_group.traverse((g) => g.related.admins.includes(ctx.subject)), manage: (ctx: Context): boolean => this.related.admins.includes(ctx.subject) || - this.related.parent.traverse((p) => p.permits.manage(ctx)), + this.related.parent.traverse((p) => p.permits.manage(ctx)) || + this.related.parent_group.traverse((g) => g.related.admins.includes(ctx.subject)), create_subtenant: (ctx: Context): boolean => this.permits.manage(ctx)