test_pillar.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: Erik Johnson <erik@saltstack.com>
  4. '''
  5. # Import Python libs
  6. from __future__ import absolute_import
  7. import copy
  8. import errno
  9. import logging
  10. import os
  11. import shutil
  12. import textwrap
  13. import subprocess
  14. # Import Salt Testing libs
  15. from tests.support.runtests import RUNTIME_VARS
  16. from tests.support.case import ModuleCase
  17. from tests.support.unit import skipIf
  18. from tests.support.helpers import requires_system_grains, dedent
  19. from tests.support.runtests import RUNTIME_VARS
  20. # Import salt libs
  21. import salt.utils.files
  22. import salt.utils.path
  23. import salt.utils.stringutils
  24. import salt.utils.yaml
  25. import salt.pillar as pillar
  26. log = logging.getLogger(__name__)
  27. GPG_HOMEDIR = os.path.join(RUNTIME_VARS.TMP_CONF_DIR, 'gpgkeys')
  28. PILLAR_BASE = os.path.join(RUNTIME_VARS.TMP, 'test-decrypt-pillar', 'pillar')
  29. TOP_SLS = os.path.join(PILLAR_BASE, 'top.sls')
  30. GPG_SLS = os.path.join(PILLAR_BASE, 'gpg.sls')
  31. DEFAULT_OPTS = {
  32. 'cachedir': os.path.join(RUNTIME_VARS.TMP, 'rootdir', 'cache'),
  33. 'config_dir': RUNTIME_VARS.TMP_CONF_DIR,
  34. 'optimization_order': [0, 1, 2],
  35. 'extension_modules': os.path.join(RUNTIME_VARS.TMP,
  36. 'test-decrypt-pillar',
  37. 'extmods'),
  38. 'pillar_roots': {'base': [PILLAR_BASE]},
  39. 'ext_pillar_first': False,
  40. 'ext_pillar': [],
  41. 'decrypt_pillar_default': 'gpg',
  42. 'decrypt_pillar_delimiter': ':',
  43. 'decrypt_pillar_renderers': ['gpg'],
  44. }
  45. ADDITIONAL_OPTS = (
  46. 'conf_file',
  47. 'file_roots',
  48. 'state_top',
  49. 'renderer',
  50. 'renderer_whitelist',
  51. 'renderer_blacklist',
  52. )
  53. TEST_KEY = '''\
  54. -----BEGIN PGP PRIVATE KEY BLOCK-----
  55. lQOYBFiKrcYBCADAj92+fz20uKxxH0ffMwcryGG9IogkiUi2QrNYilB4hwrY5Qt7
  56. Sbywlk/mSDMcABxMxS0vegqc5pgglvAnsi9w7j//9nfjiirsyiTYOOD1akTFQr7b
  57. qT6zuGFA4oYmYHvfBOena485qvlyitYLKYT9h27TDiiH6Jgt4xSRbjeyhTf3/fKD
  58. JzHA9ii5oeVi1pH/8/4USgXanBdKwO0JKQtci+PF0qe/nkzRswqTIkdgx1oyNUqL
  59. tYJ0XPOy+UyOC4J4QDIt9PQbAmiur8By4g2lLYWlGOCjs7Fcj3n5meWKzf1pmXoY
  60. lAnSab8kUZSSkoWQoTO7RbjFypULKCZui45/ABEBAAEAB/wM1wsAMtfYfx/wgxd1
  61. yJ9HyhrKU80kMotIq/Xth3uKLecJQ2yakfYlCEDXqCTQTymT7OnwaoDeqXmnYqks
  62. 3HLRYvGdjb+8ym/GTkxapqBJfQaM6MB1QTnPHhJOE0zCrlhULK2NulxYihAMFTnk
  63. kKYviaJYLG+DcH0FQkkS0XihTKcqnsoJiS6iNd5SME3pa0qijR0D5f78fkvNzzEE
  64. 9vgAX1TgQ5PDJGN6nYlW2bWxTcg+FR2cUAQPTiP9wXCH6VyJoQay7KHVr3r/7SsU
  65. 89otfcx5HVDYPrez6xnP6wN0P/mKxCDbkERLDjZjWOmNXg2zn+/t3u02e+ybfAIp
  66. kTTxBADY/FmPgLpJ2bpcPH141twpHwhKIbENlTB9745Qknr6aLA0QVCkz49/3joO
  67. Sj+SZ7Jhl6cfbynrfHwX3b1bOFTzBUH2Tsi0HX40PezEFH0apf55FLZuMOBt/lc1
  68. ET6evpIHF0dcM+BvZa7E7MyTyEq8S7Cc9RoJyfeGbS7MG5FfuwQA4y9QOb/OQglq
  69. ZffkVItwY52RKWb/b2WQmt+IcVax/j7DmBva765SIfPDvOCMrYhJBI/uYHQ0Zia7
  70. SnC9+ez55wdYqgHkYojc21CIOnUvsPSj+rOpryoXzmcTuvKeVIyIA0h/mQyWjimR
  71. ENrikC4+O8GBMY6V4uvS4EFhLfHE9g0D/20lNOKkpAKPenr8iAPWcl0/pijJCGxF
  72. agnT7O2GQ9Lr5hSjW86agkevbGktu2ja5t/fHq0wpLQ4DVLMrR0/poaprTr307kW
  73. AlQV3z/C2cMHNysz4ulOgQrudQbhUEz2A8nQxRtIfWunkEugKLr1QiCkE1LJW8Np
  74. ZLxE6Qp0/KzdQva0HVNhbHQgR1BHIDxlcmlrQHNhbHRzdGFjay5jb20+iQFUBBMB
  75. CAA+FiEE+AxQ1ELHGEyFTZPYw5x3k9EbHGsFAliKrcYCGwMFCQPCZwAFCwkIBwIG
  76. FQgJCgsCBBYCAwECHgECF4AACgkQw5x3k9EbHGubUAf+PLdp1oTLVokockZgLyIQ
  77. wxOd3ofNOgNk4QoAkSMNSbtnYoQFKumRw/yGyPSIoHMsOC/ga98r8TAJEKfx3DLA
  78. rsD34oMAaYUT+XUd0KoSmlHqBrtDD1+eBASKYsCosHpCiKuQFfLKSxvpEr2YyL8L
  79. X3Q2TY5zFlGA9Eeq5g+rlb++yRZrruFN28EWtY/pyXFZgIB30ReDwPkM9hrioPZM
  80. 0Qf3+dWZSK1rWViclB51oNy4un9stTiFZptAqz4NTNssU5A4AcNQPwBwnKIYoE58
  81. Y/Zyv8HzILGykT+qFebqRlRBI/13eHdzgJOL1iPRfjTk5Cvr+vcyIxAklXOP81ja
  82. B50DmARYiq3GAQgArnzu4SPCCQGNcCNxN4QlMP5TNvRsm5KrPbcO9j8HPfB+DRXs
  83. 6B3mnuR6OJg7YuC0C2A/m2dSHJKkF0f2AwFRpxLjJ2iAFbrZAW/N0vZDx8zO+YAU
  84. HyLu0V04wdCE5DTLkgfWNR+0uMa8qZ4Kn56Gv7O+OFE7zgTHeZ7psWlxdafeW7u6
  85. zlC/3DWksNtuNb0vQDNMM4vgXbnORIfXdyh41zvEEnr/rKw8DuJAmo20mcv6Qi51
  86. PqqyM62ddQOEVfiMs9l4vmwZAjGFNFNInyPXnogL6UPCDmizb6hh8aX/MwG/XFIG
  87. KMJWbAVGpyBuqljKIt3qLu/s8ouPqkEN+f+nGwARAQABAAf+NA36d/kieGxZpTQ1
  88. oQHP1Jty+OiXhBwP8SPtF0J7ZxuZh07cs+zDsfBok/y6bsepfuFSaIq84OBQis+B
  89. kajxkp3cXZPb7l+lQLv5k++7Dd7Ien+ewSE7TQN6HLwYATrM5n5nBcc1M5C6lQGc
  90. mr0A5yz42TVG2bHsTpi9kBtsaVRSPUHSh8A8T6eOyCrT+/CAJVEEf7JyNyaqH1dy
  91. LuxI1VF3ySDEtFzuwN8EZQP9Yz/4AVyEQEA7WkNEwSQsBi2bWgWEdG+qjqnL+YKa
  92. vwe7/aJYPeL1zICnP/Osd/UcpDxR78MbozstbRljML0fTLj7UJ+XDazwv+Kl0193
  93. 2ZK2QQQAwgXvS19MYNkHO7kbNVLt1VE2ll901iC9GFHBpFUam6gmoHXpCarB+ShH
  94. 8x25aoUu4MxHmFxXd+Zq3d6q2yb57doWoPgvqcefpGmigaITnb1jhV2rt65V8deA
  95. SQazZNqBEBbZNIhfn6ObxHXXvaYaqq/UOEQ7uKyR9WMJT/rmqMEEAOY5h1R1t7AB
  96. JZ5VnhyAhdsNWw1gTcXB3o8gKz4vjdnPm0F4aVIPfB3BukETDc3sc2tKmCfUF7I7
  97. oOrh7iRez5F0RIC3KDzXF8qUuWBfPViww45JgftdKsecCIlEEYCoc+3goX0su2bP
  98. V1MDuHijMGTJCBABDgizNb0oynW5xcrbA/0QnKfpTwi7G3oRcJWv2YebVDRcU+SP
  99. dOYhq6SnmWPizEIljRG/X7FHJB+W7tzryO3sCDTAYwxFrfMwvJ2PwnAYI4349zYd
  100. lC28HowUkBYNhwBXc48xCfyhPZtD0aLx/OX1oLZ/vi8gd8TusgGupV/JjkFVO+Nd
  101. +shN/UEAldwqkkY2iQE8BBgBCAAmFiEE+AxQ1ELHGEyFTZPYw5x3k9EbHGsFAliK
  102. rcYCGwwFCQPCZwAACgkQw5x3k9EbHGu4wwf/dRFat91BRX1TJfwJl5otoAXpItYM
  103. 6kdWWf1Eb1BicAvXhI078MSH4WXdKkJjJr1fFP8Ynil513H4Mzb0rotMAhb0jLSA
  104. lSRkMbhMvPxoS2kaYzioaBpp8yXpGiNo7dF+PJXSm/Uwp3AkcFjoVbBOqDWGgxMi
  105. DvDAstzLZ9dIcmr+OmcRQykKOKXlhEl3HnR5CyuPrA8hdVup4oeVwdkJhfJFKLLb
  106. 3fR26wxJOmIOAt24eAUy721WfQ9txNAmhdy8mY842ODZESw6WatrQjRfuqosDgrk
  107. jc0cCHsEqJNZ2AB+1uEl3tcH0tyAFJa33F0znSonP17SS1Ff9sgHYBVLUg==
  108. =06Tz
  109. -----END PGP PRIVATE KEY BLOCK-----
  110. '''
  111. GPG_PILLAR_YAML = '''\
  112. secrets:
  113. vault:
  114. foo: |
  115. -----BEGIN PGP MESSAGE-----
  116. hQEMAw2B674HRhwSAQgAhTrN8NizwUv/VunVrqa4/X8t6EUulrnhKcSeb8sZS4th
  117. W1Qz3K2NjL4lkUHCQHKZVx/VoZY7zsddBIFvvoGGfj8+2wjkEDwFmFjGE4DEsS74
  118. ZLRFIFJC1iB/O0AiQ+oU745skQkU6OEKxqavmKMrKo3rvJ8ZCXDC470+i2/Hqrp7
  119. +KWGmaDOO422JaSKRm5D9bQZr9oX7KqnrPG9I1+UbJyQSJdsdtquPWmeIpamEVHb
  120. VMDNQRjSezZ1yKC4kCWm3YQbBF76qTHzG1VlLF5qOzuGI9VkyvlMaLfMibriqY73
  121. zBbPzf6Bkp2+Y9qyzuveYMmwS4sEOuZL/PetqisWe9JGAWD/O+slQ2KRu9hNww06
  122. KMDPJRdyj5bRuBVE4hHkkP23KrYr7SuhW2vpe7O/MvWEJ9uDNegpMLhTWruGngJh
  123. iFndxegN9w==
  124. =bAuo
  125. -----END PGP MESSAGE-----
  126. bar: this was unencrypted already
  127. baz: |
  128. -----BEGIN PGP MESSAGE-----
  129. hQEMAw2B674HRhwSAQf+Ne+IfsP2IcPDrUWct8sTJrga47jQvlPCmO+7zJjOVcqz
  130. gLjUKvMajrbI/jorBWxyAbF+5E7WdG9WHHVnuoywsyTB9rbmzuPqYCJCe+ZVyqWf
  131. 9qgJ+oUjcvYIFmH3h7H68ldqbxaAUkAOQbTRHdr253wwaTIC91ZeX0SCj64HfTg7
  132. Izwk383CRWonEktXJpientApQFSUWNeLUWagEr/YPNFA3vzpPF5/Ia9X8/z/6oO2
  133. q+D5W5mVsns3i2HHbg2A8Y+pm4TWnH6mTSh/gdxPqssi9qIrzGQ6H1tEoFFOEq1V
  134. kJBe0izlfudqMq62XswzuRB4CYT5Iqw1c97T+1RqENJCASG0Wz8AGhinTdlU5iQl
  135. JkLKqBxcBz4L70LYWyHhYwYROJWjHgKAywX5T67ftq0wi8APuZl9olnOkwSK+wrY
  136. 1OZi
  137. =7epf
  138. -----END PGP MESSAGE-----
  139. qux:
  140. - foo
  141. - bar
  142. - |
  143. -----BEGIN PGP MESSAGE-----
  144. hQEMAw2B674HRhwSAQgAg1YCmokrweoOI1c9HO0BLamWBaFPTMblOaTo0WJLZoTS
  145. ksbQ3OJAMkrkn3BnnM/djJc5C7vNs86ZfSJ+pvE8Sp1Rhtuxh25EKMqGOn/SBedI
  146. gR6N5vGUNiIpG5Tf3DuYAMNFDUqw8uY0MyDJI+ZW3o3xrMUABzTH0ew+Piz85FDA
  147. YrVgwZfqyL+9OQuu6T66jOIdwQNRX2NPFZqvon8liZUPus5VzD8E5cAL9OPxQ3sF
  148. f7/zE91YIXUTimrv3L7eCgU1dSxKhhfvA2bEUi+AskMWFXFuETYVrIhFJAKnkFmE
  149. uZx+O9R9hADW3hM5hWHKH9/CRtb0/cC84I9oCWIQPdI+AaPtICxtsD2N8Q98hhhd
  150. 4M7I0sLZhV+4ZJqzpUsOnSpaGyfh1Zy/1d3ijJi99/l+uVHuvmMllsNmgR+ZTj0=
  151. =LrCQ
  152. -----END PGP MESSAGE-----
  153. '''
  154. GPG_PILLAR_ENCRYPTED = {
  155. 'secrets': {
  156. 'vault': {
  157. 'foo': '-----BEGIN PGP MESSAGE-----\n'
  158. '\n'
  159. 'hQEMAw2B674HRhwSAQgAhTrN8NizwUv/VunVrqa4/X8t6EUulrnhKcSeb8sZS4th\n'
  160. 'W1Qz3K2NjL4lkUHCQHKZVx/VoZY7zsddBIFvvoGGfj8+2wjkEDwFmFjGE4DEsS74\n'
  161. 'ZLRFIFJC1iB/O0AiQ+oU745skQkU6OEKxqavmKMrKo3rvJ8ZCXDC470+i2/Hqrp7\n'
  162. '+KWGmaDOO422JaSKRm5D9bQZr9oX7KqnrPG9I1+UbJyQSJdsdtquPWmeIpamEVHb\n'
  163. 'VMDNQRjSezZ1yKC4kCWm3YQbBF76qTHzG1VlLF5qOzuGI9VkyvlMaLfMibriqY73\n'
  164. 'zBbPzf6Bkp2+Y9qyzuveYMmwS4sEOuZL/PetqisWe9JGAWD/O+slQ2KRu9hNww06\n'
  165. 'KMDPJRdyj5bRuBVE4hHkkP23KrYr7SuhW2vpe7O/MvWEJ9uDNegpMLhTWruGngJh\n'
  166. 'iFndxegN9w==\n'
  167. '=bAuo\n'
  168. '-----END PGP MESSAGE-----\n',
  169. 'bar': 'this was unencrypted already',
  170. 'baz': '-----BEGIN PGP MESSAGE-----\n'
  171. '\n'
  172. 'hQEMAw2B674HRhwSAQf+Ne+IfsP2IcPDrUWct8sTJrga47jQvlPCmO+7zJjOVcqz\n'
  173. 'gLjUKvMajrbI/jorBWxyAbF+5E7WdG9WHHVnuoywsyTB9rbmzuPqYCJCe+ZVyqWf\n'
  174. '9qgJ+oUjcvYIFmH3h7H68ldqbxaAUkAOQbTRHdr253wwaTIC91ZeX0SCj64HfTg7\n'
  175. 'Izwk383CRWonEktXJpientApQFSUWNeLUWagEr/YPNFA3vzpPF5/Ia9X8/z/6oO2\n'
  176. 'q+D5W5mVsns3i2HHbg2A8Y+pm4TWnH6mTSh/gdxPqssi9qIrzGQ6H1tEoFFOEq1V\n'
  177. 'kJBe0izlfudqMq62XswzuRB4CYT5Iqw1c97T+1RqENJCASG0Wz8AGhinTdlU5iQl\n'
  178. 'JkLKqBxcBz4L70LYWyHhYwYROJWjHgKAywX5T67ftq0wi8APuZl9olnOkwSK+wrY\n'
  179. '1OZi\n'
  180. '=7epf\n'
  181. '-----END PGP MESSAGE-----\n',
  182. 'qux': [
  183. 'foo',
  184. 'bar',
  185. '-----BEGIN PGP MESSAGE-----\n'
  186. '\n'
  187. 'hQEMAw2B674HRhwSAQgAg1YCmokrweoOI1c9HO0BLamWBaFPTMblOaTo0WJLZoTS\n'
  188. 'ksbQ3OJAMkrkn3BnnM/djJc5C7vNs86ZfSJ+pvE8Sp1Rhtuxh25EKMqGOn/SBedI\n'
  189. 'gR6N5vGUNiIpG5Tf3DuYAMNFDUqw8uY0MyDJI+ZW3o3xrMUABzTH0ew+Piz85FDA\n'
  190. 'YrVgwZfqyL+9OQuu6T66jOIdwQNRX2NPFZqvon8liZUPus5VzD8E5cAL9OPxQ3sF\n'
  191. 'f7/zE91YIXUTimrv3L7eCgU1dSxKhhfvA2bEUi+AskMWFXFuETYVrIhFJAKnkFmE\n'
  192. 'uZx+O9R9hADW3hM5hWHKH9/CRtb0/cC84I9oCWIQPdI+AaPtICxtsD2N8Q98hhhd\n'
  193. '4M7I0sLZhV+4ZJqzpUsOnSpaGyfh1Zy/1d3ijJi99/l+uVHuvmMllsNmgR+ZTj0=\n'
  194. '=LrCQ\n'
  195. '-----END PGP MESSAGE-----\n'
  196. ],
  197. },
  198. },
  199. }
  200. GPG_PILLAR_DECRYPTED = {
  201. 'secrets': {
  202. 'vault': {
  203. 'foo': 'supersecret',
  204. 'bar': 'this was unencrypted already',
  205. 'baz': 'rosebud',
  206. 'qux': ['foo', 'bar', 'baz'],
  207. },
  208. },
  209. }
  210. class BasePillarTest(ModuleCase):
  211. '''
  212. Tests for pillar decryption
  213. '''
  214. @classmethod
  215. def setUpClass(cls):
  216. os.makedirs(PILLAR_BASE)
  217. with salt.utils.files.fopen(TOP_SLS, 'w') as fp_:
  218. fp_.write(textwrap.dedent('''\
  219. base:
  220. 'N@mins not L@minion':
  221. - ng1
  222. 'N@missing_minion':
  223. - ng2
  224. '''))
  225. with salt.utils.files.fopen(os.path.join(PILLAR_BASE, 'ng1.sls'), 'w') as fp_:
  226. fp_.write('pillar_from_nodegroup: True')
  227. with salt.utils.files.fopen(os.path.join(PILLAR_BASE, 'ng2.sls'), 'w') as fp_:
  228. fp_.write('pillar_from_nodegroup_with_ghost: True')
  229. @classmethod
  230. def tearDownClass(cls):
  231. shutil.rmtree(PILLAR_BASE)
  232. def _build_opts(self, opts):
  233. ret = copy.deepcopy(DEFAULT_OPTS)
  234. for item in ADDITIONAL_OPTS:
  235. ret[item] = self.master_opts[item]
  236. ret.update(opts)
  237. return ret
  238. def test_pillar_top_compound_match(self, grains=None):
  239. '''
  240. Test that a compound match topfile that refers to a nodegroup via N@ works
  241. as expected.
  242. '''
  243. if not grains:
  244. grains = {}
  245. grains['os'] = 'Fedora'
  246. nodegroup_opts = salt.utils.yaml.safe_load(textwrap.dedent('''\
  247. nodegroups:
  248. min: minion
  249. sub_min: sub_minion
  250. mins: N@min or N@sub_min
  251. missing_minion: L@minion,ghostminion
  252. '''))
  253. opts = self._build_opts(nodegroup_opts)
  254. pillar_obj = pillar.Pillar(opts, grains, 'minion', 'base')
  255. ret = pillar_obj.compile_pillar()
  256. self.assertEqual(ret.get('pillar_from_nodegroup_with_ghost'), True)
  257. self.assertEqual(ret.get('pillar_from_nodegroup'), None)
  258. sub_pillar_obj = pillar.Pillar(opts, grains, 'sub_minion', 'base')
  259. sub_ret = sub_pillar_obj.compile_pillar()
  260. self.assertEqual(sub_ret.get('pillar_from_nodegroup_with_ghost'), None)
  261. self.assertEqual(sub_ret.get('pillar_from_nodegroup'), True)
  262. @skipIf(not salt.utils.path.which('gpg'), 'GPG is not installed')
  263. class DecryptGPGPillarTest(ModuleCase):
  264. '''
  265. Tests for pillar decryption
  266. '''
  267. maxDiff = None
  268. @classmethod
  269. def setUpClass(cls):
  270. try:
  271. os.makedirs(GPG_HOMEDIR, mode=0o700)
  272. except Exception:
  273. cls.created_gpg_homedir = False
  274. raise
  275. else:
  276. cls.created_gpg_homedir = True
  277. cmd_prefix = ['gpg', '--homedir', GPG_HOMEDIR]
  278. cmd = cmd_prefix + ['--list-keys']
  279. log.debug('Instantiating gpg keyring using: %s', cmd)
  280. output = subprocess.Popen(cmd,
  281. stdout=subprocess.PIPE,
  282. stderr=subprocess.STDOUT,
  283. shell=False).communicate()[0]
  284. log.debug('Result:\n%s', output)
  285. cmd = cmd_prefix + ['--import', '--allow-secret-key-import']
  286. log.debug('Importing keypair using: %s', cmd)
  287. output = subprocess.Popen(cmd,
  288. stdin=subprocess.PIPE,
  289. stdout=subprocess.PIPE,
  290. stderr=subprocess.STDOUT,
  291. shell=False).communicate(input=salt.utils.stringutils.to_bytes(TEST_KEY))[0]
  292. log.debug('Result:\n%s', output)
  293. os.makedirs(PILLAR_BASE)
  294. with salt.utils.files.fopen(TOP_SLS, 'w') as fp_:
  295. fp_.write(textwrap.dedent('''\
  296. base:
  297. '*':
  298. - gpg
  299. '''))
  300. with salt.utils.files.fopen(GPG_SLS, 'w') as fp_:
  301. fp_.write(GPG_PILLAR_YAML)
  302. @classmethod
  303. def tearDownClass(cls):
  304. cmd = ['gpg-connect-agent', '--homedir', GPG_HOMEDIR]
  305. try:
  306. log.debug('Killing gpg-agent using: %s', cmd)
  307. output = subprocess.Popen(cmd,
  308. stdin=subprocess.PIPE,
  309. stdout=subprocess.PIPE,
  310. stderr=subprocess.STDOUT,
  311. shell=False).communicate(input=b'KILLAGENT')[0]
  312. log.debug('Result:\n%s', output)
  313. except OSError:
  314. log.debug('No need to kill: old gnupg doesn\'t start the agent.')
  315. if cls.created_gpg_homedir:
  316. try:
  317. shutil.rmtree(GPG_HOMEDIR)
  318. except OSError as exc:
  319. # GPG socket can disappear before rmtree gets to this point
  320. if exc.errno != errno.ENOENT:
  321. raise
  322. shutil.rmtree(PILLAR_BASE)
  323. def _build_opts(self, opts):
  324. ret = copy.deepcopy(DEFAULT_OPTS)
  325. for item in ADDITIONAL_OPTS:
  326. ret[item] = self.master_opts[item]
  327. ret.update(opts)
  328. return ret
  329. @requires_system_grains
  330. def test_decrypt_pillar_default_renderer(self, grains=None):
  331. '''
  332. Test recursive decryption of secrets:vault as well as the fallback to
  333. default decryption renderer.
  334. '''
  335. decrypt_pillar_opts = salt.utils.yaml.safe_load(textwrap.dedent('''\
  336. decrypt_pillar:
  337. - 'secrets:vault'
  338. '''))
  339. opts = self._build_opts(decrypt_pillar_opts)
  340. pillar_obj = pillar.Pillar(opts, grains, 'test', 'base')
  341. ret = pillar_obj.compile_pillar()
  342. self.assertEqual(ret, GPG_PILLAR_DECRYPTED)
  343. @requires_system_grains
  344. def test_decrypt_pillar_alternate_delimiter(self, grains=None):
  345. '''
  346. Test recursive decryption of secrets:vault using a pipe instead of a
  347. colon as the nesting delimiter.
  348. '''
  349. decrypt_pillar_opts = salt.utils.yaml.safe_load(textwrap.dedent('''\
  350. decrypt_pillar_delimiter: '|'
  351. decrypt_pillar:
  352. - 'secrets|vault'
  353. '''))
  354. opts = self._build_opts(decrypt_pillar_opts)
  355. pillar_obj = pillar.Pillar(opts, grains, 'test', 'base')
  356. ret = pillar_obj.compile_pillar()
  357. self.assertEqual(ret, GPG_PILLAR_DECRYPTED)
  358. @requires_system_grains
  359. def test_decrypt_pillar_deeper_nesting(self, grains=None):
  360. '''
  361. Test recursive decryption, only with a more deeply-nested target. This
  362. should leave the other keys in secrets:vault encrypted.
  363. '''
  364. decrypt_pillar_opts = salt.utils.yaml.safe_load(textwrap.dedent('''\
  365. decrypt_pillar:
  366. - 'secrets:vault:qux'
  367. '''))
  368. opts = self._build_opts(decrypt_pillar_opts)
  369. pillar_obj = pillar.Pillar(opts, grains, 'test', 'base')
  370. ret = pillar_obj.compile_pillar()
  371. expected = copy.deepcopy(GPG_PILLAR_ENCRYPTED)
  372. expected['secrets']['vault']['qux'][-1] = \
  373. GPG_PILLAR_DECRYPTED['secrets']['vault']['qux'][-1]
  374. self.assertEqual(ret, expected)
  375. @requires_system_grains
  376. def test_decrypt_pillar_explicit_renderer(self, grains=None):
  377. '''
  378. Test recursive decryption of secrets:vault, with the renderer
  379. explicitly defined, overriding the default. Setting the default to a
  380. nonexistant renderer so we can be sure that the override happened.
  381. '''
  382. decrypt_pillar_opts = salt.utils.yaml.safe_load(textwrap.dedent('''\
  383. decrypt_pillar_default: asdf
  384. decrypt_pillar_renderers:
  385. - asdf
  386. - gpg
  387. decrypt_pillar:
  388. - 'secrets:vault': gpg
  389. '''))
  390. opts = self._build_opts(decrypt_pillar_opts)
  391. pillar_obj = pillar.Pillar(opts, grains, 'test', 'base')
  392. ret = pillar_obj.compile_pillar()
  393. self.assertEqual(ret, GPG_PILLAR_DECRYPTED)
  394. @requires_system_grains
  395. def test_decrypt_pillar_missing_renderer(self, grains=None):
  396. '''
  397. Test decryption using a missing renderer. It should fail, leaving the
  398. encrypted keys intact, and add an error to the pillar dictionary.
  399. '''
  400. decrypt_pillar_opts = salt.utils.yaml.safe_load(textwrap.dedent('''\
  401. decrypt_pillar_default: asdf
  402. decrypt_pillar_renderers:
  403. - asdf
  404. decrypt_pillar:
  405. - 'secrets:vault'
  406. '''))
  407. opts = self._build_opts(decrypt_pillar_opts)
  408. pillar_obj = pillar.Pillar(opts, grains, 'test', 'base')
  409. ret = pillar_obj.compile_pillar()
  410. expected = copy.deepcopy(GPG_PILLAR_ENCRYPTED)
  411. expected['_errors'] = [
  412. 'Failed to decrypt pillar key \'secrets:vault\': Decryption '
  413. 'renderer \'asdf\' is not available'
  414. ]
  415. self.assertEqual(ret['_errors'], expected['_errors'])
  416. self.assertEqual(ret['secrets']['vault']['foo'],
  417. expected['secrets']['vault']['foo'])
  418. self.assertEqual(ret['secrets']['vault']['bar'],
  419. expected['secrets']['vault']['bar'])
  420. self.assertEqual(ret['secrets']['vault']['baz'],
  421. expected['secrets']['vault']['baz'])
  422. self.assertEqual(ret['secrets']['vault']['qux'],
  423. expected['secrets']['vault']['qux'])
  424. @requires_system_grains
  425. def test_decrypt_pillar_invalid_renderer(self, grains=None):
  426. '''
  427. Test decryption using a renderer which is not permitted. It should
  428. fail, leaving the encrypted keys intact, and add an error to the pillar
  429. dictionary.
  430. '''
  431. decrypt_pillar_opts = salt.utils.yaml.safe_load(textwrap.dedent('''\
  432. decrypt_pillar_default: foo
  433. decrypt_pillar_renderers:
  434. - foo
  435. - bar
  436. decrypt_pillar:
  437. - 'secrets:vault': gpg
  438. '''))
  439. opts = self._build_opts(decrypt_pillar_opts)
  440. pillar_obj = pillar.Pillar(opts, grains, 'test', 'base')
  441. ret = pillar_obj.compile_pillar()
  442. expected = copy.deepcopy(GPG_PILLAR_ENCRYPTED)
  443. expected['_errors'] = [
  444. 'Failed to decrypt pillar key \'secrets:vault\': \'gpg\' is '
  445. 'not a valid decryption renderer. Valid choices are: foo, bar'
  446. ]
  447. self.assertEqual(ret['_errors'], expected['_errors'])
  448. self.assertEqual(ret['secrets']['vault']['foo'],
  449. expected['secrets']['vault']['foo'])
  450. self.assertEqual(ret['secrets']['vault']['bar'],
  451. expected['secrets']['vault']['bar'])
  452. self.assertEqual(ret['secrets']['vault']['baz'],
  453. expected['secrets']['vault']['baz'])
  454. self.assertEqual(ret['secrets']['vault']['qux'],
  455. expected['secrets']['vault']['qux'])
  456. class RefreshPillarTest(ModuleCase):
  457. '''
  458. These tests validate the behavior defined in the documentation:
  459. https://docs.saltstack.com/en/latest/topics/pillar/#in-memory-pillar-data-vs-on-demand-pillar-data
  460. These tests also serve as a regression test for:
  461. https://github.com/saltstack/salt/issues/54941
  462. '''
  463. def cleanup_pillars(self, top_path, pillar_path):
  464. os.remove(top_path)
  465. os.remove(pillar_path)
  466. self.run_function('saltutil.refresh_pillar', arg=(True,))
  467. def create_pillar(self, key):
  468. '''
  469. Utility method to create a pillar for the minion and a value of true,
  470. this method also removes and cleans up the pillar at the end of the
  471. test.
  472. '''
  473. top_path = os.path.join(RUNTIME_VARS.TMP_PILLAR_TREE, 'top.sls')
  474. pillar_path = os.path.join(RUNTIME_VARS.TMP_PILLAR_TREE, 'test_pillar.sls')
  475. with salt.utils.files.fopen(top_path, 'w') as fd:
  476. fd.write(dedent('''
  477. base:
  478. 'minion':
  479. - test_pillar
  480. '''))
  481. with salt.utils.files.fopen(pillar_path, 'w') as fd:
  482. fd.write(dedent('''
  483. {}: true
  484. '''.format(key)))
  485. self.addCleanup(self.cleanup_pillars, top_path, pillar_path)
  486. def test_pillar_refresh_pillar_raw(self):
  487. '''
  488. Validate the minion's pillar.raw call behavior for new pillars
  489. '''
  490. key = 'issue-54941-raw'
  491. # We do not expect to see the pillar beacuse it does not exist yet
  492. val = self.run_function('pillar.raw', arg=(key,))
  493. assert val == {}
  494. self.create_pillar(key)
  495. # The pillar exists now but raw reads it from in-memory pillars
  496. val = self.run_function('pillar.raw', arg=(key,))
  497. assert val == {}
  498. # Calling refresh_pillar to update in-memory pillars
  499. ret = self.run_function('saltutil.refresh_pillar', arg=(True,))
  500. # The pillar can now be read from in-memory pillars
  501. val = self.run_function('pillar.raw', arg=(key,))
  502. assert val is True, repr(val)
  503. def test_pillar_refresh_pillar_get(self):
  504. '''
  505. Validate the minion's pillar.get call behavior for new pillars
  506. '''
  507. key = 'issue-54941-get'
  508. # We do not expect to see the pillar beacuse it does not exist yet
  509. val = self.run_function('pillar.get', arg=(key,))
  510. assert val == ''
  511. top_path = os.path.join(RUNTIME_VARS.TMP_PILLAR_TREE, 'top.sls')
  512. pillar_path = os.path.join(RUNTIME_VARS.TMP_PILLAR_TREE, 'test_pillar.sls')
  513. self.create_pillar(key)
  514. # The pillar exists now but get reads it from in-memory pillars, no
  515. # refresh happens
  516. val = self.run_function('pillar.get', arg=(key,))
  517. assert val == ''
  518. # Calling refresh_pillar to update in-memory pillars
  519. ret = self.run_function('saltutil.refresh_pillar', arg=(True,))
  520. assert ret is True
  521. # The pillar can now be read from in-memory pillars
  522. val = self.run_function('pillar.get', arg=(key,))
  523. assert val is True, repr(val)
  524. def test_pillar_refresh_pillar_item(self):
  525. '''
  526. Validate the minion's pillar.item call behavior for new pillars
  527. '''
  528. key = 'issue-54941-item'
  529. # We do not expect to see the pillar beacuse it does not exist yet
  530. val = self.run_function('pillar.item', arg=(key,))
  531. assert key in val
  532. assert val[key] == ''
  533. self.create_pillar(key)
  534. # The pillar exists now but get reads it from in-memory pillars, no
  535. # refresh happens
  536. val = self.run_function('pillar.item', arg=(key,))
  537. assert key in val
  538. assert val[key] == ''
  539. # Calling refresh_pillar to update in-memory pillars
  540. ret = self.run_function('saltutil.refresh_pillar', arg=(True,))
  541. assert ret is True
  542. # The pillar can now be read from in-memory pillars
  543. val = self.run_function('pillar.item', arg=(key,))
  544. assert key in val
  545. assert val[key] is True
  546. def test_pillar_refresh_pillar_items(self):
  547. '''
  548. Validate the minion's pillar.item call behavior for new pillars
  549. '''
  550. key = 'issue-54941-items'
  551. # We do not expect to see the pillar beacuse it does not exist yet
  552. val = self.run_function('pillar.items')
  553. assert key not in val
  554. self.create_pillar(key)
  555. # A pillar.items call sees the pillar right away because a
  556. # refresh_pillar event is fired.
  557. val = self.run_function('pillar.items')
  558. assert key in val
  559. assert val[key] is True
  560. def test_pillar_refresh_pillar_ping(self):
  561. '''
  562. Validate the minion's test.ping does not update pillars
  563. See: https://github.com/saltstack/salt/issues/54941
  564. '''
  565. key = 'issue-54941-ping'
  566. # We do not expect to see the pillar beacuse it does not exist yet
  567. val = self.run_function('pillar.item', arg=(key,))
  568. assert key in val
  569. assert val[key] == ''
  570. self.create_pillar(key)
  571. val = self.run_function('test.ping')
  572. assert val is True
  573. # The pillar exists now but get reads it from in-memory pillars, no
  574. # refresh happens
  575. val = self.run_function('pillar.item', arg=(key,))
  576. assert key in val
  577. assert val[key] == ''
  578. # Calling refresh_pillar to update in-memory pillars
  579. ret = self.run_function('saltutil.refresh_pillar', arg=(True,))
  580. assert ret is True
  581. # The pillar can now be read from in-memory pillars
  582. val = self.run_function('pillar.item', arg=(key,))
  583. assert key in val
  584. assert val[key] is True