Class: Cerbos::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/cerbos/client.rb

Overview

A client for interacting with the Cerbos policy decision point (PDP) server over gRPC.

An instance of the client may be shared between threads.

Due to a limitation in the underlying grpc gem, creating a client instance before a process fork is only (experimentally) supported on Linux and requires you to

  • have at least v1.57.0 of the grpc gem installed,
  • set the GRPC_ENABLE_FORK_SUPPORT environment variable to 1,
  • call GRPC.prefork before forking,
  • call GRPC.postfork_parent in the parent process after forking, and
  • call GRPC.postfork_child in the child processes after forking.

Otherwise, if your application runs on a forking webserver (for example, Puma in clustered mode), then you'll need to ensure that you only create client instances in the child (worker) processes.

Instance Method Summary collapse

Constructor Details

#initialize(target, tls:, grpc_channel_args: {}, grpc_metadata: {}, on_validation_error: :return, playground_instance: nil, timeout: nil) ⇒ Client

Create a client for interacting with the Cerbos PDP server over gRPC.

Examples:

Connect via TCP with no encryption

client = Cerbos::Client.new("localhost:3593", tls: false)

Connect via a Unix socket with no encryption

client = Cerbos::Client.new("unix:/var/run/cerbos.grpc.sock", tls: false)

Connect to the hosted demo PDP to experiment in the playground

client = Cerbos::Client.new("demo-pdp.cerbos.cloud", tls: Cerbos::TLS.new, playground_instance: "gE623b0180QlsG5a4QIN6UOZ6f3iSFW2")

Raise an error when input fails schema validation

client = Cerbos::Client.new("localhost:3593", tls: false, on_validation_error: :raise)

Invoke a callback when input fails schema validation

client = Cerbos::Client.new("localhost:3593", tls: false, on_validation_error: ->(validation_errors) { do_something_with validation_errors })

