UNPKG

@cerbos/grpc

Version:

Client library for interacting with the Cerbos policy decision point service over gRPC from server-side Node.js applications

347 lines (301 loc) 8.99 kB
import type { SecureContext } from "tls"; import type { ChannelOptions as GrpcChannelOptions } from "@grpc/grpc-js"; import { ChannelCredentials, Client as GenericClient, compressionAlgorithms, } from "@grpc/grpc-js"; import type { Options as CoreOptions } from "@cerbos/core"; import { Client } from "@cerbos/core"; import { Transport } from "./transport"; const { version } = require("../package.json") as { version: string }; const defaultUserAgent = `cerbos-sdk-javascript-grpc/${version}`; /** * Options for creating a new {@link GRPC} client. * * @public */ export interface Options extends CoreOptions { /** * Encrypt the gRPC connection with TLS. * * @remarks * Possible values are * * - `false` - communicate via plaintext; * * - `true` - encrypt the connection with TLS, verifying the server identity with the default set of well-known root certificate authorities; or * * - the result of calling {@link https://nodejs.org/api/tls.html#tlscreatesecurecontextoptions | tls.createSecureContext }, to configure custom root certificate authorities or client certificates for mutual TLS. */ tls: boolean | SecureContext; /** * Compress messages exchanged between the client and policy decision point server. * * @remarks * Requires the Cerbos policy decision point server to be at least v0.19. * * @defaultValue `Compression.NONE` */ compression?: Compression | undefined; /** * Advanced settings to configure the underlying gRPC client. * * @defaultValue `{}` */ channelOptions?: ChannelOptions | undefined; } /** * Compression algorithm to apply to messages exchanged between the client and policy decision point server. * * @public */ export enum Compression { /** * Do not compress messages. */ NONE = "identity", /** * Compress messages with gzip. */ GZIP = "gzip", } /** * Advanced settings to configure a gRPC client. * * @public */ export interface ChannelOptions { /** * Initial window size (in bytes) for HTTP/2 stream-level flow control. * * @defaultValue `65_535` */ "grpc-node.flow_control_window"?: number | undefined; /** * Maximum number of attempts when retrying requests. * * @defaultValue `5` */ "grpc-node.retry_max_attempts_limit"?: number | undefined; /** * Set to `1` to {@link https://nodejs.org/api/tls.html#tlssocketenabletrace | write TLS packet trace information to stderr}. * * @defaultValue `0` (disabled) */ "grpc-node.tls_enable_trace"?: number | undefined; /** * Timeout (in milliseconds) after the last RPC finishes on the client channel at which the * channel goes back into IDLE state. * * @remarks * Minimum value is 1 second. * * @defaultValue `1_800_000` (30 minutes) */ "grpc.client_idle_timeout_ms"?: number | undefined; /** * Default value for the `:authority` HTTP/2 pseudo-header. */ "grpc.default_authority"?: string | undefined; /** * Minimum amount of time between DNS resolutions (in milliseconds). * * @defaultValue `30_000` (30 seconds) */ "grpc.dns_min_time_between_resolutions_ms"?: number | undefined; /** * Set to `0` to disable channel-level debug information. * * @defaultValue `1` (enabled) */ "grpc.enable_channelz"?: number | undefined; /** * Set to `0` to disable HTTP proxies. * * @defaultValue `1` (enabled) */ "grpc.enable_http_proxy"?: number | undefined; /** * Set to `0` to prevent the client from retrying requests. * * @defaultValue `1` (enabled) */ "grpc.enable_retries"?: number | undefined; /** * The time between the first and second connection attempts (in milliseconds). * * @defaultValue `1_000` (1 second) */ "grpc.initial_reconnect_backoff_ms"?: number | undefined; /** * Set to `1` to send keepalive pings from the client without any outstanding streams. * * @defaultValue `0` (disabled) */ "grpc.keepalive_permit_without_calls"?: number | undefined; /** * Duration (in milliseconds) after which client pings the server to see if the transport is still alive. * * @defaultValue `-1` (disabled) */ "grpc.keepalive_time_ms"?: number | undefined; /** * Duration (in milliseconds) after which to close the transport if a keepalive ping has not been acknowleged by the server. */ "grpc.keepalive_timeout_ms"?: number | undefined; /** * Cap the minimum and maximum ring size values set in the load balander policy config to this value. * * @defaultValue `4096` */ "grpc.lb.ring_hash.ring_size_cap"?: number | undefined; /** * Maximum size (in bytes) of messages that can be received on the channel. * * @defaultValue `4_194_304` (4 MiB) */ "grpc.max_receive_message_length"?: number | undefined; /** * The maximum time between subsequent connection attempts (in milliseconds). * * @defaultValue `120_000` (2 minutes) */ "grpc.max_reconnect_backoff_ms"?: number | undefined; /** * Maximum size (in bytes) of messages that can be sent on the channel. * * @defaultValue `-1` (unlimited) */ "grpc.max_send_message_length"?: number | undefined; /** * Per-RPC retry buffer size (in bytes). * * @defaultValue `1_048_576` (1 MiB) */ "grpc.per_rpc_retry_buffer_size"?: number | undefined; /** * Total retry buffer size (in bytes). * * @defaultValue `16_777_216` (16 MiB) */ "grpc.retry_buffer_size"?: number | undefined; /** * Set to `1` to disable looking up service config via DNS. * * @defaultValue `0` (enabled) */ "grpc.service_config_disable_resolution"?: number | undefined; /** * Service config data in JSON form. * * @remarks * This value will be ignored if service config is found via DNS. */ "grpc.service_config"?: string | undefined; /** * Override the target name used for SSL hostname checking. * * @remarks * This should only be used for testing. */ "grpc.ssl_target_name_override"?: string | undefined; /** * If set to `0`, this channel uses the global subchannel pool. Otherwise, it uses a local subchannel pool within the channel. * * @defaultValue `0` (disabled) */ "grpc.use_local_subchannel_pool"?: number | undefined; } /** * A client for interacting with the Cerbos policy decision point server over gRPC. * * @remarks * Not supported in browsers. * * See {@link @cerbos/core#Client | the parent class} for available methods. * * @public */ export class GRPC extends Client { private readonly client: GenericClient; /** * Create a client for interacting with the Cerbos policy decision point (PDP) server over gRPC. * * @param target - Cerbos PDP server address (`"host"`, `"host:port"`, or `"unix:/path/to/socket"`). * @param options - additional client settings. * * @example * Connect via TCP with no encryption: * * ```typescript * const cerbos = new GRPC("localhost:3593", { tls: false }); * ``` * * @example * Connect via a Unix socket with no encryption: * * ```typescript * const cerbos = new GRPC("unix:/var/run/cerbos.grpc.sock", { tls: false }); * ``` * * @example * Connect to the hosted demo PDP to experiment {@link https://play.cerbos.dev | in the playground}: * * ```typescript * const cerbos = new GRPC("demo-pdp.cerbos.cloud", { tls: true, playgroundInstance: "gE623b0180QlsG5a4QIN6UOZ6f3iSFW2" }); * ``` */ public constructor(target: string, options: Options) { const client = new GenericClient( target, channelCredentials(options), channelOptions(options), ); super(new Transport(client), options); this.client = client; } /** * Disconnect from the Cerbos policy decision point server and clean up resources. * * @remarks * It is safe to call `close` more than once. * * Any interactions with the server after calling `close` will throw an error. */ public close(): void { this.client.close(); } } function channelCredentials({ playgroundInstance, tls, }: Options): ChannelCredentials { if (!tls) { if (playgroundInstance) { throw new Error( "TLS is required when connecting to a playground instance", ); } return ChannelCredentials.createInsecure(); } if (tls === true) { return ChannelCredentials.createSsl(); } return ChannelCredentials.createFromSecureContext(tls); } function channelOptions({ compression = Compression.NONE, userAgent, channelOptions: input = {}, }: Options): GrpcChannelOptions { const output = Object.fromEntries( Object.entries(input).filter(([, value]) => value !== undefined), ); output["grpc.default_compression_algorithm"] = compressionAlgorithms[compression]; output["grpc.primary_user_agent"] = `${ userAgent ? `${userAgent} ` : "" }${defaultUserAgent}`; return output; }