authz_test.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. # Copyright 2021 gRPC authors.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import datetime
  15. import time
  16. from typing import Optional
  17. from absl import flags
  18. from absl.testing import absltest
  19. import grpc
  20. from framework import xds_k8s_testcase
  21. from framework.helpers import skips
  22. flags.adopt_module_key_flags(xds_k8s_testcase)
  23. # Type aliases
  24. _XdsTestServer = xds_k8s_testcase.XdsTestServer
  25. _XdsTestClient = xds_k8s_testcase.XdsTestClient
  26. _SecurityMode = xds_k8s_testcase.SecurityXdsKubernetesTestCase.SecurityMode
  27. # The client generates QPS even when it is still loading information from xDS.
  28. # Once it finally connects there will be an outpouring of the bufferred RPCs and
  29. # the server needs time to chew through the backlog, especially since it is
  30. # still a new process and so probably interpreted. The server on one run
  31. # processed 225 RPCs a second, so with the client configured for 25 qps this is
  32. # 40 seconds worth of buffering before starting to drain the backlog.
  33. _SETTLE_DURATION = datetime.timedelta(seconds=5)
  34. _SAMPLE_DURATION = datetime.timedelta(seconds=0.5)
  35. class AuthzTest(xds_k8s_testcase.SecurityXdsKubernetesTestCase):
  36. RPC_TYPE_CYCLE = {
  37. 'UNARY_CALL': 'EMPTY_CALL',
  38. 'EMPTY_CALL': 'UNARY_CALL',
  39. }
  40. @staticmethod
  41. def isSupported(config: skips.TestConfig) -> bool:
  42. if config.client_lang in ['cpp', 'python']:
  43. return config.version_ge('v1.44.x')
  44. elif config.client_lang in ['java', 'go']:
  45. return config.version_ge('v1.42.x')
  46. return False
  47. def setUp(self):
  48. super().setUp()
  49. self.next_rpc_type: Optional[int] = None
  50. def authz_rules(self):
  51. return [
  52. {
  53. "destinations": {
  54. "hosts": [f"*:{self.server_xds_port}"],
  55. "ports": [self.server_port],
  56. "httpHeaderMatch": {
  57. "headerName": "test",
  58. "regexMatch": "host-wildcard",
  59. },
  60. },
  61. },
  62. {
  63. "destinations": {
  64. "hosts": [f"*:{self.server_xds_port}"],
  65. "ports": [self.server_port],
  66. "httpHeaderMatch": {
  67. "headerName": "test",
  68. "regexMatch": "header-regex-a+",
  69. },
  70. },
  71. },
  72. {
  73. "destinations": [{
  74. "hosts": [f"{self.server_xds_host}:{self.server_xds_port}"],
  75. "ports": [self.server_port],
  76. "httpHeaderMatch": {
  77. "headerName": "test",
  78. "regexMatch": "host-match1",
  79. },
  80. }, {
  81. "hosts": [
  82. f"a-not-it.com:{self.server_xds_port}",
  83. f"{self.server_xds_host}:{self.server_xds_port}",
  84. "z-not-it.com:1",
  85. ],
  86. "ports": [1, self.server_port, 65535],
  87. "httpHeaderMatch": {
  88. "headerName": "test",
  89. "regexMatch": "host-match2",
  90. },
  91. }],
  92. },
  93. {
  94. "destinations": {
  95. "hosts": [
  96. f"not-the-host:{self.server_xds_port}",
  97. "not-the-host",
  98. ],
  99. "ports": [self.server_port],
  100. "httpHeaderMatch": {
  101. "headerName": "test",
  102. "regexMatch": "never-match-host",
  103. },
  104. },
  105. },
  106. {
  107. "destinations": {
  108. "hosts": [f"*:{self.server_xds_port}"],
  109. "ports": [1],
  110. "httpHeaderMatch": {
  111. "headerName": "test",
  112. "regexMatch": "never-match-port",
  113. },
  114. },
  115. },
  116. # b/202058316. The wildcard principal is generating invalid config
  117. # {
  118. # "sources": {
  119. # "principals": ["*"],
  120. # },
  121. # "destinations": {
  122. # "hosts": [f"*:{self.server_xds_port}"],
  123. # "ports": [self.server_port],
  124. # "httpHeaderMatch": {
  125. # "headerName": "test",
  126. # "regexMatch": "principal-present",
  127. # },
  128. # },
  129. # },
  130. {
  131. "sources": [{
  132. "principals": [
  133. f"spiffe://{self.project}.svc.id.goog/not/the/client",
  134. ],
  135. }, {
  136. "principals": [
  137. f"spiffe://{self.project}.svc.id.goog/not/the/client",
  138. f"spiffe://{self.project}.svc.id.goog/ns/"
  139. f"{self.client_namespace}/sa/{self.client_name}",
  140. ],
  141. }],
  142. "destinations": {
  143. "hosts": [f"*:{self.server_xds_port}"],
  144. "ports": [self.server_port],
  145. "httpHeaderMatch": {
  146. "headerName": "test",
  147. "regexMatch": "match-principal",
  148. },
  149. },
  150. },
  151. {
  152. "sources": {
  153. "principals": [
  154. f"spiffe://{self.project}.svc.id.goog/not/the/client",
  155. ],
  156. },
  157. "destinations": {
  158. "hosts": [f"*:{self.server_xds_port}"],
  159. "ports": [self.server_port],
  160. "httpHeaderMatch": {
  161. "headerName": "test",
  162. "regexMatch": "never-match-principal",
  163. },
  164. },
  165. },
  166. ]
  167. def configure_and_assert(self, test_client: _XdsTestClient,
  168. test_metadata_val: Optional[str],
  169. status_code: grpc.StatusCode) -> None:
  170. # Swap method type every sub-test to avoid mixing results
  171. rpc_type = self.next_rpc_type
  172. if rpc_type is None:
  173. stats = test_client.get_load_balancer_accumulated_stats()
  174. for t in self.RPC_TYPE_CYCLE:
  175. if not stats.stats_per_method[t].rpcs_started:
  176. rpc_type = t
  177. self.assertIsNotNone(rpc_type, "All RPC types already used")
  178. self.next_rpc_type = self.RPC_TYPE_CYCLE[rpc_type]
  179. metadata = None
  180. if test_metadata_val is not None:
  181. metadata = ((rpc_type, "test", test_metadata_val),)
  182. test_client.update_config.configure(rpc_types=[rpc_type],
  183. metadata=metadata)
  184. self.assertRpcStatusCodes(test_client,
  185. status_code=status_code,
  186. duration=_SAMPLE_DURATION,
  187. method=rpc_type)
  188. def test_plaintext_allow(self) -> None:
  189. self.setupTrafficDirectorGrpc()
  190. self.td.create_authz_policy(action='ALLOW', rules=self.authz_rules())
  191. self.setupSecurityPolicies(server_tls=False,
  192. server_mtls=False,
  193. client_tls=False,
  194. client_mtls=False)
  195. test_server: _XdsTestServer = self.startSecureTestServer()
  196. self.setupServerBackends()
  197. test_client: _XdsTestClient = self.startSecureTestClient(test_server)
  198. time.sleep(_SETTLE_DURATION.total_seconds())
  199. with self.subTest('01_host_wildcard'):
  200. self.configure_and_assert(test_client, 'host-wildcard',
  201. grpc.StatusCode.OK)
  202. with self.subTest('02_no_match'):
  203. self.configure_and_assert(test_client, 'no-such-rule',
  204. grpc.StatusCode.PERMISSION_DENIED)
  205. self.configure_and_assert(test_client, None,
  206. grpc.StatusCode.PERMISSION_DENIED)
  207. with self.subTest('03_header_regex'):
  208. self.configure_and_assert(test_client, 'header-regex-a',
  209. grpc.StatusCode.OK)
  210. self.configure_and_assert(test_client, 'header-regex-aa',
  211. grpc.StatusCode.OK)
  212. self.configure_and_assert(test_client, 'header-regex-',
  213. grpc.StatusCode.PERMISSION_DENIED)
  214. self.configure_and_assert(test_client, 'header-regex-ab',
  215. grpc.StatusCode.PERMISSION_DENIED)
  216. self.configure_and_assert(test_client, 'aheader-regex-a',
  217. grpc.StatusCode.PERMISSION_DENIED)
  218. with self.subTest('04_host_match'):
  219. self.configure_and_assert(test_client, 'host-match1',
  220. grpc.StatusCode.OK)
  221. self.configure_and_assert(test_client, 'host-match2',
  222. grpc.StatusCode.OK)
  223. with self.subTest('05_never_match_host'):
  224. self.configure_and_assert(test_client, 'never-match-host',
  225. grpc.StatusCode.PERMISSION_DENIED)
  226. with self.subTest('06_never_match_port'):
  227. self.configure_and_assert(test_client, 'never-match-port',
  228. grpc.StatusCode.PERMISSION_DENIED)
  229. # b/202058316
  230. # with self.subTest('07_principal_present'):
  231. # self.configure_and_assert(test_client, 'principal-present',
  232. # grpc.StatusCode.PERMISSION_DENIED)
  233. def test_tls_allow(self) -> None:
  234. self.setupTrafficDirectorGrpc()
  235. self.td.create_authz_policy(action='ALLOW', rules=self.authz_rules())
  236. self.setupSecurityPolicies(server_tls=True,
  237. server_mtls=False,
  238. client_tls=True,
  239. client_mtls=False)
  240. test_server: _XdsTestServer = self.startSecureTestServer()
  241. self.setupServerBackends()
  242. test_client: _XdsTestClient = self.startSecureTestClient(test_server)
  243. time.sleep(_SETTLE_DURATION.total_seconds())
  244. with self.subTest('01_host_wildcard'):
  245. self.configure_and_assert(test_client, 'host-wildcard',
  246. grpc.StatusCode.OK)
  247. with self.subTest('02_no_match'):
  248. self.configure_and_assert(test_client, None,
  249. grpc.StatusCode.PERMISSION_DENIED)
  250. # b/202058316
  251. # with self.subTest('03_principal_present'):
  252. # self.configure_and_assert(test_client, 'principal-present',
  253. # grpc.StatusCode.PERMISSION_DENIED)
  254. def test_mtls_allow(self) -> None:
  255. self.setupTrafficDirectorGrpc()
  256. self.td.create_authz_policy(action='ALLOW', rules=self.authz_rules())
  257. self.setupSecurityPolicies(server_tls=True,
  258. server_mtls=True,
  259. client_tls=True,
  260. client_mtls=True)
  261. test_server: _XdsTestServer = self.startSecureTestServer()
  262. self.setupServerBackends()
  263. test_client: _XdsTestClient = self.startSecureTestClient(test_server)
  264. time.sleep(_SETTLE_DURATION.total_seconds())
  265. with self.subTest('01_host_wildcard'):
  266. self.configure_and_assert(test_client, 'host-wildcard',
  267. grpc.StatusCode.OK)
  268. with self.subTest('02_no_match'):
  269. self.configure_and_assert(test_client, None,
  270. grpc.StatusCode.PERMISSION_DENIED)
  271. # b/202058316
  272. # with self.subTest('03_principal_present'):
  273. # self.configure_and_assert(test_client, 'principal-present',
  274. # grpc.StatusCode.OK)
  275. with self.subTest('04_match_principal'):
  276. self.configure_and_assert(test_client, 'match-principal',
  277. grpc.StatusCode.OK)
  278. with self.subTest('05_never_match_principal'):
  279. self.configure_and_assert(test_client, 'never-match-principal',
  280. grpc.StatusCode.PERMISSION_DENIED)
  281. def test_plaintext_deny(self) -> None:
  282. self.setupTrafficDirectorGrpc()
  283. self.td.create_authz_policy(action='DENY', rules=self.authz_rules())
  284. self.setupSecurityPolicies(server_tls=False,
  285. server_mtls=False,
  286. client_tls=False,
  287. client_mtls=False)
  288. test_server: _XdsTestServer = self.startSecureTestServer()
  289. self.setupServerBackends()
  290. test_client: _XdsTestClient = self.startSecureTestClient(test_server)
  291. time.sleep(_SETTLE_DURATION.total_seconds())
  292. with self.subTest('01_host_wildcard'):
  293. self.configure_and_assert(test_client, 'host-wildcard',
  294. grpc.StatusCode.PERMISSION_DENIED)
  295. with self.subTest('02_no_match'):
  296. self.configure_and_assert(test_client, None, grpc.StatusCode.OK)
  297. if __name__ == '__main__':
  298. absltest.main()