Parameters:

  • target (String)

    Cerbos PDP server address ("host", "host:port", or "unix:/path/to/socket").

  • tls (TLS, MutualTLS, false)

    gRPC connection encryption settings (false for plaintext).

  • grpc_channel_args (Hash{String, Symbol => String, Integer}) (defaults to: {})

    low-level settings for the gRPC channel (see available keys in the gRPC documentation).

  • grpc_metadata (Hash{String, Symbol => String, Array<String>}) (defaults to: {})

    gRPC metadata (a.k.a. HTTP headers) to add to every request to the PDP.

  • on_validation_error (:return, :raise, #call) (defaults to: :return)

    action to take when input fails schema validation (:return to return the validation errors in the response, :raise to raise Error::ValidationFailed, or a callback to invoke).

  • playground_instance (String, nil) (defaults to: nil)

    identifier of the playground instance to use when prototyping against the hosted demo PDP.

  • timeout (Numeric, nil) (defaults to: nil)

    timeout for gRPC calls, in seconds (nil to never time out).



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/cerbos/client.rb', line 41

def initialize(target, tls:, grpc_channel_args: {}, grpc_metadata: {}, on_validation_error: :return, playground_instance: nil, timeout: nil)
  @on_validation_error = on_validation_error

  Error.handle do
    credentials = tls ? tls.to_channel_credentials : :this_channel_is_insecure

    unless playground_instance.nil?
      credentials = credentials.compose(GRPC::Core::CallCredentials.new(->(*) { {"playground-instance" => playground_instance} }))
    end

    @cerbos_service = Service.new(
      stub: Protobuf::Cerbos::Svc::V1::CerbosService::Stub,
      target:,
      credentials:,
      grpc_channel_args:,
      grpc_metadata:,
      timeout:
    )

    @health_service = Service.new(
      stub: Protobuf::Grpc::Health::V1::Health::Stub,
      target:,
      credentials:,
      grpc_channel_args:,
      grpc_metadata:,
      timeout:
    )
  end
end

Instance Method Details

#allow?(principal:, resource:, action:, aux_data: nil, request_id: SecureRandom.uuid, request_context: nil, grpc_metadata: {}) ⇒ Boolean

Check if a principal is allowed to perform an action on a resource.

Examples:

client.allow?(
  principal: {id: "user@example.com", roles: ["USER"]},
  resource: {kind: "document", id: "1"},
  action: "view"
) # => true

Parameters:

  • principal (Input::Principal, Hash)

    the principal to check.

  • resource (Input::Resource, Hash)

    the resource to check.

  • action (String)

    the action to check.

  • aux_data (Input::AuxData, Hash, nil) (defaults to: nil)

    auxiliary data.

  • request_id (String) (defaults to: SecureRandom.uuid)

    identifier for tracing the request.

  • request_context (Input::RequestContext, Hash, nil) (defaults to: nil)

    additional metadata to add to the request (requires a policy decision point server running Cerbos v0.51+).

  • grpc_metadata (Hash{String, Symbol => String, Array<String>}) (defaults to: {})

    gRPC metadata (a.k.a. HTTP headers) to add to the request.

Returns:

  • (Boolean)


89
90
91
92
93
94
95
96
97
98
99
# File 'lib/cerbos/client.rb', line 89

def allow?(principal:, resource:, action:, aux_data: nil, request_id: SecureRandom.uuid, request_context: nil, grpc_metadata: {})
  check_resource(
    principal: principal,
    resource: resource,
    actions: [action],
    aux_data: aux_data,
    request_id: request_id,
    request_context: request_context,
    grpc_metadata: 
  ).allow?(action)
end

#check_health(service: "cerbos.svc.v1.CerbosService", grpc_metadata: {}) ⇒ Output::HealthCheck

Check the health of a service provided by the policy decision point server.

Examples:

cerbos_api = client.check_health
cerbos_api.status # => :SERVING

admin_api = client.check_health(service: "cerbos.svc.v1.CerbosAdminService")
admin_api.status # => :DISABLED

Parameters:

  • service ("cerbos.svc.v1.CerbosService", "cerbos.svc.v1.CerbosAdminService") (defaults to: "cerbos.svc.v1.CerbosService")

    the service to check.

  • grpc_metadata (Hash{String, Symbol => String, Array<String>}) (defaults to: {})

    gRPC metadata (a.k.a. HTTP headers) to add to the request.

Returns:



114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/cerbos/client.rb', line 114

def check_health(service: "cerbos.svc.v1.CerbosService", grpc_metadata: {})
  Error.handle do
    request = Protobuf::Grpc::Health::V1::HealthCheckRequest.new(service: service)

    response = @health_service.call(:check, request, )

    Output::HealthCheck.from_protobuf(response)
  end
rescue Error::NotFound
  return Output::HealthCheck.new(status: :DISABLED) if service == "cerbos.svc.v1.CerbosAdminService"

  raise
end

#check_resource(principal:, resource:, actions:, aux_data: nil, include_metadata: false, request_id: SecureRandom.uuid, request_context: nil, grpc_metadata: {}) ⇒ Output::CheckResources::Result

Check a principal's permissions on a resource.

Examples:

decision = client.check_resource(
  principal: {id: "user@example.com", roles: ["USER"]},
  resource: {kind: "document", id: "1"},
  actions: ["view", "edit"]
)

decision.allow?("view") # => true

Parameters:

  • principal (Input::Principal, Hash)

    the principal to check.

  • resource (Input::Resource, Hash)

    the resource to check.

  • actions (Array<String>)

    the actions to check.

  • aux_data (Input::AuxData, Hash, nil) (defaults to: nil)

    auxiliary data.

  • include_metadata (Boolean) (defaults to: false)

    true to include additional metadata (Output::CheckResources::Result::Metadata) in the results.

  • request_id (String) (defaults to: SecureRandom.uuid)

    identifier for tracing the request.

  • request_context (Input::RequestContext, Hash, nil) (defaults to: nil)

    additional metadata to add to the request (requires a policy decision point server running Cerbos v0.51+).

  • grpc_metadata (Hash{String, Symbol => String, Array<String>}) (defaults to: {})

    gRPC metadata (a.k.a. HTTP headers) to add to the request.

Returns:



149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/cerbos/client.rb', line 149

def check_resource(principal:, resource:, actions:, aux_data: nil, include_metadata: false, request_id: SecureRandom.uuid, request_context: nil, grpc_metadata: {})
  Error.handle do
    check_resources(
      principal: principal,
      resources: [Input::ResourceCheck.new(resource: resource, actions: actions)],
      aux_data: aux_data,
      include_metadata: ,
      request_id: request_id,
      request_context: request_context,
      grpc_metadata: 
    ).find_result(resource)
  end
end

#check_resources(principal:, resources:, aux_data: nil, include_metadata: false, request_id: SecureRandom.uuid, request_context: nil, grpc_metadata: {}) ⇒ Output::CheckResources

Check a principal's permissions on a set of resources.

Examples:

decision = client.check_resources(
  principal: {id: "user@example.com", roles: ["USER"]},
  resources: [
    {
      resource: {kind: "document", id: "1"},
      actions: ["view", "edit"]
    },
    {
      resource: {kind: "image", id: "1"},
      actions: ["delete"]
    }
  ]
)

decision.allow?(resource: {kind: "document", id: "1"}, action: "view") # => true

Parameters:

  • principal (Input::Principal, Hash)

    the principal to check.

  • resources (Array<Input::ResourceCheck, Hash>)

    the resources and actions to check.

  • aux_data (Input::AuxData, Hash, nil) (defaults to: nil)

    auxiliary data.

  • include_metadata (Boolean) (defaults to: false)

    true to include additional metadata (Output::CheckResources::Result::Metadata) in the results.

  • request_id (String) (defaults to: SecureRandom.uuid)

    identifier for tracing the request.

  • request_context (Input::RequestContext, Hash, nil) (defaults to: nil)

    additional metadata to add to the request (requires a policy decision point server running Cerbos v0.51+).

  • grpc_metadata (Hash{String, Symbol => String, Array<String>}) (defaults to: {})

    gRPC metadata (a.k.a. HTTP headers) to add to the request.

Returns:



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/cerbos/client.rb', line 191

def check_resources(principal:, resources:, aux_data: nil, include_metadata: false, request_id: SecureRandom.uuid, request_context: nil, grpc_metadata: {})
  Error.handle do
    request = Protobuf::Cerbos::Request::V1::CheckResourcesRequest.new(
      principal: Input.coerce_required(principal, Input::Principal).to_protobuf,
      resources: Input.coerce_array(resources, Input::ResourceCheck).map(&:to_protobuf),
      aux_data: Input.coerce_optional(aux_data, Input::AuxData)&.to_protobuf,
      include_meta: ,
      request_id: request_id,
      request_context: Input.coerce_optional(request_context, Input::RequestContext)&.to_protobuf
    )

    response = @cerbos_service.call(:check_resources, request, )

    Output::CheckResources.from_protobuf(response).tap do |output|
      handle_validation_errors output
    end
  end
end

#plan_resources(principal:, resource:, action: "", actions: [], aux_data: nil, include_metadata: false, request_id: SecureRandom.uuid, request_context: nil, grpc_metadata: {}) ⇒ Output::PlanResources

Produce a query plan that can be used to obtain a list of resources on which a principal is allowed to perform a particular action.

Examples:

plan = client.plan_resources(
  principal: {id: "user@example.com", roles: ["USER"]},
  resource: {kind: "document"},
  actions: ["view"]
)

plan.conditional? # => true
plan.condition # => #<Cerbos::Output::PlanResources::Expression ...>

Parameters:

  • principal (Input::Principal, Hash)

    the principal for whom to plan.

  • resource (Input::ResourceQuery, Hash)

    partial details of the resources for which to plan.

  • action (String) (defaults to: "")

    deprecated (use actions instead).

  • actions (Array<String>) (defaults to: [])

    the actions for which to plan (requires a policy decision point server running Cerbos v0.44+).

  • aux_data (Input::AuxData, Hash, nil) (defaults to: nil)

    auxiliary data.

  • include_metadata (Boolean) (defaults to: false)

    true to include additional metadata (Output::CheckResources::Result::Metadata) in the results.

  • request_id (String) (defaults to: SecureRandom.uuid)

    identifier for tracing the request.

  • request_context (Input::RequestContext, Hash, nil) (defaults to: nil)

    additional metadata to add to the request (requires a policy decision point server running Cerbos v0.51+).

  • grpc_metadata (Hash{String, Symbol => String, Array<String>}) (defaults to: {})

    gRPC metadata (a.k.a. HTTP headers) to add to the request.

Returns:



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/cerbos/client.rb', line 233

def plan_resources(principal:, resource:, action: "", actions: [], aux_data: nil, include_metadata: false, request_id: SecureRandom.uuid, request_context: nil, grpc_metadata: {})
  Error.handle do
    request = Protobuf::Cerbos::Request::V1::PlanResourcesRequest.new(
      principal: Input.coerce_required(principal, Input::Principal).to_protobuf,
      resource: Input.coerce_required(resource, Input::ResourceQuery).to_protobuf,
      action: action,
      actions: actions,
      aux_data: Input.coerce_optional(aux_data, Input::AuxData)&.to_protobuf,
      include_meta: ,
      request_id: request_id,
      request_context: Input.coerce_optional(request_context, Input::RequestContext)&.to_protobuf
    )

    response = @cerbos_service.call(:plan_resources, request, )

    Output::PlanResources.from_protobuf(response).tap do |output|
      handle_validation_errors output
    end
  end
end

#server_info(grpc_metadata: {}) ⇒ Output::ServerInfo

Retrieve information about the Cerbos PDP server.

Parameters:

  • grpc_metadata (Hash{String, Symbol => String, Array<String>}) (defaults to: {})

    gRPC metadata (a.k.a. HTTP headers) to add to the request.

Returns:



259
260
261
262
263
264
265
266
267
# File 'lib/cerbos/client.rb', line 259

def server_info(grpc_metadata: {})
  Error.handle do
    request = Protobuf::Cerbos::Request::V1::ServerInfoRequest.new

    response = @cerbos_service.call(:server_info, request, )

    Output::ServerInfo.from_protobuf(response)
  end
end