1
0

test_x509.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  1. import datetime
  2. import hashlib
  3. import logging
  4. import os
  5. import pprint
  6. import textwrap
  7. import pytest
  8. import salt.utils.files
  9. from tests.support.case import ModuleCase
  10. from tests.support.helpers import slowTest, with_tempfile
  11. from tests.support.mixins import SaltReturnAssertsMixin
  12. from tests.support.runtests import RUNTIME_VARS
  13. from tests.support.unit import skipIf
  14. try:
  15. import M2Crypto # pylint: disable=W0611
  16. HAS_M2CRYPTO = True
  17. except ImportError:
  18. HAS_M2CRYPTO = False
  19. log = logging.getLogger(__name__)
  20. @pytest.mark.usefixtures("salt_sub_minion")
  21. @skipIf(not HAS_M2CRYPTO, "Skip when no M2Crypto found")
  22. class x509Test(ModuleCase, SaltReturnAssertsMixin):
  23. @classmethod
  24. def setUpClass(cls):
  25. cert_path = os.path.join(RUNTIME_VARS.BASE_FILES, "x509_test.crt")
  26. with salt.utils.files.fopen(cert_path) as fp:
  27. cls.x509_cert_text = fp.read()
  28. def setUp(self):
  29. with salt.utils.files.fopen(
  30. os.path.join(RUNTIME_VARS.TMP_PILLAR_TREE, "signing_policies.sls"), "w"
  31. ) as fp:
  32. fp.write(
  33. textwrap.dedent(
  34. """\
  35. x509_signing_policies:
  36. ca_policy:
  37. - minions: '*'
  38. - signing_private_key: {0}/pki/ca.key
  39. - signing_cert: {0}/pki/ca.crt
  40. - O: Test Company
  41. - basicConstraints: "CA:false"
  42. - keyUsage: "critical digitalSignature, keyEncipherment"
  43. - extendedKeyUsage: "critical serverAuth, clientAuth"
  44. - subjectKeyIdentifier: hash
  45. - authorityKeyIdentifier: keyid
  46. - days_valid: 730
  47. - copypath: {0}/pki
  48. compound_match:
  49. - minions: 'G@x509_test_grain:correct_value'
  50. - signing_private_key: {0}/pki/ca.key
  51. - signing_cert: {0}/pki/ca.crt
  52. - O: Test Company
  53. - basicConstraints: "CA:false"
  54. - keyUsage: "critical digitalSignature, keyEncipherment"
  55. - extendedKeyUsage: "critical serverAuth, clientAuth"
  56. - subjectKeyIdentifier: hash
  57. - authorityKeyIdentifier: keyid
  58. - days_valid: 730
  59. - copypath: {0}/pki
  60. """.format(
  61. RUNTIME_VARS.TMP
  62. )
  63. )
  64. )
  65. with salt.utils.files.fopen(
  66. os.path.join(RUNTIME_VARS.TMP_PILLAR_TREE, "top.sls"), "w"
  67. ) as fp:
  68. fp.write(
  69. textwrap.dedent(
  70. """\
  71. base:
  72. '*':
  73. - signing_policies
  74. """
  75. )
  76. )
  77. self.run_function("saltutil.refresh_pillar")
  78. self.run_function(
  79. "grains.set", ["x509_test_grain", "correct_value"], minion_tgt="sub_minion"
  80. )
  81. self.run_function(
  82. "grains.set", ["x509_test_grain", "not_correct_value"], minion_tgt="minion"
  83. )
  84. def tearDown(self):
  85. os.remove(os.path.join(RUNTIME_VARS.TMP_PILLAR_TREE, "signing_policies.sls"))
  86. os.remove(os.path.join(RUNTIME_VARS.TMP_PILLAR_TREE, "top.sls"))
  87. certs_path = os.path.join(RUNTIME_VARS.TMP, "pki")
  88. if os.path.exists(certs_path):
  89. salt.utils.files.rm_rf(certs_path)
  90. self.run_function("saltutil.refresh_pillar")
  91. self.run_function("grains.delkey", ["x509_test_grain"], minion_tgt="sub_minion")
  92. self.run_function("grains.delkey", ["x509_test_grain"], minion_tgt="minion")
  93. def run_function(self, *args, **kwargs): # pylint: disable=arguments-differ
  94. ret = super().run_function(*args, **kwargs)
  95. return ret
  96. @staticmethod
  97. def file_checksum(path):
  98. hash = hashlib.sha1()
  99. with salt.utils.files.fopen(path, "rb") as f:
  100. for block in iter(lambda: f.read(4096), b""):
  101. hash.update(block)
  102. return hash.hexdigest()
  103. @with_tempfile(suffix=".pem", create=False)
  104. @slowTest
  105. def test_issue_49027(self, pemfile):
  106. ret = self.run_state("x509.pem_managed", name=pemfile, text=self.x509_cert_text)
  107. assert isinstance(ret, dict), ret
  108. ret = ret[next(iter(ret))]
  109. assert ret.get("result") is True, ret
  110. with salt.utils.files.fopen(pemfile) as fp:
  111. result = fp.readlines()
  112. self.assertEqual(self.x509_cert_text.splitlines(True), result)
  113. @with_tempfile(suffix=".crt", create=False)
  114. @with_tempfile(suffix=".key", create=False)
  115. @slowTest
  116. def test_issue_49008(self, keyfile, crtfile):
  117. ret = self.run_function(
  118. "state.apply",
  119. ["issue-49008"],
  120. pillar={"keyfile": keyfile, "crtfile": crtfile},
  121. )
  122. assert isinstance(ret, dict), ret
  123. for state_result in ret.values():
  124. assert state_result["result"] is True, state_result
  125. assert os.path.exists(keyfile)
  126. assert os.path.exists(crtfile)
  127. @slowTest
  128. def test_cert_signing(self):
  129. ret = self.run_function(
  130. "state.apply", ["x509.cert_signing"], pillar={"tmp_dir": RUNTIME_VARS.TMP}
  131. )
  132. key = "x509_|-test_crt_|-{}/pki/test.crt_|-certificate_managed".format(
  133. RUNTIME_VARS.TMP
  134. )
  135. assert key in ret
  136. assert "changes" in ret[key]
  137. assert "Certificate" in ret[key]["changes"]
  138. assert "New" in ret[key]["changes"]["Certificate"]
  139. @slowTest
  140. def test_crl_managed(self):
  141. ret = self.run_function(
  142. "state.apply", ["x509.crl_managed"], pillar={"tmp_dir": RUNTIME_VARS.TMP}
  143. )
  144. key = "x509_|-{}/pki/ca.crl_|-{}/pki/ca.crl_|-crl_managed".format(
  145. RUNTIME_VARS.TMP, RUNTIME_VARS.TMP
  146. )
  147. # hints for easier debugging
  148. # import json
  149. # print(json.dumps(ret[key], indent=4, sort_keys=True))
  150. # print(ret[key]['comment'])
  151. assert key in ret
  152. assert "changes" in ret[key]
  153. self.assertEqual(ret[key]["result"], True)
  154. assert "New" in ret[key]["changes"]
  155. assert "Revoked Certificates" in ret[key]["changes"]["New"]
  156. self.assertEqual(
  157. ret[key]["changes"]["Old"],
  158. "{}/pki/ca.crl does not exist.".format(RUNTIME_VARS.TMP),
  159. )
  160. @slowTest
  161. def test_crl_managed_replacing_existing_crl(self):
  162. os.mkdir(os.path.join(RUNTIME_VARS.TMP, "pki"))
  163. with salt.utils.files.fopen(
  164. os.path.join(RUNTIME_VARS.TMP, "pki/ca.crl"), "wb"
  165. ) as crl_file:
  166. crl_file.write(
  167. b"""-----BEGIN RSA PRIVATE KEY-----
  168. MIICWwIBAAKBgQCjdjbgL4kQ8Lu73xeRRM1q3C3K3ptfCLpyfw38LRnymxaoJ6ls
  169. pNSx2dU1uJ89YKFlYLo1QcEk4rJ2fdIjarV0kuNCY3rC8jYUp9BpAU5Z6p9HKeT1
  170. 2rTPH81JyjbQDR5PyfCyzYOQtpwpB4zIUUK/Go7tTm409xGKbbUFugJNgQIDAQAB
  171. AoGAF24we34U1ZrMLifSRv5nu3OIFNZHyx2DLDpOFOGaII5edwgIXwxZeIzS5Ppr
  172. yO568/8jcdLVDqZ4EkgCwRTgoXRq3a1GLHGFmBdDNvWjSTTMLoozuM0t2zjRmIsH
  173. hUd7tnai9Lf1Bp5HlBEhBU2gZWk+SXqLvxXe74/+BDAj7gECQQDRw1OPsrgTvs3R
  174. 3MNwX6W8+iBYMTGjn6f/6rvEzUs/k6rwJluV7n8ISNUIAxoPy5g5vEYK6Ln/Ttc7
  175. u0K1KNlRAkEAx34qcxjuswavL3biNGE+8LpDJnJx1jaNWoH+ObuzYCCVMusdT2gy
  176. kKuq9ytTDgXd2qwZpIDNmscvReFy10glMQJAXebMz3U4Bk7SIHJtYy7OKQzn0dMj
  177. 35WnRV81c2Jbnzhhu2PQeAvt/i1sgEuzLQL9QEtSJ6wLJ4mJvImV0TdaIQJAAYyk
  178. TcKK0A8kOy0kMp3yvDHmJZ1L7wr7bBGIZPBlQ0Ddh8i1sJExm1gJ+uN2QKyg/XrK
  179. tDFf52zWnCdVGgDwcQJALW/WcbSEK+JVV6KDJYpwCzWpKIKpBI0F6fdCr1G7Xcwj
  180. c9bcgp7D7xD+TxWWNj4CSXEccJgGr91StV+gFg4ARQ==
  181. -----END RSA PRIVATE KEY-----
  182. """
  183. )
  184. ret = self.run_function(
  185. "state.apply", ["x509.crl_managed"], pillar={"tmp_dir": RUNTIME_VARS.TMP}
  186. )
  187. key = "x509_|-{}/pki/ca.crl_|-{}/pki/ca.crl_|-crl_managed".format(
  188. RUNTIME_VARS.TMP, RUNTIME_VARS.TMP
  189. )
  190. # hints for easier debugging
  191. # import json
  192. # print(json.dumps(ret[key], indent=4, sort_keys=True))
  193. # print(ret[key]['comment'])
  194. assert key in ret
  195. assert "changes" in ret[key]
  196. self.assertEqual(ret[key]["result"], True)
  197. assert "New" in ret[key]["changes"]
  198. assert "Revoked Certificates" in ret[key]["changes"]["New"]
  199. self.assertEqual(
  200. ret[key]["changes"]["Old"],
  201. "{}/pki/ca.crl is not a valid CRL.".format(RUNTIME_VARS.TMP),
  202. )
  203. def test_cert_issue_not_before_not_after(self):
  204. ret = self.run_function(
  205. "state.apply",
  206. ["test_cert_not_before_not_after"],
  207. pillar={"tmp_dir": RUNTIME_VARS.TMP},
  208. )
  209. key = "x509_|-test_crt_|-{}/pki/test.crt_|-certificate_managed".format(
  210. RUNTIME_VARS.TMP
  211. )
  212. assert key in ret
  213. assert "changes" in ret[key]
  214. assert "Certificate" in ret[key]["changes"]
  215. assert "New" in ret[key]["changes"]["Certificate"]
  216. assert "Not Before" in ret[key]["changes"]["Certificate"]["New"]
  217. assert "Not After" in ret[key]["changes"]["Certificate"]["New"]
  218. not_before = ret[key]["changes"]["Certificate"]["New"]["Not Before"]
  219. not_after = ret[key]["changes"]["Certificate"]["New"]["Not After"]
  220. assert not_before == "2019-05-05 00:00:00"
  221. assert not_after == "2020-05-05 14:30:00"
  222. def test_cert_issue_not_before(self):
  223. ret = self.run_function(
  224. "state.apply",
  225. ["test_cert_not_before"],
  226. pillar={"tmp_dir": RUNTIME_VARS.TMP},
  227. )
  228. key = "x509_|-test_crt_|-{}/pki/test.crt_|-certificate_managed".format(
  229. RUNTIME_VARS.TMP
  230. )
  231. assert key in ret
  232. assert "changes" in ret[key]
  233. assert "Certificate" in ret[key]["changes"]
  234. assert "New" in ret[key]["changes"]["Certificate"]
  235. assert "Not Before" in ret[key]["changes"]["Certificate"]["New"]
  236. assert "Not After" in ret[key]["changes"]["Certificate"]["New"]
  237. not_before = ret[key]["changes"]["Certificate"]["New"]["Not Before"]
  238. assert not_before == "2019-05-05 00:00:00"
  239. def test_cert_issue_not_after(self):
  240. ret = self.run_function(
  241. "state.apply", ["test_cert_not_after"], pillar={"tmp_dir": RUNTIME_VARS.TMP}
  242. )
  243. key = "x509_|-test_crt_|-{}/pki/test.crt_|-certificate_managed".format(
  244. RUNTIME_VARS.TMP
  245. )
  246. assert key in ret
  247. assert "changes" in ret[key]
  248. assert "Certificate" in ret[key]["changes"]
  249. assert "New" in ret[key]["changes"]["Certificate"]
  250. assert "Not Before" in ret[key]["changes"]["Certificate"]["New"]
  251. assert "Not After" in ret[key]["changes"]["Certificate"]["New"]
  252. not_after = ret[key]["changes"]["Certificate"]["New"]["Not After"]
  253. assert not_after == "2020-05-05 14:30:00"
  254. @with_tempfile(suffix=".crt", create=False)
  255. @with_tempfile(suffix=".key", create=False)
  256. def test_issue_41858(self, keyfile, crtfile):
  257. ret_key = "x509_|-test_crt_|-{}_|-certificate_managed".format(crtfile)
  258. signing_policy = "no_such_policy"
  259. ret = self.run_function(
  260. "state.apply",
  261. ["issue-41858.gen_cert"],
  262. pillar={
  263. "keyfile": keyfile,
  264. "crtfile": crtfile,
  265. "tmp_dir": RUNTIME_VARS.TMP,
  266. },
  267. )
  268. self.assertTrue(ret[ret_key]["result"])
  269. cert_sum = self.file_checksum(crtfile)
  270. ret = self.run_function(
  271. "state.apply",
  272. ["issue-41858.check"],
  273. pillar={
  274. "keyfile": keyfile,
  275. "crtfile": crtfile,
  276. "signing_policy": signing_policy,
  277. },
  278. )
  279. self.assertFalse(ret[ret_key]["result"])
  280. # self.assertSaltCommentRegexpMatches(ret[ret_key], "Signing policy {0} does not exist".format(signing_policy))
  281. self.assertEqual(self.file_checksum(crtfile), cert_sum)
  282. @with_tempfile(suffix=".crt", create=False)
  283. @with_tempfile(suffix=".key", create=False)
  284. def test_compound_match_minion_have_correct_grain_value(self, keyfile, crtfile):
  285. ret_key = "x509_|-test_crt_|-{}_|-certificate_managed".format(crtfile)
  286. signing_policy = "compound_match"
  287. ret = self.run_function(
  288. "state.apply",
  289. ["x509_compound_match.gen_ca"],
  290. pillar={"tmp_dir": RUNTIME_VARS.TMP},
  291. )
  292. # sub_minion have grain set and CA is on other minion
  293. # CA minion have same grain with incorrect value
  294. ret = self.run_function(
  295. "state.apply",
  296. ["x509_compound_match.check"],
  297. minion_tgt="sub_minion",
  298. pillar={
  299. "keyfile": keyfile,
  300. "crtfile": crtfile,
  301. "signing_policy": signing_policy,
  302. },
  303. )
  304. self.assertTrue(ret[ret_key]["result"])
  305. @with_tempfile(suffix=".crt", create=False)
  306. @with_tempfile(suffix=".key", create=False)
  307. def test_compound_match_ca_have_correct_grain_value(self, keyfile, crtfile):
  308. self.run_function(
  309. "grains.set", ["x509_test_grain", "correct_value"], minion_tgt="minion"
  310. )
  311. self.run_function(
  312. "grains.set",
  313. ["x509_test_grain", "not_correct_value"],
  314. minion_tgt="sub_minion",
  315. )
  316. ret_key = "x509_|-test_crt_|-{}_|-certificate_managed".format(crtfile)
  317. signing_policy = "compound_match"
  318. self.run_function(
  319. "state.apply",
  320. ["x509_compound_match.gen_ca"],
  321. pillar={"tmp_dir": RUNTIME_VARS.TMP},
  322. )
  323. ret = self.run_function(
  324. "state.apply",
  325. ["x509_compound_match.check"],
  326. minion_tgt="sub_minion",
  327. pillar={
  328. "keyfile": keyfile,
  329. "crtfile": crtfile,
  330. "signing_policy": signing_policy,
  331. },
  332. )
  333. self.assertFalse(ret[ret_key]["result"])
  334. @with_tempfile(suffix=".crt", create=False)
  335. @with_tempfile(suffix=".key", create=False)
  336. def test_self_signed_cert(self, keyfile, crtfile):
  337. """
  338. Self-signed certificate, no CA.
  339. Run the state twice to confirm the cert is only created once
  340. and its contents don't change.
  341. """
  342. first_run = self.run_function(
  343. "state.apply",
  344. ["x509.self_signed"],
  345. pillar={"keyfile": keyfile, "crtfile": crtfile},
  346. )
  347. key = "x509_|-self_signed_cert_|-{}_|-certificate_managed".format(crtfile)
  348. self.assertIn("New", first_run[key]["changes"]["Certificate"])
  349. self.assertEqual(
  350. "Certificate is valid and up to date",
  351. first_run[key]["changes"]["Status"]["New"],
  352. )
  353. self.assertTrue(os.path.exists(crtfile), "Certificate was not created.")
  354. with salt.utils.files.fopen(crtfile, "r") as first_cert:
  355. cert_contents = first_cert.read()
  356. second_run = self.run_function(
  357. "state.apply",
  358. ["x509.self_signed"],
  359. pillar={"keyfile": keyfile, "crtfile": crtfile},
  360. )
  361. self.assertEqual({}, second_run[key]["changes"])
  362. with salt.utils.files.fopen(crtfile, "r") as second_cert:
  363. self.assertEqual(
  364. cert_contents,
  365. second_cert.read(),
  366. "Certificate contents should not have changed.",
  367. )
  368. @with_tempfile(suffix=".crt", create=False)
  369. @with_tempfile(suffix=".key", create=False)
  370. def test_old_self_signed_cert_is_recreated(self, keyfile, crtfile):
  371. """
  372. Self-signed certificate, no CA.
  373. First create a cert that expires in 30 days, then recreate
  374. the cert because the second state run requires days_remaining
  375. to be at least 90.
  376. """
  377. first_run = self.run_function(
  378. "state.apply",
  379. ["x509.self_signed_expiry"],
  380. pillar={
  381. "keyfile": keyfile,
  382. "crtfile": crtfile,
  383. "days_valid": 30,
  384. "days_remaining": 10,
  385. },
  386. )
  387. key = "x509_|-self_signed_cert_|-{}_|-certificate_managed".format(crtfile)
  388. self.assertEqual(
  389. "Certificate is valid and up to date",
  390. first_run[key]["changes"]["Status"]["New"],
  391. )
  392. expiry = datetime.datetime.strptime(
  393. first_run[key]["changes"]["Certificate"]["New"]["Not After"],
  394. "%Y-%m-%d %H:%M:%S",
  395. )
  396. self.assertEqual(29, (expiry - datetime.datetime.now()).days)
  397. self.assertTrue(os.path.exists(crtfile), "Certificate was not created.")
  398. with salt.utils.files.fopen(crtfile, "r") as first_cert:
  399. cert_contents = first_cert.read()
  400. second_run = self.run_function(
  401. "state.apply",
  402. ["x509.self_signed_expiry"],
  403. pillar={
  404. "keyfile": keyfile,
  405. "crtfile": crtfile,
  406. "days_valid": 180,
  407. "days_remaining": 90,
  408. },
  409. )
  410. self.assertEqual(
  411. "Certificate needs renewal: 29 days remaining but it needs to be at least 90",
  412. second_run[key]["changes"]["Status"]["Old"],
  413. )
  414. expiry = datetime.datetime.strptime(
  415. second_run[key]["changes"]["Certificate"]["New"]["Not After"],
  416. "%Y-%m-%d %H:%M:%S",
  417. )
  418. self.assertEqual(179, (expiry - datetime.datetime.now()).days)
  419. with salt.utils.files.fopen(crtfile, "r") as second_cert:
  420. self.assertNotEqual(
  421. cert_contents,
  422. second_cert.read(),
  423. "Certificate contents should have changed.",
  424. )
  425. @with_tempfile(suffix=".crt", create=False)
  426. @with_tempfile(suffix=".key", create=False)
  427. def test_mismatched_self_signed_cert_is_recreated(self, keyfile, crtfile):
  428. """
  429. Self-signed certificate, no CA.
  430. First create a cert, then run the state again with a different
  431. subjectAltName. The cert should be recreated.
  432. Finally, run once more with the same subjectAltName as the
  433. second run. Nothing should change.
  434. """
  435. first_run = self.run_function(
  436. "state.apply",
  437. ["x509.self_signed_different_properties"],
  438. pillar={
  439. "keyfile": keyfile,
  440. "crtfile": crtfile,
  441. "subjectAltName": "DNS:alt.service.local",
  442. },
  443. )
  444. key = "x509_|-self_signed_cert_|-{}_|-certificate_managed".format(crtfile)
  445. self.assertEqual(
  446. "Certificate is valid and up to date",
  447. first_run[key]["changes"]["Status"]["New"],
  448. )
  449. sans = first_run[key]["changes"]["Certificate"]["New"]["X509v3 Extensions"][
  450. "subjectAltName"
  451. ]
  452. self.assertEqual("DNS:alt.service.local", sans)
  453. self.assertTrue(os.path.exists(crtfile), "Certificate was not created.")
  454. with salt.utils.files.fopen(crtfile, "r") as first_cert:
  455. first_cert_contents = first_cert.read()
  456. second_run_pillar = {
  457. "keyfile": keyfile,
  458. "crtfile": crtfile,
  459. "subjectAltName": "DNS:alt1.service.local, DNS:alt2.service.local",
  460. }
  461. second_run = self.run_function(
  462. "state.apply",
  463. ["x509.self_signed_different_properties"],
  464. pillar=second_run_pillar,
  465. )
  466. self.assertEqual(
  467. "Certificate properties are different: X509v3 Extensions",
  468. second_run[key]["changes"]["Status"]["Old"],
  469. )
  470. sans = second_run[key]["changes"]["Certificate"]["New"]["X509v3 Extensions"][
  471. "subjectAltName"
  472. ]
  473. self.assertEqual("DNS:alt1.service.local, DNS:alt2.service.local", sans)
  474. with salt.utils.files.fopen(crtfile, "r") as second_cert:
  475. second_cert_contents = second_cert.read()
  476. self.assertNotEqual(
  477. first_cert_contents,
  478. second_cert_contents,
  479. "Certificate contents should have changed.",
  480. )
  481. third_run = self.run_function(
  482. "state.apply",
  483. ["x509.self_signed_different_properties"],
  484. pillar=second_run_pillar,
  485. )
  486. self.assertEqual({}, third_run[key]["changes"])
  487. with salt.utils.files.fopen(crtfile, "r") as third_cert:
  488. self.assertEqual(
  489. second_cert_contents,
  490. third_cert.read(),
  491. "Certificate contents should not have changed.",
  492. )
  493. @with_tempfile(suffix=".crt", create=False)
  494. @with_tempfile(suffix=".key", create=False)
  495. def test_certificate_managed_with_managed_private_key_does_not_error(
  496. self, keyfile, crtfile
  497. ):
  498. """
  499. Test using the deprecated managed_private_key arg in certificate_managed does not throw an error.
  500. TODO: Remove this test in Aluminium when the arg is removed.
  501. """
  502. self.run_state("x509.private_key_managed", name=keyfile, bits=4096)
  503. ret = self.run_state(
  504. "x509.certificate_managed",
  505. name=crtfile,
  506. CN="localhost",
  507. signing_private_key=keyfile,
  508. managed_private_key={"name": keyfile, "bits": 4096},
  509. )
  510. key = "x509_|-{0}_|-{0}_|-certificate_managed".format(crtfile)
  511. self.assertEqual(True, ret[key]["result"])
  512. @with_tempfile(suffix=".crt", create=False)
  513. @with_tempfile(suffix=".key", create=False)
  514. def test_file_properties_are_updated(self, keyfile, crtfile):
  515. """
  516. Self-signed certificate, no CA.
  517. First create a cert, then run the state again with different
  518. file mode. The cert should not be recreated, but the file
  519. should be updated.
  520. Finally, run once more with the same file mode as the second
  521. run. Nothing should change.
  522. """
  523. first_run = self.run_function(
  524. "state.apply",
  525. ["x509.self_signed_different_properties"],
  526. pillar={"keyfile": keyfile, "crtfile": crtfile, "fileMode": "0755"},
  527. )
  528. key = "x509_|-self_signed_cert_|-{}_|-certificate_managed".format(crtfile)
  529. self.assertEqual(
  530. "Certificate is valid and up to date",
  531. first_run[key]["changes"]["Status"]["New"],
  532. )
  533. self.assertTrue(os.path.exists(crtfile), "Certificate was not created.")
  534. self.assertEqual("0755", oct(os.stat(crtfile).st_mode)[-4:])
  535. second_run_pillar = {
  536. "keyfile": keyfile,
  537. "crtfile": crtfile,
  538. "mode": "0600",
  539. }
  540. second_run = self.run_function(
  541. "state.apply",
  542. ["x509.self_signed_different_properties"],
  543. pillar=second_run_pillar,
  544. )
  545. self.assertEqual("0600", oct(os.stat(crtfile).st_mode)[-4:])
  546. third_run = self.run_function(
  547. "state.apply",
  548. ["x509.self_signed_different_properties"],
  549. pillar=second_run_pillar,
  550. )
  551. self.assertEqual({}, third_run[key]["changes"])
  552. self.assertEqual("0600", oct(os.stat(crtfile).st_mode)[-4:])
  553. @with_tempfile(suffix=".crt", create=False)
  554. @with_tempfile(suffix=".key", create=False)
  555. def test_file_managed_failure(self, keyfile, crtfile):
  556. """
  557. Test that a failure in the file.managed call marks the state
  558. call as failed.
  559. """
  560. crtfile_pieces = os.path.split(crtfile)
  561. bad_crtfile = os.path.join(
  562. crtfile_pieces[0], "deeply/nested", crtfile_pieces[1]
  563. )
  564. ret = self.run_function(
  565. "state.apply",
  566. ["x509.self_signed_file_error"],
  567. pillar={"keyfile": keyfile, "crtfile": bad_crtfile},
  568. )
  569. key = "x509_|-self_signed_cert_|-{}_|-certificate_managed".format(bad_crtfile)
  570. self.assertFalse(ret[key]["result"], "State should have failed.")
  571. self.assertEqual({}, ret[key]["changes"])
  572. self.assertFalse(
  573. os.path.exists(crtfile), "Certificate should not have been created."
  574. )
  575. @with_tempfile(suffix=".crt", create=False)
  576. @with_tempfile(suffix=".key", create=False)
  577. def test_py2_generated_cert_is_not_recreated(self, keyfile, crtfile):
  578. keyfile_contents = textwrap.dedent(
  579. """\
  580. -----BEGIN RSA PRIVATE KEY-----
  581. MIIEpAIBAAKCAQEAp5PQyx5NlYrfzd7vU/Xb2YR5qbWWtpWWoKmJC1gML5v5DBI7
  582. +p/kAHNNmK8uqHXTaI4N/zgarfjrg4zceq2Du7pP0xiCAYolhFqF78ibxNrN4OkT
  583. UPm2kM88iJ8Z14Yph8ueSxLIlujCGaEFhr6wRzTj4T9b+0Bb/PZHI2t5YwtIooVM
  584. EFCBFkt4bb004tO0D9q0CPPVT2AsGmxnY43Aj3Epy++kqmaWj1hIucSprkDrAXFS
  585. WacBQPFQ8XctnL2Z1Q6CJ5WUNrW8ohAJ9RJkwjiqbZTwYIPSSrl+FO3XqDY70SxU
  586. 3xDeqhU4zvyjxJ8w9SPqTUu/C3BZtRBT9dCBEQIDAQABAoIBAQCZvS23u1RYVrEe
  587. sWGF+LA67aOkg9kCJ1iqiv8UrjF32DNy1KO8OcY2d5H/+u/mUzqh2HmU5QbtBsoi
  588. xS9dSSTrLHGhbAGRogjrVRU9uCDYSBjLN2mmR4IrdkTF3pkZtpcRY0gU/eWTNXUl
  589. iCmGxhj5KtfJxZQAfLon6FW5dBdIOgxSCJhvRq0zFpWJZFGWWkBExDfeNg//0fCU
  590. UbjRjGacP/+R6FSJa6tevzgR7tIIapm1dY/ofPXIXsZGo1R87fRgLI1D+e84Jdds
  591. /U0bKzPOgAjcC1b262lJ8058pjG/nqWC0YUfpIJUVv2ciJpH3Ha+90526InLAUXA
  592. RWe1Z2YxAoGBANqACEKvUbxENu+XxQj0SI1co4SRTOvgbrSQGL61rDY6PvY/bOqC
  593. JeR0KC3MN6e7fx52tsl/eqP9iyExUpO9b0BCnGg967MivJXWUxhUdOL/r2ceQBqD
  594. DiPVZCFsjeNdSNihnNctAig9Po3GEUWE0ikHr3NcD+wXTnhnIEjJ/fltAoGBAMRW
  595. dIcOiuDLm/oDLNCpwEO4m63ymbUgeOj2cZhKMTqFmspnKnuCU1U/A8cuQcs1gydL
  596. 7MzxVP7MZDIEqT5gGj3eyuVMAmKbvLFR2NctDIDjaUs6oz0J9NGByPNjXaYr4uMd
  597. EZrxD8gLZ/G+/7eKsCgBA9ksSydDo00Vf/qAsmO1AoGBANWqc+l59eyrrCj5egU6
  598. lKQf3gsp51WV/8v0SS5dC41vwdgdx80+/fz8FbpLRHVypWlN34sFbRFmQ6Juz/iH
  599. O35UZQyO2KkxI8dGcbWOCUtditHExBzo4W/rIWKJ++pFc5Hb4DqO2dgto7kR4hvg
  600. OX9D869UbIGLfQHCntBvLju1AoGAHpcl0sEmTD4NEFgcTGqWZTbHMsQAxOLJU+rJ
  601. 6iNtJiQY6P5H9TRqDXci/I6te57bz2yZ+ZiEWKq51b06LVjF3evviuhb2sdPEAWj
  602. lmsTbqWAC1OYiXMarOXezGUn+zMNR7uIua5jehSk3lqW9x7psWHvGpA3KWf1cpYt
  603. +XbB1J0CgYBCSjALTv4dcn+CtS3kqb806z8H9MSZznUwSmcgvwCR5sqwLAUk1xRn
  604. hEqXbC1RGee3Xqv9mXPDK2LirpdRYi9Jr9ApZkrSkeaXSd2d4cy2ujUT0c7P8JrD
  605. i6QXb+HaFeBuS5ulYDmo4mIbCysuTsgrLzplViUy3xUQv23M/Eh1gw==
  606. -----END RSA PRIVATE KEY-----
  607. """
  608. )
  609. crtfile_contents = textwrap.dedent(
  610. """\
  611. -----BEGIN CERTIFICATE-----
  612. MIIEhTCCA22gAwIBAgIIUijHgif6VJUwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV
  613. BAYTAkJFMRgwFgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0ExETAPBgNVBAcMCEthcGVs
  614. bGVuMRAwDgYDVQQIDAdBbnR3ZXJwMRAwDgYDVQQKDAdFeGFtcGxlMSIwIAYJKoZI
  615. hvcNAQkBFhNjZXJ0YWRtQGV4YW1wbGUub3JnMB4XDTIwMDYxNjA3Mzk1OVoXDTMw
  616. MDYxNDA3Mzk1OVowgYIxCzAJBgNVBAYTAkJFMRgwFgYDVQQDDA9FeGFtcGxlIFJv
  617. b3QgQ0ExETAPBgNVBAcMCEthcGVsbGVuMRAwDgYDVQQIDAdBbnR3ZXJwMRAwDgYD
  618. VQQKDAdFeGFtcGxlMSIwIAYJKoZIhvcNAQkBFhNjZXJ0YWRtQGV4YW1wbGUub3Jn
  619. MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp5PQyx5NlYrfzd7vU/Xb
  620. 2YR5qbWWtpWWoKmJC1gML5v5DBI7+p/kAHNNmK8uqHXTaI4N/zgarfjrg4zceq2D
  621. u7pP0xiCAYolhFqF78ibxNrN4OkTUPm2kM88iJ8Z14Yph8ueSxLIlujCGaEFhr6w
  622. RzTj4T9b+0Bb/PZHI2t5YwtIooVMEFCBFkt4bb004tO0D9q0CPPVT2AsGmxnY43A
  623. j3Epy++kqmaWj1hIucSprkDrAXFSWacBQPFQ8XctnL2Z1Q6CJ5WUNrW8ohAJ9RJk
  624. wjiqbZTwYIPSSrl+FO3XqDY70SxU3xDeqhU4zvyjxJ8w9SPqTUu/C3BZtRBT9dCB
  625. EQIDAQABo4H8MIH5MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
  626. A1UdDgQWBBTmNsYLuQTxpANgTuw7LRn1qHJsjzCBtgYDVR0jBIGuMIGrgBTmNsYL
  627. uQTxpANgTuw7LRn1qHJsj6GBiKSBhTCBgjELMAkGA1UEBhMCQkUxGDAWBgNVBAMM
  628. D0V4YW1wbGUgUm9vdCBDQTERMA8GA1UEBwwIS2FwZWxsZW4xEDAOBgNVBAgMB0Fu
  629. dHdlcnAxEDAOBgNVBAoMB0V4YW1wbGUxIjAgBgkqhkiG9w0BCQEWE2NlcnRhZG1A
  630. ZXhhbXBsZS5vcmeCCFIox4In+lSVMA0GCSqGSIb3DQEBCwUAA4IBAQBnC1/kK+xr
  631. Vjr5Y2YRjyjm4e8I/nTU+RX2p5K+Yth3CqWO3JuDiV/31UMtPl832n2GWSgXG2pP
  632. B52oeuCP4Re76jqhOmJWY3CKPji+Rs16wj199i9AAcwhSF0rpi5+Fi84HtP3q6pH
  633. cuzZfIPW44aJ5l4k+QvTLoWzr0XujMFcYzI45i3SJqTMs8xdIP5YLN8JXtQSPw9Z
  634. 8/nBKbPj7WTUC9cj9Cw2bz+wTpdRF4XCsUF3Vpl9fP7SK8yvv0I85LZnWQx1eQlv
  635. COAM5HWxUT9bWgv18zXdYkc6VLw6ufQSxxuhLMjJxuK27Ny/F18/xYLRTVnse36d
  636. tPJrseUPmvIK
  637. -----END CERTIFICATE-----
  638. """
  639. )
  640. slsfile = textwrap.dedent(
  641. """\
  642. {%- set ca_key_path = '"""
  643. + keyfile
  644. + """' %}
  645. {%- set ca_crt_path = '"""
  646. + crtfile
  647. + """' %}
  648. certificate.authority::private-key:
  649. x509.private_key_managed:
  650. - name: {{ ca_key_path }}
  651. - backup: True
  652. certificate.authority::certificate:
  653. x509.certificate_managed:
  654. - name: {{ ca_crt_path }}
  655. - signing_private_key: {{ ca_key_path }}
  656. - CN: Example Root CA
  657. - O: Example
  658. - C: BE
  659. - ST: Antwerp
  660. - L: Kapellen
  661. - Email: certadm@example.org
  662. - basicConstraints: "critical CA:true"
  663. - keyUsage: "critical cRLSign, keyCertSign"
  664. - subjectKeyIdentifier: hash
  665. - authorityKeyIdentifier: keyid,issuer:always
  666. - days_valid: 3650
  667. - days_remaining: 0
  668. - backup: True
  669. - require:
  670. - x509: certificate.authority::private-key
  671. """
  672. )
  673. with salt.utils.files.fopen(
  674. os.path.join(RUNTIME_VARS.TMP_STATE_TREE, "cert.sls"), "w"
  675. ) as wfh:
  676. wfh.write(slsfile)
  677. # Generate the certificate twice.
  678. # On the first run, no key nor cert exist.
  679. ret = self.run_function("state.sls", ["cert"])
  680. log.debug(
  681. "First state run ret dictionary:\n%s", pprint.pformat(list(ret.values()))
  682. )
  683. for state_run_id, state_run_details in ret.items():
  684. if state_run_id.endswith("private_key_managed"):
  685. assert state_run_details["result"]
  686. assert "new" in state_run_details["changes"]
  687. if state_run_id.endswith("certificate_managed"):
  688. assert state_run_details["result"]
  689. assert "Certificate" in state_run_details["changes"]
  690. assert "New" in state_run_details["changes"]["Certificate"]
  691. assert "Status" in state_run_details["changes"]
  692. assert "New" in state_run_details["changes"]["Status"]
  693. # On the second run, they exist and should not trigger any modification
  694. ret = self.run_function("state.sls", ["cert"])
  695. log.debug(
  696. "Second state run ret dictionary:\n%s", pprint.pformat(list(ret.values()))
  697. )
  698. for state_run_id, state_run_details in ret.items():
  699. if state_run_id.endswith("private_key_managed"):
  700. assert state_run_details["result"]
  701. assert state_run_details["changes"] == {}
  702. if state_run_id.endswith("certificate_managed"):
  703. assert state_run_details["result"]
  704. assert state_run_details["changes"] == {}
  705. # Now we repleace they key and cert contents with the contents of the above
  706. # call, but under Py2
  707. with salt.utils.files.fopen(keyfile, "w") as wfh:
  708. wfh.write(keyfile_contents)
  709. with salt.utils.files.fopen(keyfile) as rfh:
  710. log.debug("Written keyfile, %r, contents:\n%s", keyfile, rfh.read())
  711. with salt.utils.files.fopen(crtfile, "w") as wfh:
  712. wfh.write(crtfile_contents)
  713. with salt.utils.files.fopen(crtfile) as rfh:
  714. log.debug("Written crtfile, %r, contents:\n%s", crtfile, rfh.read())
  715. # We should not trigger any modification
  716. ret = self.run_function("state.sls", ["cert"])
  717. log.debug(
  718. "Third state run ret dictionary:\n%s", pprint.pformat(list(ret.values()))
  719. )
  720. for state_run_id, state_run_details in ret.items():
  721. if state_run_id.endswith("private_key_managed"):
  722. assert state_run_details["result"]
  723. assert state_run_details["changes"] == {}
  724. if state_run_id.endswith("certificate_managed"):
  725. assert state_run_details["result"]
  726. assert state_run_details["changes"] == {}