Skip to content

Commit 0fc2b3d

Browse files
authored
Merge pull request #54991 from mchugh19/port-52126
master-port #52126
2 parents bc0f924 + 318a754 commit 0fc2b3d

File tree

9 files changed

+830
-1
lines changed

9 files changed

+830
-1
lines changed

doc/ref/modules/all/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ execution modules
210210
keyboard
211211
keystone
212212
keystoneng
213+
keystore
213214
kmod
214215
kubernetesmod
215216
launchctl_service
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
=====================
2+
salt.modules.keystore
3+
=====================
4+
5+
.. automodule:: salt.modules.keystore
6+
:members:

doc/ref/states/all/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ state modules
149149
keystone_role_grant
150150
keystone_service
151151
keystone_user
152+
keystore
152153
kmod
153154
kubernetes
154155
layman
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
====================
2+
salt.states.keystore
3+
====================
4+
5+
.. automodule:: salt.states.keystore
6+
:members:

doc/topics/releases/neon.rst

+49-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,54 @@
44
Salt Release Notes - Codename Neon
55
==================================
66

7+
8+
Keystore State and Module
9+
=========================
10+
11+
A new :py:func:`state <salt.states.keystore>` and
12+
:py:func:`execution module <salt.modules.keystore>` for manaing Java
13+
Keystore files is now included. It allows for adding/removing/listing
14+
as well as managing keystore files.
15+
16+
.. code-block:: bash
17+
18+
# salt-call keystore.list /path/to/keystore.jks changeit
19+
local:
20+
|_
21+
----------
22+
alias:
23+
hostname1
24+
expired:
25+
True
26+
sha1:
27+
CB:5E:DE:50:57:99:51:87:8E:2E:67:13:C5:3B:E9:38:EB:23:7E:40
28+
type:
29+
TrustedCertEntry
30+
valid_start:
31+
August 22 2012
32+
valid_until:
33+
August 21 2017
34+
35+
.. code-block:: yaml
36+
37+
define_keystore:
38+
keystore.managed:
39+
- name: /tmp/statestore.jks
40+
- passphrase: changeit
41+
- force_remove: True
42+
- entries:
43+
- alias: hostname1
44+
certificate: /tmp/testcert.crt
45+
- alias: remotehost
46+
certificate: /tmp/512.cert
47+
private_key: /tmp/512.key
48+
- alias: stringhost
49+
certificate: |
50+
-----BEGIN CERTIFICATE-----
51+
MIICEjCCAX
52+
Hn+GmxZA
53+
-----END CERTIFICATE-----
54+
755
Slot Syntax Updates
856
===================
957

@@ -60,4 +108,4 @@ Returner Removal
60108

61109
- The hipchat returner has been removed due to the service being retired. For users migrating
62110
to Slack, the :py:func:`slack <salt.returners.slack_returner>` returner may be a suitable
63-
replacement.
111+
replacement.

