OpenStack Identity (Keystone) Middleware
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_audit_api.py 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  2. # not use this file except in compliance with the License. You may obtain
  3. # a copy of the License at
  4. #
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. #
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. # License for the specific language governing permissions and limitations
  11. # under the License.
  12. import uuid
  13. from pycadf import cadftaxonomy as taxonomy
  14. import webob
  15. from keystonemiddleware import audit
  16. from keystonemiddleware.tests.unit.audit import base
  17. class AuditApiLogicTest(base.BaseAuditMiddlewareTest):
  18. def get_payload(self, method, url,
  19. audit_map=None, body=None, environ=None):
  20. audit_map = audit_map or self.audit_map
  21. environ = environ or self.get_environ_header()
  22. req = webob.Request.blank(url,
  23. body=body,
  24. method=method,
  25. environ=environ,
  26. remote_addr='192.168.0.1')
  27. middleware = audit.OpenStackAuditApi(audit_map)
  28. return middleware._create_event(req).as_dict()
  29. def test_get_list(self):
  30. path = '/v2/' + str(uuid.uuid4()) + '/servers'
  31. url = 'http://admin_host:8774' + path
  32. payload = self.get_payload('GET', url)
  33. self.assertEqual(payload['action'], 'read/list')
  34. self.assertEqual(payload['typeURI'],
  35. 'http://schemas.dmtf.org/cloud/audit/1.0/event')
  36. self.assertEqual(payload['outcome'], 'pending')
  37. self.assertEqual(payload['eventType'], 'activity')
  38. self.assertEqual(payload['target']['name'], 'nova')
  39. self.assertEqual(payload['target']['id'], 'resource_id')
  40. self.assertEqual(payload['target']['typeURI'],
  41. 'service/compute/servers')
  42. self.assertEqual(len(payload['target']['addresses']), 3)
  43. self.assertEqual(payload['target']['addresses'][0]['name'], 'admin')
  44. self.assertEqual(payload['target']['addresses'][0]['url'],
  45. 'http://admin_host:8774')
  46. self.assertEqual(payload['initiator']['id'], 'user_id')
  47. self.assertEqual(payload['initiator']['name'], 'user_name')
  48. self.assertEqual(payload['initiator']['project_id'],
  49. 'tenant_id')
  50. self.assertEqual(payload['initiator']['host']['address'],
  51. '192.168.0.1')
  52. self.assertEqual(payload['initiator']['typeURI'],
  53. 'service/security/account/user')
  54. self.assertNotEqual(payload['initiator']['credential']['token'],
  55. 'token')
  56. self.assertEqual(payload['initiator']['credential']['identity_status'],
  57. 'Confirmed')
  58. self.assertNotIn('reason', payload)
  59. self.assertNotIn('reporterchain', payload)
  60. self.assertEqual(payload['observer']['id'], 'target')
  61. self.assertEqual(path, payload['requestPath'])
  62. def test_get_read(self):
  63. url = 'http://admin_host:8774/v2/%s/servers/%s' % (uuid.uuid4().hex,
  64. uuid.uuid4().hex)
  65. payload = self.get_payload('GET', url)
  66. self.assertEqual(payload['target']['typeURI'],
  67. 'service/compute/servers/server')
  68. self.assertEqual(payload['action'], 'read')
  69. self.assertEqual(payload['outcome'], 'pending')
  70. def test_get_unknown_endpoint(self):
  71. url = 'http://unknown:8774/v2/' + str(uuid.uuid4()) + '/servers'
  72. payload = self.get_payload('GET', url)
  73. self.assertEqual(payload['action'], 'read/list')
  74. self.assertEqual(payload['outcome'], 'pending')
  75. self.assertEqual(payload['target']['name'], 'unknown')
  76. self.assertEqual(payload['target']['id'], 'unknown')
  77. self.assertEqual(payload['target']['typeURI'], 'unknown')
  78. def test_get_unknown_endpoint_default_set(self):
  79. with open(self.audit_map, "w") as f:
  80. f.write("[DEFAULT]\n")
  81. f.write("target_endpoint_type = compute\n")
  82. f.write("[path_keywords]\n")
  83. f.write("servers = server\n\n")
  84. f.write("[service_endpoints]\n")
  85. f.write("compute = service/compute")
  86. url = 'http://unknown:8774/v2/' + str(uuid.uuid4()) + '/servers'
  87. payload = self.get_payload('GET', url)
  88. self.assertEqual(payload['action'], 'read/list')
  89. self.assertEqual(payload['outcome'], 'pending')
  90. self.assertEqual(payload['target']['name'], 'nova')
  91. self.assertEqual(payload['target']['id'], 'resource_id')
  92. self.assertEqual(payload['target']['typeURI'],
  93. 'service/compute/servers')
  94. def test_put(self):
  95. url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
  96. payload = self.get_payload('PUT', url)
  97. self.assertEqual(payload['target']['typeURI'],
  98. 'service/compute/servers')
  99. self.assertEqual(payload['action'], 'update')
  100. self.assertEqual(payload['outcome'], 'pending')
  101. def test_delete(self):
  102. url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
  103. payload = self.get_payload('DELETE', url)
  104. self.assertEqual(payload['target']['typeURI'],
  105. 'service/compute/servers')
  106. self.assertEqual(payload['action'], 'delete')
  107. self.assertEqual(payload['outcome'], 'pending')
  108. def test_head(self):
  109. url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
  110. payload = self.get_payload('HEAD', url)
  111. self.assertEqual(payload['target']['typeURI'],
  112. 'service/compute/servers')
  113. self.assertEqual(payload['action'], 'read')
  114. self.assertEqual(payload['outcome'], 'pending')
  115. def test_post_update(self):
  116. url = 'http://admin_host:8774/v2/%s/servers/%s' % (uuid.uuid4().hex,
  117. uuid.uuid4().hex)
  118. payload = self.get_payload('POST', url)
  119. self.assertEqual(payload['target']['typeURI'],
  120. 'service/compute/servers/server')
  121. self.assertEqual(payload['action'], 'update')
  122. self.assertEqual(payload['outcome'], 'pending')
  123. def test_post_create(self):
  124. url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
  125. payload = self.get_payload('POST', url)
  126. self.assertEqual(payload['target']['typeURI'],
  127. 'service/compute/servers')
  128. self.assertEqual(payload['action'], 'create')
  129. self.assertEqual(payload['outcome'], 'pending')
  130. def test_post_action(self):
  131. url = 'http://admin_host:8774/v2/%s/servers/action' % uuid.uuid4().hex
  132. body = b'{"createImage" : {"name" : "new-image","metadata": ' \
  133. b'{"ImageType": "Gold","ImageVersion": "2.0"}}}'
  134. payload = self.get_payload('POST', url, body=body)
  135. self.assertEqual(payload['target']['typeURI'],
  136. 'service/compute/servers/action')
  137. self.assertEqual(payload['action'], 'update/createImage')
  138. self.assertEqual(payload['outcome'], 'pending')
  139. def test_post_empty_body_action(self):
  140. url = 'http://admin_host:8774/v2/%s/servers/action' % uuid.uuid4().hex
  141. payload = self.get_payload('POST', url)
  142. self.assertEqual(payload['target']['typeURI'],
  143. 'service/compute/servers/action')
  144. self.assertEqual(payload['action'], 'create')
  145. self.assertEqual(payload['outcome'], 'pending')
  146. def test_custom_action(self):
  147. url = 'http://admin_host:8774/v2/%s/os-hosts/%s/reboot' % (
  148. uuid.uuid4().hex, uuid.uuid4().hex)
  149. payload = self.get_payload('GET', url)
  150. self.assertEqual(payload['target']['typeURI'],
  151. 'service/compute/os-hosts/host/reboot')
  152. self.assertEqual(payload['action'], 'start/reboot')
  153. self.assertEqual(payload['outcome'], 'pending')
  154. def test_custom_action_complex(self):
  155. url = 'http://admin_host:8774/v2/%s/os-migrations' % uuid.uuid4().hex
  156. payload = self.get_payload('GET', url)
  157. self.assertEqual(payload['target']['typeURI'],
  158. 'service/compute/os-migrations')
  159. self.assertEqual(payload['action'], 'read')
  160. payload = self.get_payload('POST', url)
  161. self.assertEqual(payload['target']['typeURI'],
  162. 'service/compute/os-migrations')
  163. self.assertEqual(payload['action'], 'create')
  164. def test_response_mod_msg(self):
  165. url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
  166. req = webob.Request.blank(url,
  167. environ=self.get_environ_header('GET'),
  168. remote_addr='192.168.0.1')
  169. req.context = {}
  170. middleware = self.create_simple_middleware()
  171. middleware._process_request(req)
  172. payload = req.environ['cadf_event'].as_dict()
  173. middleware._process_response(req, webob.Response())
  174. payload2 = req.environ['cadf_event'].as_dict()
  175. self.assertEqual(payload['id'], payload2['id'])
  176. self.assertEqual(payload['tags'], payload2['tags'])
  177. self.assertEqual(payload2['outcome'], 'success')
  178. self.assertEqual(payload2['reason']['reasonType'], 'HTTP')
  179. self.assertEqual(payload2['reason']['reasonCode'], '200')
  180. self.assertEqual(len(payload2['reporterchain']), 1)
  181. self.assertEqual(payload2['reporterchain'][0]['role'], 'modifier')
  182. self.assertEqual(payload2['reporterchain'][0]['reporter']['id'],
  183. 'target')
  184. def test_missing_catalog_endpoint_id(self):
  185. env_headers = {'HTTP_X_SERVICE_CATALOG':
  186. '''[{"endpoints_links": [],
  187. "endpoints": [{"adminURL":
  188. "http://admin_host:8774",
  189. "region": "RegionOne",
  190. "publicURL":
  191. "http://public_host:8774",
  192. "internalURL":
  193. "http://internal_host:8774"}],
  194. "type": "compute",
  195. "name": "nova"}]''',
  196. 'HTTP_X_USER_ID': 'user_id',
  197. 'HTTP_X_USER_NAME': 'user_name',
  198. 'HTTP_X_AUTH_TOKEN': 'token',
  199. 'HTTP_X_PROJECT_ID': 'tenant_id',
  200. 'HTTP_X_IDENTITY_STATUS': 'Confirmed',
  201. 'REQUEST_METHOD': 'GET'}
  202. url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
  203. payload = self.get_payload('GET', url, environ=env_headers)
  204. self.assertEqual(payload['target']['id'], 'nova')
  205. def test_endpoint_missing_internal_url(self):
  206. env_headers = {'HTTP_X_SERVICE_CATALOG':
  207. '''[{"endpoints_links": [],
  208. "endpoints": [{"adminURL":
  209. "http://admin_host:8774",
  210. "region": "RegionOne",
  211. "publicURL":
  212. "http://public_host:8774"}],
  213. "type": "compute",
  214. "name": "nova"}]''',
  215. 'HTTP_X_USER_ID': 'user_id',
  216. 'HTTP_X_USER_NAME': 'user_name',
  217. 'HTTP_X_AUTH_TOKEN': 'token',
  218. 'HTTP_X_PROJECT_ID': 'tenant_id',
  219. 'HTTP_X_IDENTITY_STATUS': 'Confirmed',
  220. 'REQUEST_METHOD': 'GET'}
  221. url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
  222. payload = self.get_payload('GET', url, environ=env_headers)
  223. self.assertEqual((payload['target']['addresses'][1]['url']), "unknown")
  224. def test_endpoint_missing_public_url(self):
  225. env_headers = {'HTTP_X_SERVICE_CATALOG':
  226. '''[{"endpoints_links": [],
  227. "endpoints": [{"adminURL":
  228. "http://admin_host:8774",
  229. "region": "RegionOne",
  230. "internalURL":
  231. "http://internal_host:8774"}],
  232. "type": "compute",
  233. "name": "nova"}]''',
  234. 'HTTP_X_USER_ID': 'user_id',
  235. 'HTTP_X_USER_NAME': 'user_name',
  236. 'HTTP_X_AUTH_TOKEN': 'token',
  237. 'HTTP_X_PROJECT_ID': 'tenant_id',
  238. 'HTTP_X_IDENTITY_STATUS': 'Confirmed',
  239. 'REQUEST_METHOD': 'GET'}
  240. url = 'http://admin_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
  241. payload = self.get_payload('GET', url, environ=env_headers)
  242. self.assertEqual((payload['target']['addresses'][2]['url']), "unknown")
  243. def test_endpoint_missing_admin_url(self):
  244. env_headers = {'HTTP_X_SERVICE_CATALOG':
  245. '''[{"endpoints_links": [],
  246. "endpoints": [{"region": "RegionOne",
  247. "publicURL":
  248. "http://public_host:8774",
  249. "internalURL":
  250. "http://internal_host:8774"}],
  251. "type": "compute",
  252. "name": "nova"}]''',
  253. 'HTTP_X_USER_ID': 'user_id',
  254. 'HTTP_X_USER_NAME': 'user_name',
  255. 'HTTP_X_AUTH_TOKEN': 'token',
  256. 'HTTP_X_PROJECT_ID': 'tenant_id',
  257. 'HTTP_X_IDENTITY_STATUS': 'Confirmed',
  258. 'REQUEST_METHOD': 'GET'}
  259. url = 'http://public_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
  260. payload = self.get_payload('GET', url, environ=env_headers)
  261. self.assertEqual((payload['target']['addresses'][0]['url']), "unknown")
  262. def test_service_with_no_endpoints(self):
  263. env_headers = {'HTTP_X_SERVICE_CATALOG':
  264. '''[{"endpoints_links": [],
  265. "endpoints": [],
  266. "type": "foo",
  267. "name": "bar"}]''',
  268. 'HTTP_X_USER_ID': 'user_id',
  269. 'HTTP_X_USER_NAME': 'user_name',
  270. 'HTTP_X_AUTH_TOKEN': 'token',
  271. 'HTTP_X_PROJECT_ID': 'tenant_id',
  272. 'HTTP_X_IDENTITY_STATUS': 'Confirmed',
  273. 'REQUEST_METHOD': 'GET'}
  274. url = 'http://public_host:8774/v2/' + str(uuid.uuid4()) + '/servers'
  275. payload = self.get_payload('GET', url, environ=env_headers)
  276. self.assertEqual(payload['target']['name'], "unknown")
  277. def test_endpoint_no_service_port(self):
  278. with open(self.audit_map, "w") as f:
  279. f.write("[DEFAULT]\n")
  280. f.write("target_endpoint_type = load-balancer\n")
  281. f.write("[path_keywords]\n")
  282. f.write("loadbalancers = loadbalancer\n\n")
  283. f.write("[service_endpoints]\n")
  284. f.write("load-balancer = service/load-balancer")
  285. env_headers = {'HTTP_X_SERVICE_CATALOG':
  286. '''[{"endpoints_links": [],
  287. "endpoints": [{"adminURL":
  288. "http://admin_host/compute",
  289. "region": "RegionOne",
  290. "publicURL":
  291. "http://public_host/compute"}],
  292. "type": "compute",
  293. "name": "nova"},
  294. {"endpoints_links": [],
  295. "endpoints": [{"adminURL":
  296. "http://admin_host/load-balancer",
  297. "region": "RegionOne",
  298. "publicURL":
  299. "http://public_host/load-balancer"}],
  300. "type": "load-balancer",
  301. "name": "octavia"}]''',
  302. 'HTTP_X_USER_ID': 'user_id',
  303. 'HTTP_X_USER_NAME': 'user_name',
  304. 'HTTP_X_AUTH_TOKEN': 'token',
  305. 'HTTP_X_PROJECT_ID': 'tenant_id',
  306. 'HTTP_X_IDENTITY_STATUS': 'Confirmed',
  307. 'REQUEST_METHOD': 'GET'}
  308. url = ('http://admin_host/load-balancer/v2/loadbalancers/' +
  309. str(uuid.uuid4()))
  310. payload = self.get_payload('GET', url, environ=env_headers)
  311. self.assertEqual(payload['target']['id'], 'octavia')
  312. def test_no_auth_token(self):
  313. # Test cases where API requests such as Swift list public containers
  314. # which does not require an auth token. In these cases, CADF event
  315. # should have the defaults (i.e taxonomy.UNKNOWN) instead of raising
  316. # an exception.
  317. env_headers = {'HTTP_X_IDENTITY_STATUS': 'Invalid',
  318. 'REQUEST_METHOD': 'GET'}
  319. path = '/v1/' + str(uuid.uuid4())
  320. url = 'https://23.253.72.207' + path
  321. payload = self.get_payload('GET', url, environ=env_headers)
  322. self.assertEqual(payload['action'], 'read')
  323. self.assertEqual(payload['typeURI'],
  324. 'http://schemas.dmtf.org/cloud/audit/1.0/event')
  325. self.assertEqual(payload['outcome'], 'pending')
  326. self.assertEqual(payload['eventType'], 'activity')
  327. self.assertEqual(payload['target']['name'], taxonomy.UNKNOWN)
  328. self.assertEqual(payload['target']['id'], taxonomy.UNKNOWN)
  329. self.assertEqual(payload['target']['typeURI'], taxonomy.UNKNOWN)
  330. self.assertNotIn('addresses', payload['target'])
  331. self.assertEqual(payload['initiator']['id'], taxonomy.UNKNOWN)
  332. self.assertEqual(payload['initiator']['name'], taxonomy.UNKNOWN)
  333. self.assertEqual(payload['initiator']['project_id'],
  334. taxonomy.UNKNOWN)
  335. self.assertEqual(payload['initiator']['host']['address'],
  336. '192.168.0.1')
  337. self.assertEqual(payload['initiator']['typeURI'],
  338. 'service/security/account/user')
  339. self.assertNotEqual(payload['initiator']['credential']['token'],
  340. None)
  341. self.assertEqual(payload['initiator']['credential']['identity_status'],
  342. 'Invalid')
  343. self.assertNotIn('reason', payload)
  344. self.assertNotIn('reporterchain', payload)
  345. self.assertEqual(payload['observer']['id'], 'target')
  346. self.assertEqual(path, payload['requestPath'])
  347. def test_request_and_global_request_id(self):
  348. path = '/v1/' + str(uuid.uuid4())
  349. url = 'https://23.253.72.207' + path
  350. request_id = 'req-%s' % uuid.uuid4()
  351. global_request_id = 'req-%s' % uuid.uuid4()
  352. env_headers = self.get_environ_header('GET')
  353. env_headers['openstack.request_id'] = request_id
  354. env_headers['openstack.global_request_id'] = global_request_id
  355. payload = self.get_payload('GET', url, environ=env_headers)
  356. self.assertEqual(payload['initiator']['request_id'], request_id)
  357. self.assertEqual(payload['initiator']['global_request_id'],
  358. global_request_id)
  359. payload = self.get_payload('GET', url)
  360. self.assertNotIn('request_id', payload['initiator'])
  361. self.assertNotIn('global_request_id', payload['initiator'])