TestSecurity.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. #!/usr/bin/env python2.6
  2. '''
  3. Licensed to the Apache Software Foundation (ASF) under one
  4. or more contributor license agreements. See the NOTICE file
  5. distributed with this work for additional information
  6. regarding copyright ownership. The ASF licenses this file
  7. to you under the Apache License, Version 2.0 (the
  8. "License"); you may not use this file except in compliance
  9. with the License. You may obtain a copy of the License at
  10. http://www.apache.org/licenses/LICENSE-2.0
  11. Unless required by applicable law or agreed to in writing, software
  12. distributed under the License is distributed on an "AS IS" BASIS,
  13. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. See the License for the specific language governing permissions and
  15. limitations under the License.
  16. '''
  17. import StringIO
  18. import sys, subprocess
  19. from ambari_agent import NetUtil
  20. from ambari_agent.security import CertificateManager
  21. from mock.mock import MagicMock, patch, ANY
  22. import mock.mock
  23. import unittest
  24. from ambari_agent import ProcessHelper, main
  25. import logging
  26. import signal
  27. from ambari_agent.AmbariConfig import AmbariConfig
  28. import ConfigParser
  29. import ssl
  30. import os
  31. import tempfile
  32. from ambari_agent.Controller import Controller
  33. from ambari_agent import security
  34. aa = mock.mock.mock_open()
  35. class TestSecurity(unittest.TestCase):
  36. def setUp(self):
  37. # disable stdout
  38. out = StringIO.StringIO()
  39. sys.stdout = out
  40. # Create config
  41. self.config = AmbariConfig().getConfig()
  42. # Instantiate CachedHTTPSConnection (skip connect() call)
  43. with patch.object(security.VerifiedHTTPSConnection, "connect"):
  44. self.cachedHTTPSConnection = security.CachedHTTPSConnection(self.config)
  45. def tearDown(self):
  46. # enable stdout
  47. sys.stdout = sys.__stdout__
  48. ### VerifiedHTTPSConnection ###
  49. @patch.object(security.CertificateManager, "initSecurity")
  50. @patch("socket.create_connection")
  51. @patch("ssl.wrap_socket")
  52. def test_VerifiedHTTPSConnection_connect(self, wrap_socket_mock,
  53. create_connection_mock,
  54. init_security_mock):
  55. init_security_mock.return_value = None
  56. self.config.set('security', 'keysdir', '/dummy-keysdir')
  57. connection = security.VerifiedHTTPSConnection("example.com",
  58. self.config.get('server', 'secured_url_port'), self.config)
  59. connection._tunnel_host = False
  60. connection.sock = None
  61. connection.connect()
  62. self.assertTrue(wrap_socket_mock.called)
  63. ### VerifiedHTTPSConnection with no certificates creation
  64. @patch.object(security.CertificateManager, "initSecurity")
  65. @patch("socket.create_connection")
  66. @patch("ssl.wrap_socket")
  67. def test_Verified_HTTPSConnection_non_secure_connect(self, wrap_socket_mock,
  68. create_connection_mock,
  69. init_security_mock):
  70. connection = security.VerifiedHTTPSConnection("example.com",
  71. self.config.get('server', 'secured_url_port'), self.config)
  72. connection._tunnel_host = False
  73. connection.sock = None
  74. connection.connect()
  75. self.assertFalse(init_security_mock.called)
  76. ### VerifiedHTTPSConnection with two-way SSL authentication enabled
  77. @patch.object(security.CertificateManager, "initSecurity")
  78. @patch("socket.create_connection")
  79. @patch("ssl.wrap_socket")
  80. def test_Verified_HTTPSConnection_two_way_ssl_connect(self, wrap_socket_mock,
  81. create_connection_mock,
  82. init_security_mock):
  83. wrap_socket_mock.side_effect=ssl.SSLError()
  84. connection = security.VerifiedHTTPSConnection("example.com",
  85. self.config.get('server', 'secured_url_port'), self.config)
  86. connection._tunnel_host = False
  87. connection.sock = None
  88. try:
  89. connection.connect()
  90. except ssl.SSLError:
  91. pass
  92. self.assertTrue(init_security_mock.called)
  93. ### CachedHTTPSConnection ###
  94. @patch.object(security.VerifiedHTTPSConnection, "connect")
  95. def test_CachedHTTPSConnection_connect(self, vhc_connect_mock):
  96. self.config.set('server', 'hostname', 'dummy.server.hostname')
  97. self.config.set('server', 'secured_url_port', '443')
  98. # Testing not connected case
  99. self.cachedHTTPSConnection.connected = False
  100. self.cachedHTTPSConnection.connect()
  101. self.assertTrue(vhc_connect_mock.called)
  102. vhc_connect_mock.reset_mock()
  103. # Testing already connected case
  104. self.cachedHTTPSConnection.connect()
  105. self.assertFalse(vhc_connect_mock.called)
  106. @patch.object(security.CachedHTTPSConnection, "connect")
  107. def test_forceClear(self, connect_mock):
  108. # Testing if httpsconn instance changed
  109. old = self.cachedHTTPSConnection.httpsconn
  110. self.cachedHTTPSConnection.forceClear()
  111. self.assertNotEqual(old, self.cachedHTTPSConnection.httpsconn)
  112. @patch.object(security.CachedHTTPSConnection, "connect")
  113. def test_request(self, connect_mock):
  114. httpsconn_mock = MagicMock(create = True)
  115. self.cachedHTTPSConnection.httpsconn = httpsconn_mock
  116. dummy_request = MagicMock(create = True)
  117. dummy_request.get_method.return_value = "dummy_get_method"
  118. dummy_request.get_full_url.return_value = "dummy_full_url"
  119. dummy_request.get_data.return_value = "dummy_get_data"
  120. dummy_request.headers = "dummy_headers"
  121. responce_mock = MagicMock(create = True)
  122. responce_mock.read.return_value = "dummy responce"
  123. httpsconn_mock.getresponse.return_value = responce_mock
  124. # Testing normal case
  125. responce = self.cachedHTTPSConnection.request(dummy_request)
  126. self.assertEqual(responce, responce_mock.read.return_value)
  127. httpsconn_mock.request.assert_called_once_with(
  128. dummy_request.get_method.return_value,
  129. dummy_request.get_full_url.return_value,
  130. dummy_request.get_data.return_value,
  131. dummy_request.headers)
  132. # Testing case of exception
  133. try:
  134. def side_eff():
  135. raise Exception("Dummy exception")
  136. httpsconn_mock.read.side_effect = side_eff
  137. responce = self.cachedHTTPSConnection.request(dummy_request)
  138. self.fail("Should raise IOError")
  139. except Exception, err:
  140. # Expected
  141. pass
  142. ### CertificateManager ###
  143. @patch("ambari_agent.hostname.hostname")
  144. def test_getAgentKeyName(self, hostname_mock):
  145. hostname_mock.return_value = "dummy.hostname"
  146. self.config.set('security', 'keysdir', '/dummy-keysdir')
  147. man = CertificateManager(self.config)
  148. res = man.getAgentKeyName()
  149. self.assertEquals(res, "/dummy-keysdir/dummy.hostname.key")
  150. @patch("ambari_agent.hostname.hostname")
  151. def test_getAgentCrtName(self, hostname_mock):
  152. hostname_mock.return_value = "dummy.hostname"
  153. self.config.set('security', 'keysdir', '/dummy-keysdir')
  154. man = CertificateManager(self.config)
  155. res = man.getAgentCrtName()
  156. self.assertEquals(res, "/dummy-keysdir/dummy.hostname.crt")
  157. @patch("ambari_agent.hostname.hostname")
  158. def test_getAgentCrtReqName(self, hostname_mock):
  159. hostname_mock.return_value = "dummy.hostname"
  160. self.config.set('security', 'keysdir', '/dummy-keysdir')
  161. man = CertificateManager(self.config)
  162. res = man.getAgentCrtReqName()
  163. self.assertEquals(res, "/dummy-keysdir/dummy.hostname.csr")
  164. def test_getSrvrCrtName(self):
  165. self.config.set('security', 'keysdir', '/dummy-keysdir')
  166. man = CertificateManager(self.config)
  167. res = man.getSrvrCrtName()
  168. self.assertEquals(res, "/dummy-keysdir/ca.crt")
  169. @patch("os.path.exists")
  170. @patch.object(security.CertificateManager, "loadSrvrCrt")
  171. @patch.object(security.CertificateManager, "getAgentKeyName")
  172. @patch.object(security.CertificateManager, "genAgentCrtReq")
  173. @patch.object(security.CertificateManager, "getAgentCrtName")
  174. @patch.object(security.CertificateManager, "reqSignCrt")
  175. def test_checkCertExists(self, reqSignCrt_mock, getAgentCrtName_mock,
  176. genAgentCrtReq_mock, getAgentKeyName_mock,
  177. loadSrvrCrt_mock, exists_mock):
  178. self.config.set('security', 'keysdir', '/dummy-keysdir')
  179. getAgentKeyName_mock.return_value = "dummy AgentKeyName"
  180. getAgentCrtName_mock.return_value = "dummy AgentCrtName"
  181. man = CertificateManager(self.config)
  182. # Case when all files exist
  183. exists_mock.side_effect = [True, True, True]
  184. man.checkCertExists()
  185. self.assertFalse(loadSrvrCrt_mock.called)
  186. self.assertFalse(genAgentCrtReq_mock.called)
  187. self.assertFalse(reqSignCrt_mock.called)
  188. # Absent server cert
  189. exists_mock.side_effect = [False, True, True]
  190. man.checkCertExists()
  191. self.assertTrue(loadSrvrCrt_mock.called)
  192. self.assertFalse(genAgentCrtReq_mock.called)
  193. self.assertFalse(reqSignCrt_mock.called)
  194. loadSrvrCrt_mock.reset_mock()
  195. # Absent agent key
  196. exists_mock.side_effect = [True, False, True]
  197. man.checkCertExists()
  198. self.assertFalse(loadSrvrCrt_mock.called)
  199. self.assertTrue(genAgentCrtReq_mock.called)
  200. self.assertFalse(reqSignCrt_mock.called)
  201. genAgentCrtReq_mock.reset_mock()
  202. # Absent agent cert
  203. exists_mock.side_effect = [True, True, False]
  204. man.checkCertExists()
  205. self.assertFalse(loadSrvrCrt_mock.called)
  206. self.assertFalse(genAgentCrtReq_mock.called)
  207. self.assertTrue(reqSignCrt_mock.called)
  208. reqSignCrt_mock.reset_mock()
  209. @patch('urllib2.urlopen')
  210. @patch.object(security.CertificateManager, "getSrvrCrtName")
  211. def test_loadSrvrCrt(self, getSrvrCrtName_mock, urlopen_mock):
  212. read_mock = MagicMock(create=True)
  213. read_mock.read.return_value = "dummy_cert"
  214. urlopen_mock.return_value = read_mock
  215. _, tmpoutfile = tempfile.mkstemp()
  216. getSrvrCrtName_mock.return_value = tmpoutfile
  217. man = CertificateManager(self.config)
  218. man.loadSrvrCrt()
  219. # Checking file contents
  220. saved = open(tmpoutfile, 'r').read()
  221. self.assertEqual(saved, read_mock.read.return_value)
  222. os.unlink(tmpoutfile)
  223. @patch("ambari_agent.hostname.hostname")
  224. @patch('__builtin__.open', create=True, autospec=True)
  225. @patch.dict('os.environ', {'DUMMY_PASSPHRASE': 'dummy-passphrase'})
  226. @patch('json.dumps')
  227. @patch('urllib2.Request')
  228. @patch('urllib2.urlopen')
  229. @patch('json.loads')
  230. def test_reqSignCrt(self, loads_mock, urlopen_mock, request_mock, dumps_mock, open_mock, hostname_mock):
  231. self.config.set('security', 'keysdir', '/dummy-keysdir')
  232. self.config.set('security', 'passphrase_env_var_name', 'DUMMY_PASSPHRASE')
  233. man = CertificateManager(self.config)
  234. hostname_mock.return_value = "dummy-hostname"
  235. open_mock.return_value.read.return_value = "dummy_request"
  236. urlopen_mock.return_value.read.return_value = "dummy_server_request"
  237. loads_mock.return_value = {
  238. 'result': 'OK',
  239. 'signedCa': 'dummy-crt'
  240. }
  241. # Test normal server interaction
  242. man.reqSignCrt()
  243. self.assertEqual(dumps_mock.call_args[0][0], {
  244. 'csr' : 'dummy_request',
  245. 'passphrase' : 'dummy-passphrase'
  246. })
  247. self.assertEqual(open_mock.return_value.write.call_args[0][0], 'dummy-crt')
  248. # Test negative server reply
  249. dumps_mock.reset_mock()
  250. open_mock.return_value.write.reset_mock()
  251. loads_mock.return_value = {
  252. 'result': 'FAIL',
  253. 'signedCa': 'fail-crt'
  254. }
  255. # If certificate signing failed, then exception must be raised
  256. try:
  257. man.reqSignCrt()
  258. self.fail()
  259. except ssl.SSLError:
  260. pass
  261. self.assertFalse(open_mock.return_value.write.called)
  262. # Test connection fail
  263. dumps_mock.reset_mock()
  264. open_mock.return_value.write.reset_mock()
  265. try:
  266. man.reqSignCrt()
  267. self.fail("Expected exception here")
  268. except Exception, err:
  269. # expected
  270. pass
  271. @patch("subprocess.Popen")
  272. @patch("subprocess.Popen.communicate")
  273. def test_genAgentCrtReq(self, communicate_mock, popen_mock):
  274. man = CertificateManager(self.config)
  275. p = MagicMock(spec=subprocess.Popen)
  276. p.communicate = communicate_mock
  277. popen_mock.return_value = p
  278. man.genAgentCrtReq()
  279. self.assertTrue(popen_mock.called)
  280. self.assertTrue(communicate_mock.called)
  281. @patch.object(security.CertificateManager, "checkCertExists")
  282. def test_initSecurity(self, checkCertExists_method):
  283. man = CertificateManager(self.config)
  284. man.initSecurity()
  285. self.assertTrue(checkCertExists_method.called)