salt/modules/keystore.py

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# -*- coding: utf-8 -*-
2+
'''
3+
Module to interact with keystores
4+
'''
5+
6+
# Import Python libs
7+
from __future__ import absolute_import, unicode_literals, print_function
8+
import logging
9+
from datetime import datetime
10+
import os
11+
12+
log = logging.getLogger(__name__)
13+
14+
__virtualname__ = 'keystore'
15+
16+
# Import third party libs
17+
from salt.exceptions import CommandExecutionError, SaltInvocationError
18+
19+
try:
20+
import jks
21+
import OpenSSL
22+
has_depends = True
23+
except ImportError:
24+
has_depends = False
25+
26+
27+
def __virtual__():
28+
'''
29+
Check dependencies
30+
'''
31+
if has_depends is False:
32+
msg = 'jks unavailable: {0} execution module cant be loaded '.format(__virtualname__)
33+
return False, msg
34+
return __virtualname__
35+
36+
37+
def _parse_cert(alias, public_cert, return_cert=False):
38+
ASN1 = OpenSSL.crypto.FILETYPE_ASN1
39+
PEM = OpenSSL.crypto.FILETYPE_PEM
40+
cert_data = {}
41+
sha1 = public_cert.digest(b'sha1')
42+
43+
cert_pem = OpenSSL.crypto.dump_certificate(PEM, public_cert)
44+
raw_until = public_cert.get_notAfter()
45+
date_until = datetime.strptime(raw_until, '%Y%m%d%H%M%SZ')
46+
string_until = date_until.strftime("%B %d %Y")
47+
48+
raw_start = public_cert.get_notBefore()
49+
date_start = datetime.strptime(raw_start, '%Y%m%d%H%M%SZ')
50+
string_start = date_start.strftime("%B %d %Y")
51+
52+
if return_cert:
53+
cert_data['pem'] = cert_pem
54+
cert_data['alias'] = alias
55+
cert_data['sha1'] = sha1
56+
cert_data['valid_until'] = string_until
57+
cert_data['valid_start'] = string_start
58+
cert_data['expired'] = date_until < datetime.now()
59+
60+
return cert_data
61+
62+
63+
def list(keystore, passphrase, alias=None, return_cert=False):
64+
'''
65+
Lists certificates in a keytool managed keystore.
66+
67+
68+
:param keystore: The path to the keystore file to query
69+
:param passphrase: The passphrase to use to decode the keystore
70+
:param alias: (Optional) If found, displays details on only this key
71+
:param return_certs: (Optional) Also return certificate PEM.
72+
73+
.. warning::
74+
75+
There are security implications for using return_cert to return decrypted certificates.
76+
77+
CLI Example:
78+
79+
.. code-block:: bash
80+
81+
salt '*' keystore.list /usr/lib/jvm/java-8/jre/lib/security/cacerts changeit
82+
salt '*' keystore.list /usr/lib/jvm/java-8/jre/lib/security/cacerts changeit debian:verisign_-_g5.pem
83+
84+
'''
85+
ASN1 = OpenSSL.crypto.FILETYPE_ASN1
86+
PEM = OpenSSL.crypto.FILETYPE_PEM
87+
decoded_certs = []
88+
entries = []
89+
90+
keystore = jks.KeyStore.load(keystore, passphrase)
91+
92+
if alias:
93+
# If alias is given, look it up and build expected data structure
94+
entry_value = keystore.entries.get(alias)
95+
if entry_value:
96+
entries = [(alias, entry_value)]
97+
else:
98+
entries = keystore.entries.items()
99+
100+
if entries:
101+
for entry_alias, cert_enc in entries:
102+
entry_data = {}
103+
if isinstance(cert_enc, jks.PrivateKeyEntry):
104+
cert_result = cert_enc.cert_chain[0][1]
105+
entry_data['type'] = 'PrivateKeyEntry'
106+
elif isinstance(cert_enc, jks.TrustedCertEntry):
107+
cert_result = cert_enc.cert
108+
entry_data['type'] = 'TrustedCertEntry'
109+
else:
110+
raise CommandExecutionError('Unsupported EntryType detected in keystore')
111+
112+
# Detect if ASN1 binary, otherwise assume PEM
113+
if '\x30' in cert_result[0]:
114+
public_cert = OpenSSL.crypto.load_certificate(ASN1, cert_result)
115+
else:
116+
public_cert = OpenSSL.crypto.load_certificate(PEM, cert_result)
117+
118+
entry_data.update(_parse_cert(entry_alias, public_cert, return_cert))
119+
decoded_certs.append(entry_data)
120+
121+
return decoded_certs
122+
123+
124+
def add(name, keystore, passphrase, certificate, private_key=None):
125+
'''
126+
Adds certificates to an existing keystore or creates a new one if necesssary.
127+
128+
:param name: alias for the certificate
129+
:param keystore: The path to the keystore file to query
130+
:param passphrase: The passphrase to use to decode the keystore
131+
:param certificate: The PEM public certificate to add to keystore. Can be a string for file.
132+
:param private_key: (Optional for TrustedCert) The PEM private key to add to the keystore
133+
134+
CLI Example:
135+
136+
.. code-block:: bash
137+
138+
salt '*' keystore.add aliasname /tmp/test.store changeit /tmp/testcert.crt
139+
salt '*' keystore.add aliasname /tmp/test.store changeit certificate="-----BEGIN CERTIFICATE-----SIb...BM=-----END CERTIFICATE-----"
140+
salt '*' keystore.add keyname /tmp/test.store changeit /tmp/512.cert private_key=/tmp/512.key
141+
142+
'''
143+
ASN1 = OpenSSL.crypto.FILETYPE_ASN1
144+
PEM = OpenSSL.crypto.FILETYPE_PEM
145+
certs_list = []
146+
if os.path.isfile(keystore):
147+
keystore_object = jks.KeyStore.load(keystore, passphrase)
148+
for alias, loaded_cert in keystore_object.entries.items():
149+
certs_list.append(loaded_cert)
150+
151+
try:
152+
cert_string = __salt__['x509.get_pem_entry'](certificate)
153+
except SaltInvocationError:
154+
raise SaltInvocationError('Invalid certificate file or string: {0}'.format(certificate))
155+
156+
if private_key:
157+
# Accept PEM input format, but convert to DES for loading into new keystore
158+
key_string = __salt__['x509.get_pem_entry'](private_key)
159+
loaded_cert = OpenSSL.crypto.load_certificate(PEM, cert_string)
160+
loaded_key = OpenSSL.crypto.load_privatekey(PEM, key_string)
161+
dumped_cert = OpenSSL.crypto.dump_certificate(ASN1, loaded_cert)
162+
dumped_key = OpenSSL.crypto.dump_privatekey(ASN1, loaded_key)
163+
164+
new_entry = jks.PrivateKeyEntry.new(name, [dumped_cert], dumped_key, 'rsa_raw')
165+
else:
166+
new_entry = jks.TrustedCertEntry.new(name, cert_string)
167+
168+
certs_list.append(new_entry)
169+
170+
keystore_object = jks.KeyStore.new('jks', certs_list)
171+
keystore_object.save(keystore, passphrase)
172+
return True
173+
174+
175+
def remove(name, keystore, passphrase):
176+
'''
177+
Removes a certificate from an existing keystore.
178+
Returns True if remove was successful, otherwise False
179+
180+
:param name: alias for the certificate
181+
:param keystore: The path to the keystore file to query
182+
:param passphrase: The passphrase to use to decode the keystore
183+
184+
CLI Example:
185+
186+
.. code-block:: bash
187+
188+
salt '*' keystore.remove aliasname /tmp/test.store changeit
189+
'''
190+
certs_list = []
191+
keystore_object = jks.KeyStore.load(keystore, passphrase)
192+
for alias, loaded_cert in keystore_object.entries.items():
193+
if name not in alias:
194+
certs_list.append(loaded_cert)
195+
196+
if len(keystore_object.entries) != len(certs_list):
197+
# Entry has been removed, save keystore updates
198+
keystore_object = jks.KeyStore.new('jks', certs_list)
199+
keystore_object.save(keystore, passphrase)
200+
return True
201+
else:
202+
# No alias found, notify user
203+
return False

