ory / docs

The Ory documentation
https://www.ory.sh/docs
Apache License 2.0
135 stars 374 forks source link

Userset Rewrite Example Does Not Work With Keto Run In Docker #1097

Closed snasphysicist closed 1 year ago

snasphysicist commented 1 year ago

Preflight checklist

Describe the bug

Trying to configure namespaces from https://www.ory.sh/docs/keto/guides/userset-rewrites using the latest Docker instance of Keto will fail with

ime=2022-11-24T10:46:21Z level=error msg=Failed to parse OPL config files at target file:///app/namespaces/. audience=application error=map[message:error from 4:49 to 4:49: fatal: at "@ory/keto-namespace-types\"\n": unclosed string literal

   2 | 
   3 | import { Namespace, SubjectSet, Context } from "@ory/keto-namespace-types"
                                                        ^                        
   3 | 

Reproducing the bug

In addition to the below configuration, you'll need this namespace ts file (based on https://www.ory.sh/docs/keto/guides/userset-rewrites)

// Copyright © 2022 Ory Corp
// SPDX-License-Identifier: Apache-2.0

import { Namespace, SubjectSet, Context } from "@ory/keto-namespace-types"

Then run the following docker commands, replacing the paths containing /home/ssmith/Scratchpad/keto/local/* with your own directories containing the configuration as keto-configuration-local.yaml (for /cfg/) and the namespace ts as keto-ts-configuration-local.ts (for ns).

docker run -p 5432:5432 --name="policy-db" --network="policy" --env POSTGRES_USER="admin" --env POSTGRES_PASSWORD="admin" --env POSTGRES_DB="policy" -d postgres:12

export DSN="postgresql://admin:admin@policy-db:5432/policy?sslmode=disable"

docker run --network="policy" --env DSN="$DSN" \
  -v /home/ssmith/Scratchpad/keto/local/cfg/:/app/configuration/ \
  -v /home/ssmith/Scratchpad/keto/local/ns/:/app/namespaces/ \
  ubuntu /bin/bash -c 'find /app/configuration'

docker run --name="keto-migrate-up" --network="policy" --env DSN="$DSN" \
  -v /home/ssmith/Scratchpad/keto/local/cfg/:/app/configuration/ \
  -v /home/ssmith/Scratchpad/keto/local/ns/:/app/namespaces/ \
  oryd/keto --config /app/configuration/keto-configuration-local.yaml \
  migrate up --yes

docker run --network="policy" --env DSN="$DSN" \
  -v /home/ssmith/Scratchpad/keto/local/cfg/:/app/configuration/ \
  -v /home/ssmith/Scratchpad/keto/local/ns/:/app/namespaces/ \
  oryd/keto --config /app/configuration/keto-configuration-local.yaml \
  namespace validate

docker run --name="keto-serve" --network="policy" --env DSN="$DSN" \
  -v /home/ssmith/Scratchpad/keto/local/cfg/:/app/configuration/ \
  -v /home/ssmith/Scratchpad/keto/local/ns/:/app/namespaces/ \
  -p 4466:4466 -p 4467:4467 \
  oryd/keto --config /app/configuration/keto-configuration-local.yaml \
  serve

Finally, make requests to this Keto server using this Python script:

from dataclasses import dataclass
from typing import Optional
import json
import requests

host = "localhost"
read_port = "4466"
write_port = "4467"

def write_url(path: str) -> str:
    return f"http://{host}:{write_port}/{path}"

def read_url(path: str) -> str:
    return f"http://{host}:{read_port}/{path}"

@dataclass
class SubjectSet:
    namespace: str
    object: str
    relation: Optional[str] = None

def create_relation_tuple(
    namespace: str,
    object: str,
    relation: str,
    subject_id: str = None,
    subject_set: SubjectSet = None
):
    payload = {
        "namespace": namespace,
        "object": object,
        "relation": relation,
    }
    if subject_id is not None:
        payload["subject_id"] = subject_id
    if subject_set is not None:
        payload["subject_set"] = {
            "namespace": subject_set.namespace,
            "object": subject_set.object,
        }
        if subject_set.relation is not None:
            payload["subject_set"]["relation"] = subject_set.relation
    url = write_url("admin/relation-tuples")
    r = requests.put(url, data=json.dumps(payload))
    r.raise_for_status()

def check_relation_tuple(
    namespace: str,
    object: str,
    relation: str,
    subject_set: SubjectSet,
) -> bool:
    payload = {
        "namespace": namespace,
        "object": object,
        "relation": relation,
        "subject_set": {
            "namespace": subject_set.namespace,
            "object": subject_set.object,
            "relation": subject_set.relation,
        },
    }
    url = read_url("relation-tuples/check")
    r = requests.post(url, data=json.dumps(payload))
    if r.status_code != 200 and r.status_code != 403:
        r.raise_for_status()
    return r.json()

create_relation_tuple("Group", "developer", "members", subject_id="Patrik")
create_relation_tuple("Group", "developer", "members", subject_set=SubjectSet(
    namespace="User",
    object="Patrik",
))
create_relation_tuple("Group", "developer", "members", subject_set=SubjectSet(
    namespace="User",
    object="Henning",
))
create_relation_tuple("Folder", "keto/", "viewers", subject_set=SubjectSet(
    namespace="Group",
    object="developer",
    relation="members"
))
create_relation_tuple("File", "keto/README.md", "parents", subject_set=SubjectSet(
    namespace="Folder",
    object="keto/",
))
create_relation_tuple("Folder", "keto/src/", "parents", subject_set=SubjectSet(
    namespace="Folder",
    object="keto/",
))
create_relation_tuple("File", "keto/src/main.go", "parents", subject_set=SubjectSet(
    namespace="Folder",
    object="keto/src/",
))
create_relation_tuple("File", "private", "owners", subject_set=SubjectSet(
    namespace="User",
    object="Henning",
))

print(check_relation_tuple("File", "keto/src/main.go", "view", SubjectSet(
    namespace="User",
    object="Patrik",
)))
print(check_relation_tuple("File", "private", "view", SubjectSet(
    namespace="User",
    object="Patrik",
)))

The requests will fail with 404s (namespace not found) and you will see the log messages below among Keto's log entries.

Relevant log output

ime=2022-11-24T10:46:21Z level=error msg=Failed to parse OPL config files at target file:///app/namespaces/. audience=application error=map[message:error from 4:49 to 4:49: fatal: at "@ory/keto-namespace-types\"\n": unclosed string literal

   2 | 
   3 | import { Namespace, SubjectSet, Context } from "@ory/keto-namespace-types"
                                                        ^                        
   3 |

Relevant configuration

## ORY Keto Configuration
#

# dsn: memory

serve:

  write:
    host: 0.0.0.0
    cors:
      allowed_origins:
        - "*"
      allowed_methods:
        - GET
        - POST
        - PUT
        - PATCH
        - DELETE
      allowed_headers:
        - "Authorization"
        - "Content-Type"
      exposed_headers:
        - "Content-Type"
      allow_credentials: true
      max_age: 0
      debug: false
      enabled: true
    port: 4467

  metrics:
    host: 0.0.0.0
    cors:
      allowed_origins:
        - "*"
      allowed_methods:
        - GET
        - POST
        - PUT
        - PATCH
        - DELETE
      allowed_headers:
        - "Authorization"
        - "Content-Type"
      exposed_headers:
        - "Content-Type"
      allow_credentials: true
      max_age: 0
      debug: false
      enabled: true
    port: 8081

  # opl:
  #   host: 0.0.0.0
  #   cors:
  #     allowed_origins:
  #       - "*"
  #     allowed_methods:
  #       - GET
  #       - POST
  #       - PUT
  #       - PATCH
  #       - DELETE
  #     allowed_headers:
  #       - "Authorization"
  #       - "Content-Type"
  #     exposed_headers:
  #       - "Content-Type"
  #     allow_credentials: true
  #     max_age: 0
  #     debug: false
  #     enabled: true
  #   port: 8082

  read:
    host: 0.0.0.0
    cors:
      allowed_origins:
        - "*"
      allowed_methods:
        - GET
        - POST
        - PUT
        - PATCH
        - DELETE
      allowed_headers:
        - "Authorization"
        - "Content-Type"
      exposed_headers:
        - "Content-Type"
      allow_credentials: true
      max_age: 0
      debug: false
      enabled: true
    port: 4466

# One of:
# - cpu
# - mem
# - ""
profiling: ""

log:
  # One of:
  # - json
  # - json_pretty
  # - gelf
  # - text
  format: text

  leak_sensitive_values: false
  # redaction_text: ""

  # One of:
  # - panic
  # - fatal
  # - error
  # - warn
  # - info
  # - debug
  # - trace
  level: trace

## tracing ##
#
# Configure distributed tracing.
#
# tracing:
#   service_name: policy-engine
#   providers:
#     jaeger:
#       propagation: jaeger
#       max_tag_value_length: 0
#       sampling:
#         type: const
#         value: 1
#         server_url: http://localhost:5778/sampling
#       local_agent_address: 127.0.0.1:6831

  # One of:
  # - jaeger
  # - zipkin
  # - datadog
  # - elastic-apm
  # - instana
  # provider: "jaeger"

# Namespace configuration or it's location.
#
# Default value: file://./keto_namespaces
namespaces:
  location: file:///app/namespaces/

# clients:
#   http:
#     disallow_private_ip_ranges: false

## The Keto version this config is written for. ##
#
# SemVer according to https://semver.org/ prefixed with `v` as in our releases.
#
# Set this value using environment variables on
# - Linux/macOS:
#    $ export VERSION=<value>
# - Windows Command Line (CMD):
#    > set VERSION=<value>
#
version: v0.10.0-alpha.0

# $schema: http://a.aaa

Version

Version: v0.10.0-alpha.0 / Build Commit: 52259a30d0be0257f1bb7ef591ae769808450230 / Build Timestamp: 2022-09-27T13:05:05Z

On which operating system are you observing this issue?

Linux

In which environment are you deploying?

Docker

Additional Context

If I remove the import line it seems to work, so I think the example ts file in https://www.ory.sh/docs/keto/guides/userset-rewrites just needs updating, however I cannot find the right place in this repository to update it.

znorris commented 1 year ago

@snasphysicist This bug appears to be fixed. I'm able to take the current example at https://www.ory.sh/docs/keto/guides/userset-rewrites and load it with image oryd/keto:v0.11.1-alpha.0 without error.

// namespaces.keto.ts
import { Namespace, SubjectSet, Context } from "@ory/keto-namespace-types"

class User implements Namespace {
  related: {
    manager: User[]
  }
}

class Group implements Namespace {
  related: {
    members: (User | Group)[]
  }
}

class Folder implements Namespace {
  related: {
    parents: (File | Folder)[]
    viewers: SubjectSet<Group, "members">[]
  }

  permits = {
    view: (ctx: Context): boolean =>
      this.related.viewers.includes(ctx.subject) ||
      this.related.parents.traverse((p) => p.permits.view(ctx)),
  }
}

class File implements Namespace {
  related: {
    parents: (File | Folder)[]
    viewers: (User | SubjectSet<Group, "members">)[]
    owners: (User | SubjectSet<Group, "members">)[]
  }

  // Some comment
  permits = {
    view: (ctx: Context): boolean =>
      this.related.parents.traverse((p) => p.permits.view(ctx)) ||
      this.related.viewers.includes(ctx.subject) ||
      this.related.owners.includes(ctx.subject),

    edit: (ctx: Context) => this.related.owners.includes(ctx.subject),
  }
}
vinckr commented 1 year ago

Thanks for the heads up Zach! Closing this as resolved for now, but feel free to chime up if you are still having trouble @snasphysicist