Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/grpc-js-xds/gulpfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const copyTestFixtures = checkTask(() =>
const runTests = checkTask(() => {
process.env.GRPC_EXPERIMENTAL_XDS_FEDERATION = 'true';
process.env.GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG = 'true';
process.env.GRPC_XDS_EXPERIMENTAL_RBAC = 'true';
if (Number(process.versions.node.split('.')[0]) <= 14) {
process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_RING_HASH = 'false';
}
Expand Down
2 changes: 1 addition & 1 deletion packages/grpc-js-xds/interop/test-server.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ COPY --from=build /node/src/grpc-node/packages/grpc-js ./packages/grpc-js/
COPY --from=build /node/src/grpc-node/packages/grpc-js-xds ./packages/grpc-js-xds/

ENV GRPC_VERBOSITY="DEBUG"
ENV GRPC_TRACE=xds_client,server,xds_server,http_filter,certificate_provider
ENV GRPC_TRACE=xds_client,server,xds_server,http_filter,certificate_provider,rbac_filter

# tini serves as PID 1 and enables the server to properly respond to signals.
COPY --from=build /tini /tini
Expand Down
2 changes: 1 addition & 1 deletion packages/grpc-js-xds/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"prepare": "npm run generate-types && npm run compile",
"pretest": "npm run compile",
"posttest": "npm run check",
"generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/xds/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v3/ads.proto envoy/service/load_stats/v3/lrs.proto envoy/config/listener/v3/listener.proto envoy/config/route/v3/route.proto envoy/config/cluster/v3/cluster.proto envoy/config/endpoint/v3/endpoint.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto udpa/type/v1/typed_struct.proto xds/type/v3/typed_struct.proto envoy/extensions/filters/http/fault/v3/fault.proto envoy/service/status/v3/csds.proto envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.proto envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.proto envoy/extensions/clusters/aggregate/v3/cluster.proto envoy/extensions/transport_sockets/tls/v3/tls.proto envoy/config/rbac/v3/rbac.proto",
"generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/xds/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v3/ads.proto envoy/service/load_stats/v3/lrs.proto envoy/config/listener/v3/listener.proto envoy/config/route/v3/route.proto envoy/config/cluster/v3/cluster.proto envoy/config/endpoint/v3/endpoint.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto udpa/type/v1/typed_struct.proto xds/type/v3/typed_struct.proto envoy/extensions/filters/http/fault/v3/fault.proto envoy/service/status/v3/csds.proto envoy/extensions/load_balancing_policies/wrr_locality/v3/wrr_locality.proto envoy/extensions/load_balancing_policies/ring_hash/v3/ring_hash.proto envoy/extensions/load_balancing_policies/pick_first/v3/pick_first.proto envoy/extensions/clusters/aggregate/v3/cluster.proto envoy/extensions/transport_sockets/tls/v3/tls.proto envoy/config/rbac/v3/rbac.proto envoy/extensions/filters/http/rbac/v3/rbac.proto",
"generate-interop-types": "proto-loader-gen-types --keep-case --longs String --enums String --defaults --oneofs --json --includeComments --includeDirs proto/ -O interop/generated --grpcLib @grpc/grpc-js grpc/testing/test.proto",
"generate-test-types": "proto-loader-gen-types --keep-case --longs String --enums String --defaults --oneofs --json --includeComments --includeDirs proto/ -O test/generated --grpcLib @grpc/grpc-js grpc/testing/echo.proto"
},
Expand Down
1 change: 1 addition & 0 deletions packages/grpc-js-xds/src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ export const EXPERIMENTAL_RING_HASH = (process.env.GRPC_XDS_EXPERIMENTAL_ENABLE_
export const EXPERIMENTAL_PICK_FIRST = (process.env.GRPC_EXPERIMENTAL_PICKFIRST_LB_CONFIG ?? 'false') === 'true';
export const EXPERIMENTAL_DUALSTACK_ENDPOINTS = (process.env.GRPC_EXPERIMENTAL_XDS_DUALSTACK_ENDPOINTS ?? 'true') === 'true';
export const AGGREGATE_CLUSTER_BACKWARDS_COMPAT = (process.env.GRPC_XDS_AGGREGATE_CLUSTER_BACKWARD_COMPAT ?? 'false') === 'true';
export const EXPERIMENTAL_RBAC = (process.env.GRPC_XDS_EXPERIMENTAL_RBAC ?? 'false') === 'true';

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions packages/grpc-js-xds/src/generated/rbac.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

175 changes: 175 additions & 0 deletions packages/grpc-js-xds/src/http-filter/rbac-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* Copyright 2021 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// This is a non-public, unstable API, but it's very convenient
import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util';
import { experimental, logVerbosity, ServerInterceptingCall, ServerInterceptor, ServerListener, status } from '@grpc/grpc-js';
import { Any__Output } from '../generated/google/protobuf/Any';
import { HttpFilterConfig, registerHttpFilter } from '../http-filter';
import { RbacPolicyGroup, UnifiedInfo as UnifiedRbacInfo } from '../rbac';
import { RBAC__Output } from '../generated/envoy/extensions/filters/http/rbac/v3/RBAC';
import { RBACPerRoute__Output } from '../generated/envoy/extensions/filters/http/rbac/v3/RBACPerRoute';
import { parseConfig as parseRbacConfig } from '../rbac';
import { EXPERIMENTAL_RBAC } from '../environment';

const TRACER_NAME = 'rbac_filter';

function trace(text: string): void {
experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text);
}

const resourceRoot = loadProtosWithOptionsSync([
'envoy/extensions/filters/http/rbac/v3/rbac.proto'], {
keepCase: true,
includeDirs: [
// Paths are relative to src/build/http-filter
__dirname + '/../../../deps/xds/',
__dirname + '/../../../deps/envoy-api/',
__dirname + '/../../../deps/protoc-gen-validate/',
__dirname + '/../../../deps/googleapis/'
],
}
);

const RBAC_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC';
const RBAC_FILTER_OVERRIDE_URL ='type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute';

const toObjectOptions = {
longs: String,
enums: String,
defaults: true,
oneofs: true
}

function parseAnyMessage<MessageType>(message: Any__Output): MessageType | null {
const typeName = message.type_url.substring(message.type_url.lastIndexOf('/') + 1);
const messageType = resourceRoot.lookup(typeName);
if (messageType) {
const decodedMessage = (messageType as any).decode(message.value);
return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as MessageType;
} else {
return null;
}
}

interface RbacFilterConfig extends HttpFilterConfig {
typeUrl: 'type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC';
config: RbacPolicyGroup;
}

function parseTopLevelRbacConfig(encodedConfig: Any__Output): RbacFilterConfig | null {
if (encodedConfig.type_url !== RBAC_FILTER_URL) {
trace('Config parsing failed: unexpected type URL: ' + encodedConfig.type_url);
return null;
}
const parsedMessage = parseAnyMessage<RBAC__Output>(encodedConfig);
if (parsedMessage === null) {
trace('Config parsing failed: failed to parse RBAC message');
return null;
}
trace('Parsing RBAC message ' + JSON.stringify(parsedMessage, undefined, 2));
if (!parsedMessage.rules) {
trace('Config parsing failed: no rules found');
return null;
}
try {
return {
typeUrl: RBAC_FILTER_URL,
config: parseRbacConfig(parsedMessage.rules)
};
} catch (e) {
trace('Config parsing failed: ' + (e as Error).message);
return null;
}
}

function parseOverrideRbacConfig(encodedConfig: Any__Output): RbacFilterConfig | null {
if (encodedConfig.type_url !== RBAC_FILTER_OVERRIDE_URL) {
trace('Config parsing failed: unexpected type URL: ' + encodedConfig.type_url);
return null;
}
const parsedMessage = parseAnyMessage<RBACPerRoute__Output>(encodedConfig);
if (parsedMessage === null) {
trace('Config parsing failed: failed to parse RBACPerRoute message');
return null;
}
trace('Parsing RBAC message ' + JSON.stringify(parsedMessage, undefined, 2));
if (!parsedMessage.rbac?.rules) {
trace('Config parsing failed: no rules found');
return null;
}
try {
return {
typeUrl: RBAC_FILTER_URL,
config: parseRbacConfig(parsedMessage.rbac.rules)
};
} catch (e) {
trace('Config parsing failed: ' + (e as Error).message);
return null;
}
}

function createRbacServerFilter(config: HttpFilterConfig, overrideConfigMap: Map<string, HttpFilterConfig>): ServerInterceptor {
return function rbacServerFilter(methodDescriptor, call): ServerInterceptingCall {
const listener: ServerListener = {
onReceiveMetadata: (metadata, next) => {
let activeConfig = config;
const routeName = metadata.get('grpc-route')[0];
if (routeName) {
const overrideConfig = overrideConfigMap.get(routeName as string);
if (overrideConfig) {
activeConfig = overrideConfig;
}
}
const rbacMetadata = metadata.clone();
rbacMetadata.set(':method', 'POST');
rbacMetadata.set(':authority', call.getHost());
rbacMetadata.set(':path', methodDescriptor.path);
const connectionInfo = call.getConnectionInfo();
const authContext = call.getAuthContext();
const info: UnifiedRbacInfo = {
destinationIp: connectionInfo.localAddress!,
destinationPort: connectionInfo.localPort!,
sourceIp: connectionInfo.remoteAddress!,
headers: rbacMetadata,
tls: authContext.transportSecurityType !== undefined,
peerCertificate: authContext.sslPeerCertificate ?? null,
urlPath: methodDescriptor.path
};
if ((activeConfig as RbacFilterConfig).config.apply(info)) {
next(metadata);
} else {
call.sendStatus({code: status.PERMISSION_DENIED, details: 'Unauthorized RPC rejected'});
}
}
};
return new ServerInterceptingCall(call, {
start: next => {
next(listener);
}
});
}
}

export function setup() {
if (EXPERIMENTAL_RBAC) {
registerHttpFilter(RBAC_FILTER_URL, {
parseTopLevelFilterConfig: parseTopLevelRbacConfig,
parseOverrideFilterConfig: parseOverrideRbacConfig,
createServerFilter: createRbacServerFilter
});
}
}
2 changes: 2 additions & 0 deletions packages/grpc-js-xds/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as xds_wrr_locality from './load-balancer-xds-wrr-locality';
import * as ring_hash from './load-balancer-ring-hash';
import * as router_filter from './http-filter/router-filter';
import * as fault_injection_filter from './http-filter/fault-injection-filter';
import * as rbac_filter from './http-filter/rbac-filter';
import * as csds from './csds';
import * as round_robin_lb from './lb-policy-registry/round-robin';
import * as typed_struct_lb from './lb-policy-registry/typed-struct';
Expand Down Expand Up @@ -53,6 +54,7 @@ export function register() {
ring_hash.setup();
router_filter.setup();
fault_injection_filter.setup();
rbac_filter.setup();
csds.setup();
round_robin_lb.setup();
typed_struct_lb.setup();
Expand Down
Loading