salt/modules/saltcheck.py

+95
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,101 @@
4242
assertion: assertEqual
4343
expected-return: 'hello'
4444
45+
Example with jinja
46+
------------------
47+
48+
.. code-block:: jinja
49+
50+
{% for package in ["apache2", "openssh"] %}
51+
{# or another example #}
52+
{# for package in salt['pillar.get']("packages") #}
53+
test_{{ package }}_latest:
54+
module_and_function: pkg.upgrade_available
55+
args:
56+
- {{ package }}
57+
assertion: assertFalse
58+
{% endfor %}
59+
60+
Example with setup state including pillar
61+
-----------------------------------------
62+
63+
.. code-block:: yaml
64+
65+
setup_test_environment:
66+
module_and_function: saltcheck.state_apply
67+
args:
68+
- common
69+
pillar-data:
70+
data: value
71+
72+
verify_vim:
73+
module_and_function: pkg.version
74+
args:
75+
- vim
76+
assertion: assertNotEmpty
77+
78+
Example with skip
79+
-----------------
80+
81+
.. code-block:: yaml
82+
83+
package_latest:
84+
module_and_function: pkg.upgrade_available
85+
args:
86+
- apache2
87+
assertion: assertFalse
88+
skip: True
89+
90+
Example with assertion_section
91+
------------------------------
92+
93+
.. code-block:: yaml
94+
95+
validate_shell:
96+
module_and_function: user.info
97+
args:
98+
- root
99+
assertion: assertEqual
100+
expected-return: /bin/bash
101+
assertion_section: shell
102+
103+
Example suppressing print results
104+
---------------------------------
105+
106+
.. code-block:: yaml
107+
108+
validate_env_nameNode:
109+
module_and_function: hadoop.dfs
110+
args:
111+
- text
112+
- /oozie/common/env.properties
113+
expected-return: nameNode = hdfs://nameservice2
114+
assertion: assertNotIn
115+
print_result: False
116+
117+
Supported assertions
118+
====================
119+
120+
* assertEqual
121+
* assertNotEqual
122+
* assertTrue
123+
* assertFalse
124+
* assertIn
125+
* assertNotIn
126+
* assertGreater
127+
* assertGreaterEqual
128+
* assertLess
129+
* assertLessEqual
130+
* assertEmpty
131+
* assertNotEmpty
132+
133+
.. warning::
134+
135+
The saltcheck.state_apply function is an alias for
136+
:py:func:`state.apply <salt.modules.state.apply>`. If using the
137+
:ref:`ACL system <acl-eauth>` ``saltcheck.*`` might provide more capability
138+
than intended if only ``saltcheck.run_state_tests`` and
139+
``saltcheck.run_highstate_tests`` are needed.
45140
'''
46141

47142
# Import Python libs

0 commit comments

Comments
 (0)