| # 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. |
| |
| import datetime |
| import time |
| from typing import Optional |
| |
| from absl import flags |
| from absl.testing import absltest |
| import grpc |
| |
| from framework import xds_k8s_testcase |
| from framework.helpers import skips |
| |
| flags.adopt_module_key_flags(xds_k8s_testcase) |
| |
| # Type aliases |
| _XdsTestServer = xds_k8s_testcase.XdsTestServer |
| _XdsTestClient = xds_k8s_testcase.XdsTestClient |
| _SecurityMode = xds_k8s_testcase.SecurityXdsKubernetesTestCase.SecurityMode |
| _Lang = skips.Lang |
| |
| # The client generates QPS even when it is still loading information from xDS. |
| # Once it finally connects there will be an outpouring of the bufferred RPCs and |
| # the server needs time to chew through the backlog, especially since it is |
| # still a new process and so probably interpreted. The server on one run |
| # processed 225 RPCs a second, so with the client configured for 25 qps this is |
| # 40 seconds worth of buffering before starting to drain the backlog. |
| _SETTLE_DURATION = datetime.timedelta(seconds=5) |
| _SAMPLE_DURATION = datetime.timedelta(seconds=0.5) |
| |
| |
| class AuthzTest(xds_k8s_testcase.SecurityXdsKubernetesTestCase): |
| RPC_TYPE_CYCLE = { |
| 'UNARY_CALL': 'EMPTY_CALL', |
| 'EMPTY_CALL': 'UNARY_CALL', |
| } |
| |
| @staticmethod |
| def is_supported(config: skips.TestConfig) -> bool: |
| # Per "Authorization (RBAC)" in |
| # https://github.com/grpc/grpc/blob/master/doc/grpc_xds_features.md |
| if config.client_lang in _Lang.CPP | _Lang.PYTHON: |
| return config.version_gte('v1.47.x') |
| elif config.client_lang in _Lang.GO | _Lang.JAVA: |
| return config.version_gte('v1.42.x') |
| elif config.client_lang == _Lang.NODE: |
| return False |
| return True |
| |
| def setUp(self): |
| super().setUp() |
| self.next_rpc_type: Optional[int] = None |
| |
| def authz_rules(self): |
| return [ |
| { |
| "destinations": { |
| "hosts": [f"*:{self.server_xds_port}"], |
| "ports": [self.server_port], |
| "httpHeaderMatch": { |
| "headerName": "test", |
| "regexMatch": "host-wildcard", |
| }, |
| }, |
| }, |
| { |
| "destinations": { |
| "hosts": [f"*:{self.server_xds_port}"], |
| "ports": [self.server_port], |
| "httpHeaderMatch": { |
| "headerName": "test", |
| "regexMatch": "header-regex-a+", |
| }, |
| }, |
| }, |
| { |
| "destinations": [{ |
| "hosts": [f"{self.server_xds_host}:{self.server_xds_port}"], |
| "ports": [self.server_port], |
| "httpHeaderMatch": { |
| "headerName": "test", |
| "regexMatch": "host-match1", |
| }, |
| }, { |
| "hosts": [ |
| f"a-not-it.com:{self.server_xds_port}", |
| f"{self.server_xds_host}:{self.server_xds_port}", |
| "z-not-it.com:1", |
| ], |
| "ports": [1, self.server_port, 65535], |
| "httpHeaderMatch": { |
| "headerName": "test", |
| "regexMatch": "host-match2", |
| }, |
| }], |
| }, |
| { |
| "destinations": { |
| "hosts": [ |
| f"not-the-host:{self.server_xds_port}", |
| "not-the-host", |
| ], |
| "ports": [self.server_port], |
| "httpHeaderMatch": { |
| "headerName": "test", |
| "regexMatch": "never-match-host", |
| }, |
| }, |
| }, |
| { |
| "destinations": { |
| "hosts": [f"*:{self.server_xds_port}"], |
| "ports": [1], |
| "httpHeaderMatch": { |
| "headerName": "test", |
| "regexMatch": "never-match-port", |
| }, |
| }, |
| }, |
| # b/202058316. The wildcard principal is generating invalid config |
| # { |
| # "sources": { |
| # "principals": ["*"], |
| # }, |
| # "destinations": { |
| # "hosts": [f"*:{self.server_xds_port}"], |
| # "ports": [self.server_port], |
| # "httpHeaderMatch": { |
| # "headerName": "test", |
| # "regexMatch": "principal-present", |
| # }, |
| # }, |
| # }, |
| { |
| "sources": [{ |
| "principals": [ |
| f"spiffe://{self.project}.svc.id.goog/not/the/client", |
| ], |
| }, { |
| "principals": [ |
| f"spiffe://{self.project}.svc.id.goog/not/the/client", |
| f"spiffe://{self.project}.svc.id.goog/ns/" |
| f"{self.client_namespace}/sa/{self.client_name}", |
| ], |
| }], |
| "destinations": { |
| "hosts": [f"*:{self.server_xds_port}"], |
| "ports": [self.server_port], |
| "httpHeaderMatch": { |
| "headerName": "test", |
| "regexMatch": "match-principal", |
| }, |
| }, |
| }, |
| { |
| "sources": { |
| "principals": [ |
| f"spiffe://{self.project}.svc.id.goog/not/the/client", |
| ], |
| }, |
| "destinations": { |
| "hosts": [f"*:{self.server_xds_port}"], |
| "ports": [self.server_port], |
| "httpHeaderMatch": { |
| "headerName": "test", |
| "regexMatch": "never-match-principal", |
| }, |
| }, |
| }, |
| ] |
| |
| def configure_and_assert(self, test_client: _XdsTestClient, |
| test_metadata_val: Optional[str], |
| status_code: grpc.StatusCode) -> None: |
| # Swap method type every sub-test to avoid mixing results |
| rpc_type = self.next_rpc_type |
| if rpc_type is None: |
| stats = test_client.get_load_balancer_accumulated_stats() |
| for t in self.RPC_TYPE_CYCLE: |
| if not stats.stats_per_method[t].rpcs_started: |
| rpc_type = t |
| self.assertIsNotNone(rpc_type, "All RPC types already used") |
| self.next_rpc_type = self.RPC_TYPE_CYCLE[rpc_type] |
| |
| metadata = None |
| if test_metadata_val is not None: |
| metadata = ((rpc_type, "test", test_metadata_val),) |
| test_client.update_config.configure(rpc_types=[rpc_type], |
| metadata=metadata) |
| # b/228743575 Python has as race. Give us time to fix it. |
| stray_rpc_limit = 1 if self.lang_spec.client_lang == _Lang.PYTHON else 0 |
| self.assertRpcStatusCodes(test_client, |
| expected_status=status_code, |
| duration=_SAMPLE_DURATION, |
| method=rpc_type, |
| stray_rpc_limit=stray_rpc_limit) |
| |
| def test_plaintext_allow(self) -> None: |
| self.setupTrafficDirectorGrpc() |
| self.td.create_authz_policy(action='ALLOW', rules=self.authz_rules()) |
| self.setupSecurityPolicies(server_tls=False, |
| server_mtls=False, |
| client_tls=False, |
| client_mtls=False) |
| |
| test_server: _XdsTestServer = self.startSecureTestServer() |
| self.setupServerBackends() |
| test_client: _XdsTestClient = self.startSecureTestClient(test_server) |
| time.sleep(_SETTLE_DURATION.total_seconds()) |
| |
| with self.subTest('01_host_wildcard'): |
| self.configure_and_assert(test_client, 'host-wildcard', |
| grpc.StatusCode.OK) |
| |
| with self.subTest('02_no_match'): |
| self.configure_and_assert(test_client, 'no-such-rule', |
| grpc.StatusCode.PERMISSION_DENIED) |
| self.configure_and_assert(test_client, None, |
| grpc.StatusCode.PERMISSION_DENIED) |
| |
| with self.subTest('03_header_regex'): |
| self.configure_and_assert(test_client, 'header-regex-a', |
| grpc.StatusCode.OK) |
| self.configure_and_assert(test_client, 'header-regex-aa', |
| grpc.StatusCode.OK) |
| self.configure_and_assert(test_client, 'header-regex-', |
| grpc.StatusCode.PERMISSION_DENIED) |
| self.configure_and_assert(test_client, 'header-regex-ab', |
| grpc.StatusCode.PERMISSION_DENIED) |
| self.configure_and_assert(test_client, 'aheader-regex-a', |
| grpc.StatusCode.PERMISSION_DENIED) |
| |
| with self.subTest('04_host_match'): |
| self.configure_and_assert(test_client, 'host-match1', |
| grpc.StatusCode.OK) |
| self.configure_and_assert(test_client, 'host-match2', |
| grpc.StatusCode.OK) |
| |
| with self.subTest('05_never_match_host'): |
| self.configure_and_assert(test_client, 'never-match-host', |
| grpc.StatusCode.PERMISSION_DENIED) |
| |
| with self.subTest('06_never_match_port'): |
| self.configure_and_assert(test_client, 'never-match-port', |
| grpc.StatusCode.PERMISSION_DENIED) |
| |
| # b/202058316 |
| # with self.subTest('07_principal_present'): |
| # self.configure_and_assert(test_client, 'principal-present', |
| # grpc.StatusCode.PERMISSION_DENIED) |
| |
| def test_tls_allow(self) -> None: |
| self.setupTrafficDirectorGrpc() |
| self.td.create_authz_policy(action='ALLOW', rules=self.authz_rules()) |
| self.setupSecurityPolicies(server_tls=True, |
| server_mtls=False, |
| client_tls=True, |
| client_mtls=False) |
| |
| test_server: _XdsTestServer = self.startSecureTestServer() |
| self.setupServerBackends() |
| test_client: _XdsTestClient = self.startSecureTestClient(test_server) |
| time.sleep(_SETTLE_DURATION.total_seconds()) |
| |
| with self.subTest('01_host_wildcard'): |
| self.configure_and_assert(test_client, 'host-wildcard', |
| grpc.StatusCode.OK) |
| |
| with self.subTest('02_no_match'): |
| self.configure_and_assert(test_client, None, |
| grpc.StatusCode.PERMISSION_DENIED) |
| |
| # b/202058316 |
| # with self.subTest('03_principal_present'): |
| # self.configure_and_assert(test_client, 'principal-present', |
| # grpc.StatusCode.PERMISSION_DENIED) |
| |
| def test_mtls_allow(self) -> None: |
| self.setupTrafficDirectorGrpc() |
| self.td.create_authz_policy(action='ALLOW', rules=self.authz_rules()) |
| self.setupSecurityPolicies(server_tls=True, |
| server_mtls=True, |
| client_tls=True, |
| client_mtls=True) |
| |
| test_server: _XdsTestServer = self.startSecureTestServer() |
| self.setupServerBackends() |
| test_client: _XdsTestClient = self.startSecureTestClient(test_server) |
| time.sleep(_SETTLE_DURATION.total_seconds()) |
| |
| with self.subTest('01_host_wildcard'): |
| self.configure_and_assert(test_client, 'host-wildcard', |
| grpc.StatusCode.OK) |
| |
| with self.subTest('02_no_match'): |
| self.configure_and_assert(test_client, None, |
| grpc.StatusCode.PERMISSION_DENIED) |
| |
| # b/202058316 |
| # with self.subTest('03_principal_present'): |
| # self.configure_and_assert(test_client, 'principal-present', |
| # grpc.StatusCode.OK) |
| |
| with self.subTest('04_match_principal'): |
| self.configure_and_assert(test_client, 'match-principal', |
| grpc.StatusCode.OK) |
| |
| with self.subTest('05_never_match_principal'): |
| self.configure_and_assert(test_client, 'never-match-principal', |
| grpc.StatusCode.PERMISSION_DENIED) |
| |
| def test_plaintext_deny(self) -> None: |
| self.setupTrafficDirectorGrpc() |
| self.td.create_authz_policy(action='DENY', rules=self.authz_rules()) |
| self.setupSecurityPolicies(server_tls=False, |
| server_mtls=False, |
| client_tls=False, |
| client_mtls=False) |
| |
| test_server: _XdsTestServer = self.startSecureTestServer() |
| self.setupServerBackends() |
| test_client: _XdsTestClient = self.startSecureTestClient(test_server) |
| time.sleep(_SETTLE_DURATION.total_seconds()) |
| |
| with self.subTest('01_host_wildcard'): |
| self.configure_and_assert(test_client, 'host-wildcard', |
| grpc.StatusCode.PERMISSION_DENIED) |
| |
| with self.subTest('02_no_match'): |
| self.configure_and_assert(test_client, None, grpc.StatusCode.OK) |
| |
| |
| if __name__ == '__main__': |
| absltest.main() |