Merge branch 'develop' into panos/develop

This commit is contained in:
spenceation 2017-08-12 16:48:32 -04:00 committed by GitHub
commit 2b516313f2
41 changed files with 1055 additions and 294 deletions

View File

@ -1,6 +1,6 @@
=======================
salt.modules.kubernetes
=======================
======================
salt.states.kubernetes
======================
.. automodule:: salt.modules.kubernetes
.. automodule:: salt.states.kubernetes
:members:

View File

@ -49,7 +49,7 @@ Set up an initial profile at ``/etc/salt/cloud.profiles`` or in the
.. code-block:: yaml
joyent_512
joyent_512:
provider: my-joyent-config
size: g4-highcpu-512M
image: ubuntu-16.04

View File

@ -63,10 +63,10 @@ environments (i.e. ``saltenvs``) have been added:
available as saltenvs.
Salt Cloud Features
===================
-------------------
Pre-Flight Commands
-------------------
===================
Support has been added for specified "preflight commands" to run on a VM before
the deploy script is run. These must be defined as a list in a cloud configuration
@ -99,22 +99,22 @@ populated as a list of domain roles.
Deprecations
============
------------
Configuration Option Deprecations
---------------------------------
=================================
- The ``requests_lib`` configuration option has been removed. Please use
``backend`` instead.
Profitbricks Cloud Updated Dependency
-------------------------------------
=====================================
The minimum version of the `profitbrick` python package for the `profitbricks`
The minimum version of the ``profitbrick`` python package for the ``profitbricks``
cloud driver has changed from 3.0.0 to 3.1.0.
Module Deprecations
-------------------
===================
The ``blockdev`` execution module has been removed. Its functions were merged
with the ``disk`` module. Please use the ``disk`` execution module instead.
@ -154,7 +154,7 @@ The ``win_service`` module had the following changes:
``service_type`` instead.
Runner Deprecations
-------------------
===================
The ``manage`` runner had the following changes:
@ -162,7 +162,7 @@ The ``manage`` runner had the following changes:
use ``salt-ssh`` roster entries for the host instead.
State Deprecations
------------------
==================
The ``archive`` state had the following changes:
@ -185,7 +185,7 @@ The ``file`` state had the following changes:
- The ``show_diff`` option was removed. Please use ``show_changes`` instead.
Grain Deprecations
------------------
==================
For ``smartos`` some grains have been deprecated. These grains will be removed in Neon.
@ -193,7 +193,7 @@ For ``smartos`` some grains have been deprecated. These grains will be removed i
- The ``datacenter`` has been replaced with ``mdata:sdc:datacenter_name`` grain.
Utils Deprecations
------------------
==================
The ``salt.utils.cloud.py`` file had the following change:
@ -201,7 +201,7 @@ The ``salt.utils.cloud.py`` file had the following change:
optional.
Other Miscellaneous Deprecations
--------------------------------
================================
The ``version.py`` file had the following changes:

View File

@ -5,3 +5,4 @@ yappi>=0.8.2
--allow-unverified python-neutronclient>2.3.6
python-gnupg
cherrypy>=3.2.2
libnacl

View File

@ -223,6 +223,7 @@ class SSH(object):
else:
self.event = None
self.opts = opts
self.opts['__salt_ssh'] = True
if self.opts[u'regen_thin']:
self.opts[u'ssh_wipe'] = True
if not salt.utils.path.which(u'ssh'):

View File

@ -2381,7 +2381,7 @@ def _zpool_data(grains):
return {}
# quickly return if NetBSD (ZFS still under development)
if salt.utils.is_netbsd():
if salt.utils.platform.is_netbsd():
return {}
# quickly return if no zpool and zfs command

View File

@ -34,7 +34,7 @@ def disks():
return _freebsd_geom()
elif salt.utils.platform.is_linux():
return _linux_disks()
elif salt.utils.is_windows():
elif salt.utils.platform.is_windows():
return _windows_disks()
else:
log.trace('Disk grain does not support OS')

View File

@ -125,7 +125,8 @@ def cert(name,
salt 'gitlab.example.com' acme.cert dev.example.com "[gitlab.example.com]" test_cert=True renew=14 webroot=/opt/gitlab/embedded/service/gitlab-rails/public
'''
cmd = [LEA, 'certonly', '--quiet']
# cmd = [LEA, 'certonly', '--quiet']
cmd = [LEA, 'certonly']
cert_file = _cert_file(name, 'cert')
if not __salt__['file.file_exists'](cert_file):

View File

@ -40,7 +40,7 @@ import salt.utils.path
import salt.utils.platform
import salt.utils.templates
if salt.utils.is_windows():
if salt.utils.platform.is_windows():
import win32file
# TODO: Check that the passed arguments are correct
@ -1063,7 +1063,7 @@ def unzip(zip_file,
continue
zfile.extract(target, dest, password)
if extract_perms:
if not salt.utils.is_windows():
if not salt.utils.platform.is_windows():
perm = zfile.getinfo(target).external_attr >> 16
if perm == 0:
umask_ = os.umask(0)

View File

@ -200,7 +200,7 @@ def execute(context=None, lens=None, commands=(), load_path=None):
method = METHOD_MAP[cmd]
nargs = arg_map[method]
parts = salt.utils.args.shlex_split(arg)
parts = salt.utils.args.shlex_split(arg, posix=False)
if len(parts) not in nargs:
err = '{0} takes {1} args: {2}'.format(method, nargs, parts)

View File

@ -223,10 +223,23 @@ def create_mount_target(filesystemid,
client = _get_conn(key=key, keyid=keyid, profile=profile, region=region)
return client.create_mount_point(FileSystemId=filesystemid,
SubnetId=subnetid,
IpAddress=ipaddress,
SecurityGroups=securitygroups)
if ipaddress is None and securitygroups is None:
return client.create_mount_target(FileSystemId=filesystemid,
SubnetId=subnetid)
if ipaddress is None:
return client.create_mount_target(FileSystemId=filesystemid,
SubnetId=subnetid,
SecurityGroups=securitygroups)
if securitygroups is None:
return client.create_mount_target(FileSystemId=filesystemid,
SubnetId=subnetid,
IpAddress=ipaddress)
return client.create_mount_target(FileSystemId=filesystemid,
SubnetId=subnetid,
IpAddress=ipaddress,
SecurityGroups=securitygroups)
def create_tags(filesystemid,

View File

@ -1839,7 +1839,7 @@ def create(image,
generate one for you (it will be included in the return data).
skip_translate
This function translates Salt CLI input into the format which
This function translates Salt CLI or SLS input into the format which
docker-py_ expects. However, in the event that Salt's translation logic
fails (due to potential changes in the Docker Remote API, or to bugs in
the translation code), this argument can be used to exert granular
@ -2107,9 +2107,9 @@ def create(image,
- ``dns_search="[foo1.domain.tld, foo2.domain.tld]"``
domainname
Set custom DNS search domains
The domain name to use for the container
Example: ``domainname=domain.tld,domain2.tld``
Example: ``domainname=domain.tld``
entrypoint
Entrypoint for the container. Either a string (e.g. ``"mycmd --arg1

View File

@ -11,112 +11,159 @@ regardless if they are encrypted or not.
When generating keys and encrypting passwords use --local when using salt-call for extra
security. Also consider using just the salt runner nacl when encrypting pillar passwords.
:configuration: The following configuration defaults can be
define (pillar or config files) Avoid storing private keys in pillars! Ensure master does not have `pillar_opts=True`:
.. code-block:: python
# cat /etc/salt/master.d/nacl.conf
nacl.config:
# NOTE: `key` and `key_file` have been renamed to `sk`, `sk_file`
# also `box_type` default changed from secretbox to sealedbox.
box_type: sealedbox (default)
sk_file: /etc/salt/pki/master/nacl (default)
pk_file: /etc/salt/pki/master/nacl.pub (default)
sk: None
pk: None
Usage can override the config defaults:
.. code-block:: bash
salt-call nacl.enc sk_file=/etc/salt/pki/master/nacl pk_file=/etc/salt/pki/master/nacl.pub
The nacl lib uses 32byte keys, these keys are base64 encoded to make your life more simple.
To generate your `key` or `keyfile` you can use:
To generate your `sk_file` and `pk_file` use:
.. code-block:: bash
salt-call --local nacl.keygen keyfile=/root/.nacl
salt-call --local nacl.keygen sk_file=/etc/salt/pki/master/nacl
# or if you want to work without files.
salt-call --local nacl.keygen
local:
----------
pk:
/kfGX7PbWeu099702PBbKWLpG/9p06IQRswkdWHCDk0=
sk:
SVWut5SqNpuPeNzb1b9y6b2eXg2PLIog43GBzp48Sow=
Now with your key, you can encrypt some data:
Now with your keypair, you can encrypt data:
You have two option, `sealedbox` or `secretbox`.
SecretBox is data encrypted using private key `pk`. Sealedbox is encrypted using public key `pk`.
Recommend using Sealedbox because the one way encryption permits developers to encrypt data for source control but not decrypt.
Sealedbox only has one key that is for both encryption and decryption.
.. code-block:: bash
salt-call --local nacl.enc mypass keyfile=/root/.nacl
DRB7Q6/X5gGSRCTpZyxS6hXO5LnlJIIJ4ivbmUlbWj0llUA+uaVyvou3vJ4=
salt-call --local nacl.enc asecretpass pk=/kfGX7PbWeu099702PBbKWLpG/9p06IQRswkdWHCDk0=
tqXzeIJnTAM9Xf0mdLcpEdklMbfBGPj2oTKmlgrm3S1DTVVHNnh9h8mU1GKllGq/+cYsk6m5WhGdk58=
To decrypt the data:
.. code-block:: bash
salt-call --local nacl.dec data='DRB7Q6/X5gGSRCTpZyxS6hXO5LnlJIIJ4ivbmUlbWj0llUA+uaVyvou3vJ4=' keyfile=/root/.nacl
mypass
salt-call --local nacl.dec data='tqXzeIJnTAM9Xf0mdLcpEdklMbfBGPj2oTKmlgrm3S1DTVVHNnh9h8mU1GKllGq/+cYsk6m5WhGdk58=' \
sk='SVWut5SqNpuPeNzb1b9y6b2eXg2PLIog43GBzp48Sow='
The following optional configurations can be defined in the
minion or master config. Avoid storing the config in pillars!
When the keys are defined in the master config you can use them from the nacl runner
without extra parameters:
.. code-block:: yaml
.. code-block:: python
cat /etc/salt/master.d/nacl.conf
# cat /etc/salt/master.d/nacl.conf
nacl.config:
key: 'cKEzd4kXsbeCE7/nLTIqXwnUiD1ulg4NoeeYcCFpd9k='
keyfile: /root/.nacl
When the key is defined in the master config you can use it from the nacl runner:
sk_file: /etc/salt/pki/master/nacl
pk: 'cTIqXwnUiD1ulg4kXsbeCE7/NoeKEzd4nLeYcCFpd9k='
.. code-block:: bash
salt-run nacl.enc 'myotherpass'
salt-run nacl.enc 'asecretpass'
salt-run nacl.dec 'tqXzeIJnTAM9Xf0mdLcpEdklMbfBGPj2oTKmlgrm3S1DTVVHNnh9h8mU1GKllGq/+cYsk6m5WhGdk58='
Now you can create a pillar with protected data like:
.. code-block:: yam
# a salt developers minion could have pillar data that includes a nacl public key
nacl.config:
pk: '/kfGX7PbWeu099702PBbKWLpG/9p06IQRswkdWHCDk0='
The developer can then use a less-secure system to encrypt data.
.. code-block:: bash
salt-call --local nacl.enc apassword
Pillar files can include protected data that the salt master decrypts:
.. code-block:: jinja
pillarexample:
user: root
password: {{ salt.nacl.dec('DRB7Q6/X5gGSRCTpZyxS6hXO5LnlJIIJ4ivbmUlbWj0llUA+uaVyvou3vJ4=') }}
password1: {{salt.nacl.dec('DRB7Q6/X5gGSRCTpZyxS6hlbWj0llUA+uaVyvou3vJ4=')|json}}
cert_key: {{salt.nacl.dec_file('/srv/salt/certs/example.com/key.nacl')|json}}
cert_key2: {{salt.nacl.dec_file('salt:///certs/example.com/key.nacl')|json}}
Or do something interesting with grains like:
.. code-block:: jinja
salt-call nacl.enc minionname:dbrole
AL24Z2C5OlkReer3DuQTFdrNLchLuz3NGIhGjZkLtKRYry/b/CksWM8O9yskLwH2AGVLoEXI5jAa
salt minionname grains.setval role 'AL24Z2C5OlkReer3DuQTFdrNLchLuz3NGIhGjZkLtKRYry/b/CksWM8O9yskLwH2AGVLoEXI5jAa'
{%- set r = grains.get('role') %}
{%- set role = None %}
{%- if r and 'nacl.dec' in salt %}
{%- set r = salt['nacl.dec'](r,keyfile='/root/.nacl').split(':') %}
{%- if opts['id'] == r[0] %}
{%- set role = r[1] %}
{%- endif %}
{%- endif %}
base:
{%- if role %}
'{{ opts['id'] }}':
- {{ role }}
{%- endif %}
Multi-line text items like certificates require a bit of extra work. You have to strip the new lines
and replace them with '/n' characters. Certificates specifically require some leading white space when
calling nacl.enc so that the '--' in the first line (commonly -----BEGIN CERTIFICATE-----) doesn't get
interpreted as an argument to nacl.enc. For instance if you have a certificate file that lives in cert.crt:
Larger files like certificates can be encrypted with:
.. code-block:: bash
cert=$(cat cert.crt |awk '{printf "%s\\n",$0} END {print ""}'); salt-run nacl.enc " $cert"
salt-call nacl.enc_file /tmp/cert.crt out=/tmp/cert.nacl
# or more advanced
cert=$(cat /tmp/cert.crt)
salt-call --out=newline_values_only nacl.enc_pub data="$cert" > /tmp/cert.nacl
Pillar data should look the same, even though the secret will be quite long. However, when calling
multiline encrypted secrets from pillar in a state, use the following format to avoid issues with /n
creating extra whitespace at the beginning of each line in the cert file:
In pillars rended with jinja be sure to include `|json` so line breaks are encoded:
.. code-block:: jinja
secret.txt:
file.managed:
- template: jinja
- user: user
- group: group
- mode: 700
- contents: "{{- salt['pillar.get']('secret') }}"
cert: "{{salt.nacl.dec('S2uogToXkgENz9...085KYt')|json}}"
In states rendered with jinja it is also good pratice to include `|json`:
.. code-block:: jinja
{{sls}} private key:
file.managed:
- name: /etc/ssl/private/cert.key
- mode: 700
- contents: "{{pillar['pillarexample']['cert_key']|json}}"
Optional small program to encrypt data without needing salt modules.
.. code-block:: python
#!/bin/python3
import sys, base64, libnacl.sealed
pk = base64.b64decode('YOURPUBKEY')
b = libnacl.sealed.SealedBox(pk)
data = sys.stdin.buffer.read()
print(base64.b64encode(b.encrypt(data)).decode())
.. code-block:: bash
echo 'apassword' | nacl_enc.py
The '{{-' will tell jinja to strip the whitespace from the beginning of each of the new lines.
'''
from __future__ import absolute_import
import base64
import os
import salt.utils.files
import salt.utils
import salt.utils.win_functions
import salt.utils.win_dacl
import salt.syspaths
REQ_ERROR = None
try:
import libnacl.secret
import libnacl.sealed
except (ImportError, OSError) as e:
REQ_ERROR = 'libnacl import error, perhaps missing python libnacl package'
REQ_ERROR = 'libnacl import error, perhaps missing python libnacl package or should update.'
__virtualname__ = 'nacl'
@ -130,91 +177,294 @@ def _get_config(**kwargs):
Return configuration
'''
config = {
'key': None,
'keyfile': None,
'box_type': 'sealedbox',
'sk': None,
'sk_file': '/etc/salt/pki/master/nacl',
'pk': None,
'pk_file': '/etc/salt/pki/master/nacl.pub',
}
config_key = '{0}.config'.format(__virtualname__)
config.update(__salt__['config.get'](config_key, {}))
for k in set(config) & set(kwargs):
try:
config.update(__salt__['config.get'](config_key, {}))
except (NameError, KeyError) as e:
# likly using salt-run so fallback to __opts__
config.update(__opts__.get(config_key, {}))
# pylint: disable=C0201
for k in set(config.keys()) & set(kwargs.keys()):
config[k] = kwargs[k]
return config
def _get_key(rstrip_newline=True, **kwargs):
def _get_sk(**kwargs):
'''
Return key
Return sk
'''
config = _get_config(**kwargs)
key = config['key']
keyfile = config['keyfile']
if not key and keyfile:
if not os.path.isfile(keyfile):
raise Exception('file not found: {0}'.format(keyfile))
with salt.utils.files.fopen(keyfile, 'rb') as keyf:
key = keyf.read()
key = config['sk']
sk_file = config['sk_file']
if not key and sk_file:
with salt.utils.fopen(sk_file, 'rb') as keyf:
key = str(keyf.read()).rstrip('\n')
if key is None:
raise Exception('no key found')
key = str(key)
if rstrip_newline:
key = key.rstrip('\n')
return key
raise Exception('no key or sk_file found')
return base64.b64decode(key)
def keygen(keyfile=None):
def _get_pk(**kwargs):
'''
Use libnacl to generate a private key
Return pk
'''
config = _get_config(**kwargs)
pubkey = config['pk']
pk_file = config['pk_file']
if not pubkey and pk_file:
with salt.utils.fopen(pk_file, 'rb') as keyf:
pubkey = str(keyf.read()).rstrip('\n')
if pubkey is None:
raise Exception('no pubkey or pk_file found')
pubkey = str(pubkey)
return base64.b64decode(pubkey)
def keygen(sk_file=None, pk_file=None):
'''
Use libnacl to generate a keypair.
If no `sk_file` is defined return a keypair.
If only the `sk_file` is defined `pk_file` will use the same name with a postfix `.pub`.
When the `sk_file` is already existing, but `pk_file` is not. The `pk_file` will be generated
using the `sk_file`.
CLI Examples:
.. code-block:: bash
salt-call nacl.keygen
salt-call nacl.keygen sk_file=/etc/salt/pki/master/nacl
salt-call nacl.keygen sk_file=/etc/salt/pki/master/nacl pk_file=/etc/salt/pki/master/nacl.pub
salt-call --local nacl.keygen
salt-call --local nacl.keygen keyfile=/root/.nacl
salt-call --local --out=newline_values_only nacl.keygen > /root/.nacl
'''
b = libnacl.secret.SecretBox()
key = b.sk
key = base64.b64encode(key)
if keyfile:
if os.path.isfile(keyfile):
raise Exception('file already found: {0}'.format(keyfile))
with salt.utils.files.fopen(keyfile, 'w') as keyf:
keyf.write(key)
return 'saved: {0}'.format(keyfile)
return key
if sk_file is None:
kp = libnacl.public.SecretKey()
return {'sk': base64.b64encode(kp.sk), 'pk': base64.b64encode(kp.pk)}
if pk_file is None:
pk_file = '{0}.pub'.format(sk_file)
if sk_file and pk_file is None:
if not os.path.isfile(sk_file):
kp = libnacl.public.SecretKey()
with salt.utils.fopen(sk_file, 'w') as keyf:
keyf.write(base64.b64encode(kp.sk))
if salt.utils.is_windows():
cur_user = salt.utils.win_functions.get_current_user()
salt.utils.win_dacl.set_owner(sk_file, cur_user)
salt.utils.win_dacl.set_permissions(sk_file, cur_user, 'full_control', 'grant', reset_perms=True, protected=True)
else:
# chmod 0600 file
os.chmod(sk_file, 1536)
return 'saved sk_file: {0}'.format(sk_file)
else:
raise Exception('sk_file:{0} already exist.'.format(sk_file))
if sk_file is None and pk_file:
raise Exception('sk_file: Must be set inorder to generate a public key.')
if os.path.isfile(sk_file) and os.path.isfile(pk_file):
raise Exception('sk_file:{0} and pk_file:{1} already exist.'.format(sk_file, pk_file))
if os.path.isfile(sk_file) and not os.path.isfile(pk_file):
# generate pk using the sk
with salt.utils.fopen(sk_file, 'rb') as keyf:
sk = str(keyf.read()).rstrip('\n')
sk = base64.b64decode(sk)
kp = libnacl.public.SecretKey(sk)
with salt.utils.fopen(pk_file, 'w') as keyf:
keyf.write(base64.b64encode(kp.pk))
return 'saved pk_file: {0}'.format(pk_file)
kp = libnacl.public.SecretKey()
with salt.utils.fopen(sk_file, 'w') as keyf:
keyf.write(base64.b64encode(kp.sk))
if salt.utils.is_windows():
cur_user = salt.utils.win_functions.get_current_user()
salt.utils.win_dacl.set_owner(sk_file, cur_user)
salt.utils.win_dacl.set_permissions(sk_file, cur_user, 'full_control', 'grant', reset_perms=True, protected=True)
else:
# chmod 0600 file
os.chmod(sk_file, 1536)
with salt.utils.fopen(pk_file, 'w') as keyf:
keyf.write(base64.b64encode(kp.pk))
return 'saved sk_file:{0} pk_file: {1}'.format(sk_file, pk_file)
def enc(data, **kwargs):
'''
Takes a key generated from `nacl.keygen` and encrypt some data.
Alias to `{box_type}_encrypt`
box_type: secretbox, sealedbox(default)
'''
box_type = _get_config(**kwargs)['box_type']
if box_type == 'sealedbox':
return sealedbox_encrypt(data, **kwargs)
if box_type == 'secretbox':
return secretbox_encrypt(data, **kwargs)
return sealedbox_encrypt(data, **kwargs)
def enc_file(name, out=None, **kwargs):
'''
This is a helper function to encrypt a file and return its contents.
You can provide an optional output file using `out`
`name` can be a local file or when not using `salt-run` can be a url like `salt://`, `https://` etc.
CLI Examples:
.. code-block:: bash
salt-call --local nacl.enc datatoenc
salt-call --local nacl.enc datatoenc keyfile=/root/.nacl
salt-call --local nacl.enc datatoenc key='cKEzd4kXsbeCE7/nLTIqXwnUiD1ulg4NoeeYcCFpd9k='
salt-run nacl.enc_file name=/tmp/id_rsa
salt-call nacl.enc_file name=salt://crt/mycert out=/tmp/cert
salt-run nacl.enc_file name=/tmp/id_rsa box_type=secretbox \
sk_file=/etc/salt/pki/master/nacl.pub
'''
key = _get_key(**kwargs)
sk = base64.b64decode(key)
b = libnacl.secret.SecretBox(sk)
return base64.b64encode(b.encrypt(data))
try:
data = __salt__['cp.get_file_str'](name)
except Exception as e:
# likly using salt-run so fallback to local filesystem
with salt.utils.fopen(name, 'rb') as f:
data = f.read()
d = enc(data, **kwargs)
if out:
if os.path.isfile(out):
raise Exception('file:{0} already exist.'.format(out))
with salt.utils.fopen(out, 'wb') as f:
f.write(d)
return 'Wrote: {0}'.format(out)
return d
def dec(data, **kwargs):
'''
Takes a key generated from `nacl.keygen` and decrypt some data.
Alias to `{box_type}_decrypt`
box_type: secretbox, sealedbox(default)
'''
box_type = _get_config(**kwargs)['box_type']
if box_type == 'sealedbox':
return sealedbox_decrypt(data, **kwargs)
if box_type == 'secretbox':
return secretbox_decrypt(data, **kwargs)
return sealedbox_decrypt(data, **kwargs)
def dec_file(name, out=None, **kwargs):
'''
This is a helper function to decrypt a file and return its contents.
You can provide an optional output file using `out`
`name` can be a local file or when not using `salt-run` can be a url like `salt://`, `https://` etc.
CLI Examples:
.. code-block:: bash
salt-call --local nacl.dec pEXHQM6cuaF7A=
salt-call --local nacl.dec data='pEXHQM6cuaF7A=' keyfile=/root/.nacl
salt-call --local nacl.dec data='pEXHQM6cuaF7A=' key='cKEzd4kXsbeCE7/nLTIqXwnUiD1ulg4NoeeYcCFpd9k='
salt-run nacl.dec_file name=/tmp/id_rsa.nacl
salt-call nacl.dec_file name=salt://crt/mycert.nacl out=/tmp/id_rsa
salt-run nacl.dec_file name=/tmp/id_rsa.nacl box_type=secretbox \
sk_file=/etc/salt/pki/master/nacl.pub
'''
key = _get_key(**kwargs)
sk = base64.b64decode(key)
b = libnacl.secret.SecretBox(key=sk)
try:
data = __salt__['cp.get_file_str'](name)
except Exception as e:
# likly using salt-run so fallback to local filesystem
with salt.utils.fopen(name, 'rb') as f:
data = f.read()
d = dec(data, **kwargs)
if out:
if os.path.isfile(out):
raise Exception('file:{0} already exist.'.format(out))
with salt.utils.fopen(out, 'wb') as f:
f.write(d)
return 'Wrote: {0}'.format(out)
return d
def sealedbox_encrypt(data, **kwargs):
'''
Encrypt data using a public key generated from `nacl.keygen`.
The encryptd data can be decrypted using `nacl.sealedbox_decrypt` only with the secret key.
CLI Examples:
.. code-block:: bash
salt-run nacl.sealedbox_encrypt datatoenc
salt-call --local nacl.sealedbox_encrypt datatoenc pk_file=/etc/salt/pki/master/nacl.pub
salt-call --local nacl.sealedbox_encrypt datatoenc pk='vrwQF7cNiNAVQVAiS3bvcbJUnF0cN6fU9YTZD9mBfzQ='
'''
pk = _get_pk(**kwargs)
b = libnacl.sealed.SealedBox(pk)
return base64.b64encode(b.encrypt(data))
def sealedbox_decrypt(data, **kwargs):
'''
Decrypt data using a secret key that was encrypted using a public key with `nacl.sealedbox_encrypt`.
CLI Examples:
.. code-block:: bash
salt-call nacl.sealedbox_decrypt pEXHQM6cuaF7A=
salt-call --local nacl.sealedbox_decrypt data='pEXHQM6cuaF7A=' sk_file=/etc/salt/pki/master/nacl
salt-call --local nacl.sealedbox_decrypt data='pEXHQM6cuaF7A=' sk='YmFkcGFzcwo='
'''
if data is None:
return None
sk = _get_sk(**kwargs)
keypair = libnacl.public.SecretKey(sk)
b = libnacl.sealed.SealedBox(keypair)
return b.decrypt(base64.b64decode(data))
def secretbox_encrypt(data, **kwargs):
'''
Encrypt data using a secret key generated from `nacl.keygen`.
The same secret key can be used to decrypt the data using `nacl.secretbox_decrypt`.
CLI Examples:
.. code-block:: bash
salt-run nacl.secretbox_encrypt datatoenc
salt-call --local nacl.secretbox_encrypt datatoenc sk_file=/etc/salt/pki/master/nacl
salt-call --local nacl.secretbox_encrypt datatoenc sk='YmFkcGFzcwo='
'''
sk = _get_sk(**kwargs)
b = libnacl.secret.SecretBox(sk)
return base64.b64encode(b.encrypt(data))
def secretbox_decrypt(data, **kwargs):
'''
Decrypt data that was encrypted using `nacl.secretbox_encrypt` using the secret key
that was generated from `nacl.keygen`.
CLI Examples:
.. code-block:: bash
salt-call nacl.secretbox_decrypt pEXHQM6cuaF7A=
salt-call --local nacl.secretbox_decrypt data='pEXHQM6cuaF7A=' sk_file=/etc/salt/pki/master/nacl
salt-call --local nacl.secretbox_decrypt data='pEXHQM6cuaF7A=' sk='YmFkcGFzcwo='
'''
if data is None:
return None
key = _get_sk(**kwargs)
b = libnacl.secret.SecretBox(key=key)
return b.decrypt(base64.b64decode(data))

View File

@ -257,8 +257,8 @@ def items(*args, **kwargs):
__opts__,
__grains__,
__opts__['id'],
pillar_override=kwargs.get('pillar'),
pillarenv=kwargs.get('pillarenv') or __opts__['pillarenv'])
pillar_override=pillar_override,
pillarenv=pillarenv)
return pillar.compile_pillar()

View File

@ -611,11 +611,11 @@ def set_user_tags(name, tags, runas=None):
if runas is None and not salt.utils.platform.is_windows():
runas = salt.utils.get_user()
if tags and isinstance(tags, (list, tuple)):
tags = ' '.join(tags)
if not isinstance(tags, (list, tuple)):
tags = [tags]
res = __salt__['cmd.run_all'](
[RABBITMQCTL, 'set_user_tags', name, tags],
[RABBITMQCTL, 'set_user_tags', name] + list(tags),
runas=runas,
python_shell=False)
msg = "Tag(s) set"

View File

@ -18,6 +18,7 @@ import salt.utils.files
import salt.utils.stringutils
import salt.utils.templates
import salt.utils.validate.net
from salt.exceptions import CommandExecutionError
from salt.ext import six
# Set up logging
@ -667,14 +668,24 @@ def _parse_settings_eth(opts, iface_type, enabled, iface):
bypassfirewall = False
else:
_raise_error_iface(iface, opts[opt], valid)
bridgectls = [
'net.bridge.bridge-nf-call-ip6tables',
'net.bridge.bridge-nf-call-iptables',
'net.bridge.bridge-nf-call-arptables',
]
if bypassfirewall:
__salt__['sysctl.persist']('net.bridge.bridge-nf-call-ip6tables', '0')
__salt__['sysctl.persist']('net.bridge.bridge-nf-call-iptables', '0')
__salt__['sysctl.persist']('net.bridge.bridge-nf-call-arptables', '0')
sysctl_value = 0
else:
__salt__['sysctl.persist']('net.bridge.bridge-nf-call-ip6tables', '1')
__salt__['sysctl.persist']('net.bridge.bridge-nf-call-iptables', '1')
__salt__['sysctl.persist']('net.bridge.bridge-nf-call-arptables', '1')
sysctl_value = 1
for sysctl in bridgectls:
try:
__salt__['sysctl.persist'](sysctl, sysctl_value)
except CommandExecutionError:
log.warning('Failed to set sysctl: {0}'.format(sysctl))
else:
if 'bridge' in opts:
result['bridge'] = opts['bridge']

View File

@ -856,7 +856,7 @@ def create_cert_binding(name, site, hostheader='', ipaddress='*', port=443,
new_cert_bindings = list_cert_bindings(site)
if binding_info not in new_cert_bindings(site):
if binding_info not in new_cert_bindings:
log.error('Binding not present: {0}'.format(binding_info))
return False

View File

@ -580,13 +580,21 @@ import signal
import tarfile
from multiprocessing import Process, Pipe
# Import third-party libs
# pylint: disable=import-error
import cherrypy # pylint: disable=3rd-party-module-not-gated
import yaml
from salt.ext import six
# pylint: enable=import-error
logger = logging.getLogger(__name__)
# Import third-party libs
# pylint: disable=import-error, 3rd-party-module-not-gated
import cherrypy
try:
from cherrypy.lib import cpstats
except ImportError:
cpstats = None
logger.warn('Import of cherrypy.cpstats failed. '
'Possible upstream bug: '
'https://github.com/cherrypy/cherrypy/issues/1444')
import yaml
# pylint: enable=import-error, 3rd-party-module-not-gated
# Import Salt libs
import salt
@ -594,12 +602,11 @@ import salt.auth
import salt.utils
import salt.utils.event
import salt.utils.stringutils
from salt.ext import six
# Import salt-api libs
import salt.netapi
logger = logging.getLogger(__name__)
# Imports related to websocket
try:
from .tools import websockets
@ -2717,13 +2724,6 @@ class Stats(object):
:status 406: |406|
'''
if hasattr(logging, 'statistics'):
# Late import
try:
from cherrypy.lib import cpstats
except ImportError:
logger.error('Import of cherrypy.cpstats failed. Possible '
'upstream bug here: https://github.com/cherrypy/cherrypy/issues/1444')
return {}
return cpstats.extrapolate_statistics(logging.statistics)
return {}
@ -2843,13 +2843,14 @@ class API(object):
'tools.trailing_slash.on': True,
'tools.gzip.on': True,
'tools.cpstats.on': self.apiopts.get('collect_stats', False),
'tools.html_override.on': True,
'tools.cors_tool.on': True,
},
}
if cpstats and self.apiopts.get('collect_stats', False):
conf['/']['tools.cpstats.on'] = True
if 'favicon' in self.apiopts:
conf['/favicon.ico'] = {
'tools.staticfile.on': True,

View File

@ -523,8 +523,7 @@ class BaseSaltAPIHandler(tornado.web.RequestHandler, SaltClientsMixIn): # pylin
try:
# Use cgi.parse_header to correctly separate parameters from value
header = cgi.parse_header(self.request.headers['Content-Type'])
value, parameters = header
value, parameters = cgi.parse_header(self.request.headers['Content-Type'])
return ct_in_map[value](tornado.escape.native_str(data))
except KeyError:
self.send_error(406)
@ -538,7 +537,7 @@ class BaseSaltAPIHandler(tornado.web.RequestHandler, SaltClientsMixIn): # pylin
if not self.request.body:
return
data = self.deserialize(self.request.body)
self.raw_data = copy(data)
self.request_payload = copy(data)
if data and 'arg' in data and not isinstance(data['arg'], list):
data['arg'] = [data['arg']]
@ -696,15 +695,13 @@ class SaltAuthHandler(BaseSaltAPIHandler): # pylint: disable=W0223
}}
'''
try:
request_payload = self.deserialize(self.request.body)
if not isinstance(request_payload, dict):
if not isinstance(self.request_payload, dict):
self.send_error(400)
return
creds = {'username': request_payload['username'],
'password': request_payload['password'],
'eauth': request_payload['eauth'],
creds = {'username': self.request_payload['username'],
'password': self.request_payload['password'],
'eauth': self.request_payload['eauth'],
}
# if any of the args are missing, its a bad request
except KeyError:
@ -1641,7 +1638,7 @@ class WebhookSaltAPIHandler(SaltAPIHandler): # pylint: disable=W0223
value = value[0]
arguments[argname] = value
ret = self.event.fire_event({
'post': self.raw_data,
'post': self.request_payload,
'get': arguments,
# In Tornado >= v4.0.3, the headers come
# back as an HTTPHeaders instance, which

View File

@ -174,7 +174,7 @@ def get_printout(out, opts=None, **kwargs):
else:
if opts.get('force_color', False):
opts['color'] = True
elif opts.get('no_color', False) or salt.utils.is_windows():
elif opts.get('no_color', False) or salt.utils.platform.is_windows():
opts['color'] = False
else:
pass

View File

@ -14,16 +14,13 @@ import os
# Import Salt libs
import salt.cloud
import salt.utils.args
from salt.exceptions import SaltCloudConfigError
# Get logging started
log = logging.getLogger(__name__)
def _filter_kwargs(k):
return dict((x, k[x]) for x in k if not x.startswith('__'))
def _get_client():
'''
Return cloud client
@ -114,7 +111,7 @@ def profile(prof=None, instances=None, opts=None, **kwargs):
client = _get_client()
if isinstance(opts, dict):
client.opts.update(opts)
info = client.profile(prof, instances, **_filter_kwargs(kwargs))
info = client.profile(prof, instances, **salt.utils.args.clean_kwargs(**kwargs))
return info
@ -123,7 +120,7 @@ def map_run(path=None, **kwargs):
Execute a salt cloud map file
'''
client = _get_client()
info = client.map_run(path, **_filter_kwargs(kwargs))
info = client.map_run(path, **salt.utils.args.clean_kwargs(**kwargs))
return info
@ -154,7 +151,14 @@ def action(func=None,
info = {}
client = _get_client()
try:
info = client.action(func, cloudmap, instances, provider, instance, **_filter_kwargs(kwargs))
info = client.action(
func,
cloudmap,
instances,
provider,
instance,
**salt.utils.args.clean_kwargs(**kwargs)
)
except SaltCloudConfigError as err:
log.error(err)
return info
@ -172,12 +176,8 @@ def create(provider, instances, opts=None, **kwargs):
image=ami-1624987f size='t1.micro' ssh_username=ec2-user \
securitygroup=default delvol_on_destroy=True
'''
create_kwargs = {}
for kwarg in kwargs:
if not kwarg.startswith('__'):
create_kwargs[kwarg] = kwargs[kwarg]
client = _get_client()
if isinstance(opts, dict):
client.opts.update(opts)
info = client.create(provider, instances, **create_kwargs)
info = client.create(provider, instances, **salt.utils.args.clean_kwargs(**kwargs))
return info

View File

@ -22,6 +22,7 @@ from salt.ext.six.moves.urllib.request import urlopen as _urlopen # pylint: dis
# Import salt libs
import salt.key
import salt.utils
import salt.utils.compat
import salt.utils.files
import salt.utils.minions
import salt.client
@ -670,7 +671,7 @@ def versions():
ver_diff = -2
else:
minion_version = salt.version.SaltStackVersion.parse(minions[minion])
ver_diff = cmp(minion_version, master_version)
ver_diff = salt.utils.compat.cmp(minion_version, master_version)
if ver_diff not in version_status:
version_status[ver_diff] = {}

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
'''
This runner helps create encrypted passwords that can be included in pillars.
This module helps include encrypted passwords in pillars, grains and salt state files.
:depends: libnacl, https://github.com/saltstack/libnacl
@ -8,35 +8,162 @@ This is often useful if you wish to store your pillars in source control or
share your pillar data with others that you trust. I don't advise making your pillars public
regardless if they are encrypted or not.
The following configurations can be defined in the master config
so your users can create encrypted passwords using the runner nacl:
When generating keys and encrypting passwords use --local when using salt-call for extra
security. Also consider using just the salt runner nacl when encrypting pillar passwords.
:configuration: The following configuration defaults can be
define (pillar or config files) Avoid storing private keys in pillars! Ensure master does not have `pillar_opts=True`:
.. code-block:: python
# cat /etc/salt/master.d/nacl.conf
nacl.config:
# NOTE: `key` and `key_file` have been renamed to `sk`, `sk_file`
# also `box_type` default changed from secretbox to sealedbox.
box_type: sealedbox (default)
sk_file: /etc/salt/pki/master/nacl (default)
pk_file: /etc/salt/pki/master/nacl.pub (default)
sk: None
pk: None
Usage can override the config defaults:
.. code-block:: bash
salt-call nacl.enc sk_file=/etc/salt/pki/master/nacl pk_file=/etc/salt/pki/master/nacl.pub
The nacl lib uses 32byte keys, these keys are base64 encoded to make your life more simple.
To generate your `sk_file` and `pk_file` use:
.. code-block:: bash
cat /etc/salt/master.d/nacl.conf
salt-call --local nacl.keygen sk_file=/etc/salt/pki/master/nacl
# or if you want to work without files.
salt-call --local nacl.keygen
local:
----------
pk:
/kfGX7PbWeu099702PBbKWLpG/9p06IQRswkdWHCDk0=
sk:
SVWut5SqNpuPeNzb1b9y6b2eXg2PLIog43GBzp48Sow=
Now with your keypair, you can encrypt data:
You have two option, `sealedbox` or `secretbox`.
SecretBox is data encrypted using private key `pk`. Sealedbox is encrypted using public key `pk`.
Recommend using Sealedbox because the one way encryption permits developers to encrypt data for source control but not decrypt.
Sealedbox only has one key that is for both encryption and decryption.
.. code-block:: bash
salt-call --local nacl.enc asecretpass pk=/kfGX7PbWeu099702PBbKWLpG/9p06IQRswkdWHCDk0=
tqXzeIJnTAM9Xf0mdLcpEdklMbfBGPj2oTKmlgrm3S1DTVVHNnh9h8mU1GKllGq/+cYsk6m5WhGdk58=
To decrypt the data:
.. code-block:: bash
salt-call --local nacl.dec data='tqXzeIJnTAM9Xf0mdLcpEdklMbfBGPj2oTKmlgrm3S1DTVVHNnh9h8mU1GKllGq/+cYsk6m5WhGdk58=' \
sk='SVWut5SqNpuPeNzb1b9y6b2eXg2PLIog43GBzp48Sow='
When the keys are defined in the master config you can use them from the nacl runner
without extra parameters:
.. code-block:: python
# cat /etc/salt/master.d/nacl.conf
nacl.config:
key: 'cKEzd4kXsbeCE7/nLTIqXwnUiD1ulg4NoeeYcCFpd9k='
keyfile: /root/.nacl
Now with the config in the master you can use the runner nacl like:
sk_file: /etc/salt/pki/master/nacl
pk: 'cTIqXwnUiD1ulg4kXsbeCE7/NoeKEzd4nLeYcCFpd9k='
.. code-block:: bash
salt-run nacl.enc 'data'
salt-run nacl.enc 'asecretpass'
salt-run nacl.dec 'tqXzeIJnTAM9Xf0mdLcpEdklMbfBGPj2oTKmlgrm3S1DTVVHNnh9h8mU1GKllGq/+cYsk6m5WhGdk58='
.. code-block:: yam
# a salt developers minion could have pillar data that includes a nacl public key
nacl.config:
pk: '/kfGX7PbWeu099702PBbKWLpG/9p06IQRswkdWHCDk0='
The developer can then use a less-secure system to encrypt data.
.. code-block:: bash
salt-call --local nacl.enc apassword
Pillar files can include protected data that the salt master decrypts:
.. code-block:: jinja
pillarexample:
user: root
password1: {{salt.nacl.dec('DRB7Q6/X5gGSRCTpZyxS6hlbWj0llUA+uaVyvou3vJ4=')|json}}
cert_key: {{salt.nacl.dec_file('/srv/salt/certs/example.com/key.nacl')|json}}
cert_key2: {{salt.nacl.dec_file('salt:///certs/example.com/key.nacl')|json}}
Larger files like certificates can be encrypted with:
.. code-block:: bash
salt-call nacl.enc_file /tmp/cert.crt out=/tmp/cert.nacl
# or more advanced
cert=$(cat /tmp/cert.crt)
salt-call --out=newline_values_only nacl.enc_pub data="$cert" > /tmp/cert.nacl
In pillars rended with jinja be sure to include `|json` so line breaks are encoded:
.. code-block:: jinja
cert: "{{salt.nacl.dec('S2uogToXkgENz9...085KYt')|json}}"
In states rendered with jinja it is also good pratice to include `|json`:
.. code-block:: jinja
{{sls}} private key:
file.managed:
- name: /etc/ssl/private/cert.key
- mode: 700
- contents: "{{pillar['pillarexample']['cert_key']|json}}"
Optional small program to encrypt data without needing salt modules.
.. code-block:: python
#!/bin/python3
import sys, base64, libnacl.sealed
pk = base64.b64decode('YOURPUBKEY')
b = libnacl.sealed.SealedBox(pk)
data = sys.stdin.buffer.read()
print(base64.b64encode(b.encrypt(data)).decode())
.. code-block:: bash
echo 'apassword' | nacl_enc.py
'''
from __future__ import absolute_import
import base64
import os
import salt.utils.files
import salt.utils
import salt.utils.win_functions
import salt.utils.win_dacl
import salt.syspaths
REQ_ERROR = None
try:
import libnacl.secret
except ImportError as e:
REQ_ERROR = 'libnacl import error, perhaps missing python libnacl package'
import libnacl.sealed
except (ImportError, OSError) as e:
REQ_ERROR = 'libnacl import error, perhaps missing python libnacl package or should update.'
__virtualname__ = 'nacl'
@ -50,91 +177,294 @@ def _get_config(**kwargs):
Return configuration
'''
config = {
'key': None,
'keyfile': None,
'box_type': 'sealedbox',
'sk': None,
'sk_file': '/etc/salt/pki/master/nacl',
'pk': None,
'pk_file': '/etc/salt/pki/master/nacl.pub',
}
config_key = '{0}.config'.format(__virtualname__)
config.update(__opts__.get(config_key, {}))
for k in set(config) & set(kwargs):
try:
config.update(__salt__['config.get'](config_key, {}))
except (NameError, KeyError) as e:
# likly using salt-run so fallback to __opts__
config.update(__opts__.get(config_key, {}))
# pylint: disable=C0201
for k in set(config.keys()) & set(kwargs.keys()):
config[k] = kwargs[k]
return config
def _get_key(rstrip_newline=True, **kwargs):
def _get_sk(**kwargs):
'''
Return key
Return sk
'''
config = _get_config(**kwargs)
key = config['key']
keyfile = config['keyfile']
if not key and keyfile:
if not os.path.isfile(keyfile):
raise Exception('file not found: {0}'.format(keyfile))
with salt.utils.files.fopen(keyfile, 'rb') as keyf:
key = keyf.read()
key = config['sk']
sk_file = config['sk_file']
if not key and sk_file:
with salt.utils.fopen(sk_file, 'rb') as keyf:
key = str(keyf.read()).rstrip('\n')
if key is None:
raise Exception('no key found')
key = str(key)
if rstrip_newline:
key = key.rstrip('\n')
return key
raise Exception('no key or sk_file found')
return base64.b64decode(key)
def keygen(keyfile=None):
def _get_pk(**kwargs):
'''
Use libnacl to generate a private key
Return pk
'''
config = _get_config(**kwargs)
pubkey = config['pk']
pk_file = config['pk_file']
if not pubkey and pk_file:
with salt.utils.fopen(pk_file, 'rb') as keyf:
pubkey = str(keyf.read()).rstrip('\n')
if pubkey is None:
raise Exception('no pubkey or pk_file found')
pubkey = str(pubkey)
return base64.b64decode(pubkey)
def keygen(sk_file=None, pk_file=None):
'''
Use libnacl to generate a keypair.
If no `sk_file` is defined return a keypair.
If only the `sk_file` is defined `pk_file` will use the same name with a postfix `.pub`.
When the `sk_file` is already existing, but `pk_file` is not. The `pk_file` will be generated
using the `sk_file`.
CLI Examples:
.. code-block:: bash
salt-run nacl.keygen
salt-run nacl.keygen keyfile=/root/.nacl
salt-run --out=newline_values_only nacl.keygen > /root/.nacl
salt-call nacl.keygen
salt-call nacl.keygen sk_file=/etc/salt/pki/master/nacl
salt-call nacl.keygen sk_file=/etc/salt/pki/master/nacl pk_file=/etc/salt/pki/master/nacl.pub
salt-call --local nacl.keygen
'''
b = libnacl.secret.SecretBox()
key = b.sk
key = base64.b64encode(key)
if keyfile:
if os.path.isfile(keyfile):
raise Exception('file already found: {0}'.format(keyfile))
with salt.utils.files.fopen(keyfile, 'w') as keyf:
keyf.write(key)
return 'saved: {0}'.format(keyfile)
return key
if sk_file is None:
kp = libnacl.public.SecretKey()
return {'sk': base64.b64encode(kp.sk), 'pk': base64.b64encode(kp.pk)}
if pk_file is None:
pk_file = '{0}.pub'.format(sk_file)
if sk_file and pk_file is None:
if not os.path.isfile(sk_file):
kp = libnacl.public.SecretKey()
with salt.utils.fopen(sk_file, 'w') as keyf:
keyf.write(base64.b64encode(kp.sk))
if salt.utils.is_windows():
cur_user = salt.utils.win_functions.get_current_user()
salt.utils.win_dacl.set_owner(sk_file, cur_user)
salt.utils.win_dacl.set_permissions(sk_file, cur_user, 'full_control', 'grant', reset_perms=True, protected=True)
else:
# chmod 0600 file
os.chmod(sk_file, 1536)
return 'saved sk_file: {0}'.format(sk_file)
else:
raise Exception('sk_file:{0} already exist.'.format(sk_file))
if sk_file is None and pk_file:
raise Exception('sk_file: Must be set inorder to generate a public key.')
if os.path.isfile(sk_file) and os.path.isfile(pk_file):
raise Exception('sk_file:{0} and pk_file:{1} already exist.'.format(sk_file, pk_file))
if os.path.isfile(sk_file) and not os.path.isfile(pk_file):
# generate pk using the sk
with salt.utils.fopen(sk_file, 'rb') as keyf:
sk = str(keyf.read()).rstrip('\n')
sk = base64.b64decode(sk)
kp = libnacl.public.SecretKey(sk)
with salt.utils.fopen(pk_file, 'w') as keyf:
keyf.write(base64.b64encode(kp.pk))
return 'saved pk_file: {0}'.format(pk_file)
kp = libnacl.public.SecretKey()
with salt.utils.fopen(sk_file, 'w') as keyf:
keyf.write(base64.b64encode(kp.sk))
if salt.utils.is_windows():
cur_user = salt.utils.win_functions.get_current_user()
salt.utils.win_dacl.set_owner(sk_file, cur_user)
salt.utils.win_dacl.set_permissions(sk_file, cur_user, 'full_control', 'grant', reset_perms=True, protected=True)
else:
# chmod 0600 file
os.chmod(sk_file, 1536)
with salt.utils.fopen(pk_file, 'w') as keyf:
keyf.write(base64.b64encode(kp.pk))
return 'saved sk_file:{0} pk_file: {1}'.format(sk_file, pk_file)
def enc(data, **kwargs):
'''
Takes a key generated from `nacl.keygen` and encrypt some data.
Alias to `{box_type}_encrypt`
box_type: secretbox, sealedbox(default)
'''
box_type = _get_config(**kwargs)['box_type']
if box_type == 'sealedbox':
return sealedbox_encrypt(data, **kwargs)
if box_type == 'secretbox':
return secretbox_encrypt(data, **kwargs)
return sealedbox_encrypt(data, **kwargs)
def enc_file(name, out=None, **kwargs):
'''
This is a helper function to encrypt a file and return its contents.
You can provide an optional output file using `out`
`name` can be a local file or when not using `salt-run` can be a url like `salt://`, `https://` etc.
CLI Examples:
.. code-block:: bash
salt-run nacl.enc datatoenc
salt-run nacl.enc datatoenc keyfile=/root/.nacl
salt-run nacl.enc datatoenc key='cKEzd4kXsbeCE7/nLTIqXwnUiD1ulg4NoeeYcCFpd9k='
salt-run nacl.enc_file name=/tmp/id_rsa
salt-call nacl.enc_file name=salt://crt/mycert out=/tmp/cert
salt-run nacl.enc_file name=/tmp/id_rsa box_type=secretbox \
sk_file=/etc/salt/pki/master/nacl.pub
'''
key = _get_key(**kwargs)
sk = base64.b64decode(key)
b = libnacl.secret.SecretBox(sk)
return base64.b64encode(b.encrypt(data))
try:
data = __salt__['cp.get_file_str'](name)
except Exception as e:
# likly using salt-run so fallback to local filesystem
with salt.utils.fopen(name, 'rb') as f:
data = f.read()
d = enc(data, **kwargs)
if out:
if os.path.isfile(out):
raise Exception('file:{0} already exist.'.format(out))
with salt.utils.fopen(out, 'wb') as f:
f.write(d)
return 'Wrote: {0}'.format(out)
return d
def dec(data, **kwargs):
'''
Takes a key generated from `nacl.keygen` and decrypt some data.
Alias to `{box_type}_decrypt`
box_type: secretbox, sealedbox(default)
'''
box_type = _get_config(**kwargs)['box_type']
if box_type == 'sealedbox':
return sealedbox_decrypt(data, **kwargs)
if box_type == 'secretbox':
return secretbox_decrypt(data, **kwargs)
return sealedbox_decrypt(data, **kwargs)
def dec_file(name, out=None, **kwargs):
'''
This is a helper function to decrypt a file and return its contents.
You can provide an optional output file using `out`
`name` can be a local file or when not using `salt-run` can be a url like `salt://`, `https://` etc.
CLI Examples:
.. code-block:: bash
salt-run nacl.dec pEXHQM6cuaF7A=
salt-run nacl.dec data='pEXHQM6cuaF7A=' keyfile=/root/.nacl
salt-run nacl.dec data='pEXHQM6cuaF7A=' key='cKEzd4kXsbeCE7/nLTIqXwnUiD1ulg4NoeeYcCFpd9k='
salt-run nacl.dec_file name=/tmp/id_rsa.nacl
salt-call nacl.dec_file name=salt://crt/mycert.nacl out=/tmp/id_rsa
salt-run nacl.dec_file name=/tmp/id_rsa.nacl box_type=secretbox \
sk_file=/etc/salt/pki/master/nacl.pub
'''
key = _get_key(**kwargs)
sk = base64.b64decode(key)
b = libnacl.secret.SecretBox(key=sk)
try:
data = __salt__['cp.get_file_str'](name)
except Exception as e:
# likly using salt-run so fallback to local filesystem
with salt.utils.fopen(name, 'rb') as f:
data = f.read()
d = dec(data, **kwargs)
if out:
if os.path.isfile(out):
raise Exception('file:{0} already exist.'.format(out))
with salt.utils.fopen(out, 'wb') as f:
f.write(d)
return 'Wrote: {0}'.format(out)
return d
def sealedbox_encrypt(data, **kwargs):
'''
Encrypt data using a public key generated from `nacl.keygen`.
The encryptd data can be decrypted using `nacl.sealedbox_decrypt` only with the secret key.
CLI Examples:
.. code-block:: bash
salt-run nacl.sealedbox_encrypt datatoenc
salt-call --local nacl.sealedbox_encrypt datatoenc pk_file=/etc/salt/pki/master/nacl.pub
salt-call --local nacl.sealedbox_encrypt datatoenc pk='vrwQF7cNiNAVQVAiS3bvcbJUnF0cN6fU9YTZD9mBfzQ='
'''
pk = _get_pk(**kwargs)
b = libnacl.sealed.SealedBox(pk)
return base64.b64encode(b.encrypt(data))
def sealedbox_decrypt(data, **kwargs):
'''
Decrypt data using a secret key that was encrypted using a public key with `nacl.sealedbox_encrypt`.
CLI Examples:
.. code-block:: bash
salt-call nacl.sealedbox_decrypt pEXHQM6cuaF7A=
salt-call --local nacl.sealedbox_decrypt data='pEXHQM6cuaF7A=' sk_file=/etc/salt/pki/master/nacl
salt-call --local nacl.sealedbox_decrypt data='pEXHQM6cuaF7A=' sk='YmFkcGFzcwo='
'''
if data is None:
return None
sk = _get_sk(**kwargs)
keypair = libnacl.public.SecretKey(sk)
b = libnacl.sealed.SealedBox(keypair)
return b.decrypt(base64.b64decode(data))
def secretbox_encrypt(data, **kwargs):
'''
Encrypt data using a secret key generated from `nacl.keygen`.
The same secret key can be used to decrypt the data using `nacl.secretbox_decrypt`.
CLI Examples:
.. code-block:: bash
salt-run nacl.secretbox_encrypt datatoenc
salt-call --local nacl.secretbox_encrypt datatoenc sk_file=/etc/salt/pki/master/nacl
salt-call --local nacl.secretbox_encrypt datatoenc sk='YmFkcGFzcwo='
'''
sk = _get_sk(**kwargs)
b = libnacl.secret.SecretBox(sk)
return base64.b64encode(b.encrypt(data))
def secretbox_decrypt(data, **kwargs):
'''
Decrypt data that was encrypted using `nacl.secretbox_encrypt` using the secret key
that was generated from `nacl.keygen`.
CLI Examples:
.. code-block:: bash
salt-call nacl.secretbox_decrypt pEXHQM6cuaF7A=
salt-call --local nacl.secretbox_decrypt data='pEXHQM6cuaF7A=' sk_file=/etc/salt/pki/master/nacl
salt-call --local nacl.secretbox_decrypt data='pEXHQM6cuaF7A=' sk='YmFkcGFzcwo='
'''
if data is None:
return None
key = _get_sk(**kwargs)
b = libnacl.secret.SecretBox(key=key)
return b.decrypt(base64.b64decode(data))

View File

@ -85,7 +85,7 @@ def _check_for_changes(entity_type, ret, existing, modified):
if 'generation' in existing['content'].keys():
del existing['content']['generation']
if cmp(modified['content'], existing['content']) == 0:
if modified['content'] == existing['content']:
ret['comment'] = '{entity_type} is currently enforced to the desired state. No changes made.'.format(entity_type=entity_type)
else:
ret['comment'] = '{entity_type} was enforced to the desired state. Note: Only parameters specified ' \
@ -94,7 +94,7 @@ def _check_for_changes(entity_type, ret, existing, modified):
ret['changes']['new'] = modified['content']
else:
if cmp(modified, existing) == 0:
if modified == existing:
ret['comment'] = '{entity_type} is currently enforced to the desired state. No changes made.'.format(entity_type=entity_type)
else:
ret['comment'] = '{entity_type} was enforced to the desired state. Note: Only parameters specified ' \

View File

@ -37,14 +37,16 @@ Connection module for Amazon Cloud Formation
- name: mystack
'''
from __future__ import absolute_import
# Import Python libs
from __future__ import absolute_import
import logging
import json
# Import 3rd-party libs
# Import Salt libs
import salt.utils.compat
from salt.ext import six
# Import 3rd-party libs
try:
from salt._compat import ElementTree as ET
HAS_ELEMENT_TREE = True
@ -142,10 +144,14 @@ def present(name, template_body=None, template_url=None, parameters=None, notifi
stack_policy_body = _get_template(stack_policy_body, name)
stack_policy_during_update_body = _get_template(stack_policy_during_update_body, name)
for i in [template_body, stack_policy_body, stack_policy_during_update_body]:
if isinstance(i, dict):
return i
_valid = _validate(template_body, template_url, region, key, keyid, profile)
log.debug('Validate is : {0}.'.format(_valid))
if _valid is not True:
code, message = _get_error(_valid)
code, message = _valid
ret['result'] = False
ret['comment'] = 'Template could not be validated.\n{0} \n{1}'.format(code, message)
return ret
@ -155,7 +161,7 @@ def present(name, template_body=None, template_url=None, parameters=None, notifi
template = template['GetTemplateResponse']['GetTemplateResult']['TemplateBody'].encode('ascii', 'ignore')
template = json.loads(template)
_template_body = json.loads(template_body)
compare = cmp(template, _template_body)
compare = salt.utils.compat.cmp(template, _template_body)
if compare != 0:
log.debug('Templates are not the same. Compare value is {0}'.format(compare))
# At this point we should be able to run update safely since we already validated the template
@ -251,7 +257,7 @@ def _get_template(template, name):
def _validate(template_body=None, template_url=None, region=None, key=None, keyid=None, profile=None):
# Validates template. returns true if template syntax is correct.
validate = __salt__['boto_cfn.validate_template'](template_body, template_url, region, key, keyid, profile)
log.debug('Validate is result is {0}.'.format(str(validate)))
log.debug('Validate result is {0}.'.format(str(validate)))
if isinstance(validate, six.string_types):
code, message = _get_error(validate)
log.debug('Validate error is {0} and message is {1}.'.format(code, message))

View File

@ -274,7 +274,7 @@ def user_absent(name, delete_keys=True, delete_mfa_devices=True, delete_profile=
if profile_deleted:
ret['comment'] = ' '.join([ret['comment'], 'IAM user {0} login profile is deleted.'.format(name)])
if __opts__['test']:
ret['comment'] = ' '.join([ret['comment'], 'IAM user {0} policies are set to be deleted.'.format(name)])
ret['comment'] = ' '.join([ret['comment'], 'IAM user {0} managed policies are set to be detached.'.format(name)])
ret['result'] = None
else:
_ret = _user_policies_detached(name, region, key, keyid, profile)
@ -283,6 +283,16 @@ def user_absent(name, delete_keys=True, delete_mfa_devices=True, delete_profile=
ret['result'] = _ret['result']
if ret['result'] is False:
return ret
if __opts__['test']:
ret['comment'] = ' '.join([ret['comment'], 'IAM user {0} inline policies are set to be deleted.'.format(name)])
ret['result'] = None
else:
_ret = _user_policies_deleted(name, region, key, keyid, profile)
ret['comment'] = ' '.join([ret['comment'], _ret['comment']])
if not _ret['result']:
ret['result'] = _ret['result']
if ret['result'] is False:
return ret
# finally, actually delete the user
if __opts__['test']:
ret['comment'] = ' '.join([ret['comment'], 'IAM user {0} is set to be deleted.'.format(name)])
@ -738,7 +748,49 @@ def _user_policies_detached(
newpolicies = [x.get('policy_arn') for x in _list]
ret['changes']['new'] = {'managed_policies': newpolicies}
msg = '{0} policies detached from user {1}.'
ret['comment'] = msg.format(', '.join(newpolicies), name)
ret['comment'] = msg.format(', '.join(oldpolicies), name)
return ret
def _user_policies_deleted(
name,
region=None,
key=None,
keyid=None,
profile=None):
ret = {'result': True, 'comment': '', 'changes': {}}
oldpolicies = __salt__['boto_iam.get_all_user_policies'](user_name=name,
region=region, key=key, keyid=keyid, profile=profile)
if not oldpolicies:
msg = 'No inline policies in user {0}.'.format(name)
ret['comment'] = msg
return ret
if __opts__['test']:
msg = '{0} policies to be deleted from user {1}.'
ret['comment'] = msg.format(', '.join(oldpolicies), name)
ret['result'] = None
return ret
ret['changes']['old'] = {'inline_policies': oldpolicies}
for policy_name in oldpolicies:
policy_deleted = __salt__['boto_iam.delete_user_policy'](name,
policy_name,
region=region, key=key,
keyid=keyid,
profile=profile)
if not policy_deleted:
newpolicies = __salt__['boto_iam.get_all_user_policies'](name, region=region,
key=key, keyid=keyid,
profile=profile)
ret['changes']['new'] = {'inline_policies': newpolicies}
ret['result'] = False
msg = 'Failed to detach {0} from user {1}'
ret['comment'] = msg.format(policy_name, name)
return ret
newpolicies = __salt__['boto_iam.get_all_user_policies'](name, region=region, key=key,
keyid=keyid, profile=profile)
ret['changes']['new'] = {'inline_policies': newpolicies}
msg = '{0} policies deleted from user {1}.'
ret['comment'] = msg.format(', '.join(oldpolicies), name)
return ret
@ -790,7 +842,7 @@ def group_absent(name, region=None, key=None, keyid=None, profile=None):
ret['comment'] = 'IAM Group {0} does not exist.'.format(name)
return ret
if __opts__['test']:
ret['comment'] = ' '.join([ret['comment'], 'IAM group {0} policies are set to be deleted.'.format(name)])
ret['comment'] = ' '.join([ret['comment'], 'IAM group {0} managed policies are set to be detached.'.format(name)])
ret['result'] = None
else:
_ret = _group_policies_detached(name, region, key, keyid, profile)
@ -799,6 +851,16 @@ def group_absent(name, region=None, key=None, keyid=None, profile=None):
ret['result'] = _ret['result']
if ret['result'] is False:
return ret
if __opts__['test']:
ret['comment'] = ' '.join([ret['comment'], 'IAM group {0} inline policies are set to be deleted.'.format(name)])
ret['result'] = None
else:
_ret = _group_policies_deleted(name, region, key, keyid, profile)
ret['comment'] = ' '.join([ret['comment'], _ret['comment']])
if not _ret['result']:
ret['result'] = _ret['result']
if ret['result'] is False:
return ret
ret['comment'] = ' '.join([ret['comment'], 'IAM group {0} users are set to be removed.'.format(name)])
existing_users = __salt__['boto_iam.get_group_members'](group_name=name, region=region, key=key, keyid=keyid, profile=profile)
_ret = _case_group(ret, [], name, existing_users, region, key, keyid, profile)
@ -1152,6 +1214,48 @@ def _group_policies_detached(
return ret
def _group_policies_deleted(
name,
region=None,
key=None,
keyid=None,
profile=None):
ret = {'result': True, 'comment': '', 'changes': {}}
oldpolicies = __salt__['boto_iam.get_all_group_policies'](group_name=name,
region=region, key=key, keyid=keyid, profile=profile)
if not oldpolicies:
msg = 'No inline policies in group {0}.'.format(name)
ret['comment'] = msg
return ret
if __opts__['test']:
msg = '{0} policies to be deleted from group {1}.'
ret['comment'] = msg.format(', '.join(oldpolicies), name)
ret['result'] = None
return ret
ret['changes']['old'] = {'inline_policies': oldpolicies}
for policy_name in oldpolicies:
policy_deleted = __salt__['boto_iam.delete_group_policy'](name,
policy_name,
region=region, key=key,
keyid=keyid,
profile=profile)
if not policy_deleted:
newpolicies = __salt__['boto_iam.get_all_group_policies'](name, region=region,
key=key, keyid=keyid,
profile=profile)
ret['changes']['new'] = {'inline_policies': newpolicies}
ret['result'] = False
msg = 'Failed to detach {0} from group {1}'
ret['comment'] = msg.format(policy_name, name)
return ret
newpolicies = __salt__['boto_iam.get_all_group_policies'](name, region=region, key=key,
keyid=keyid, profile=profile)
ret['changes']['new'] = {'inline_policies': newpolicies}
msg = '{0} policies deleted from group {1}.'
ret['comment'] = msg.format(', '.join(oldpolicies), name)
return ret
def account_policy(name=None, allow_users_to_change_password=None,
hard_expiry=None, max_password_age=None,
minimum_password_length=None, password_reuse_prevention=None,

View File

@ -8,6 +8,12 @@ For documentation on setting up the cisconso proxy minion look in the documentat
for :mod:`salt.proxy.cisconso <salt.proxy.cisconso>`.
'''
# Import Python libs
from __future__ import absolute_import
# Import Salt libs
import salt.utils.compat
def __virtual__():
return 'cisconso.set_data_value' in __salt__
@ -53,7 +59,7 @@ def value_present(name, datastore, path, config):
existing = __salt__['cisconso.get_data'](datastore, path)
if cmp(existing, config):
if salt.utils.compat.cmp(existing, config):
ret['result'] = True
ret['comment'] = 'Config is already set'

View File

@ -146,7 +146,7 @@ def running(name,
.. _docker-container-running-skip-translate:
skip_translate
This function translates Salt CLI input into the format which
This function translates Salt CLI or SLS input into the format which
docker-py_ expects. However, in the event that Salt's translation logic
fails (due to potential changes in the Docker Remote API, or to bugs in
the translation code), this argument can be used to exert granular
@ -678,24 +678,14 @@ def running(name,
- foo2.domain.tld
domainname
Set custom DNS search domains. Can be expressed as a comma-separated
list or a YAML list. The below two examples are equivalent:
The domain name to use for the container
.. code-block:: yaml
foo:
docker_container.running:
- image: bar/baz:latest
- dommainname: domain.tld,domain2.tld
.. code-block:: yaml
foo:
docker_container.running:
- image: bar/baz:latest
- dommainname:
- domain.tld
- domain2.tld
- dommainname: domain.tld
entrypoint
Entrypoint for the container

View File

@ -1437,7 +1437,7 @@ def absent(name):
ret['comment'] = 'File {0} is set for removal'.format(name)
return ret
try:
if salt.utils.is_windows():
if salt.utils.platform.is_windows():
__salt__['file.remove'](name, force=True)
else:
__salt__['file.remove'](name)
@ -1454,7 +1454,7 @@ def absent(name):
ret['comment'] = 'Directory {0} is set for removal'.format(name)
return ret
try:
if salt.utils.is_windows():
if salt.utils.platform.is_windows():
__salt__['file.remove'](name, force=True)
else:
__salt__['file.remove'](name)

View File

@ -27,7 +27,7 @@ from salt.states.git import _fail, _neutral_test
log = logging.getLogger(__name__)
HG_BINARY = 'hg.exe' if salt.utils.is_windows() else 'hg'
HG_BINARY = 'hg.exe' if salt.utils.platform.is_windows() else 'hg'
def __virtual__():

View File

@ -206,7 +206,7 @@ def sections_present(name, sections=None, separator='='):
ret['result'] = False
ret['comment'] = "{0}".format(err)
return ret
if cmp(dict(sections[section]), cur_section) == 0:
if dict(sections[section]) == cur_section:
ret['comment'] += 'Section unchanged {0}.\n'.format(section)
continue
elif cur_section:

View File

@ -178,15 +178,12 @@ def _fulfills_version_spec(versions, oper, desired_version,
if isinstance(versions, dict) and 'version' in versions:
versions = versions['version']
for ver in versions:
if oper == '==':
if fnmatch.fnmatch(ver, desired_version):
return True
elif salt.utils.compare_versions(ver1=ver,
oper=oper,
ver2=desired_version,
cmp_func=cmp_func,
ignore_epoch=ignore_epoch):
if (oper == '==' and fnmatch.fnmatch(ver, desired_version)) \
or salt.utils.compare_versions(ver1=ver,
oper=oper,
ver2=desired_version,
cmp_func=cmp_func,
ignore_epoch=ignore_epoch):
return True
return False

View File

@ -84,7 +84,7 @@ def present(name, **kwargs):
for right in kwargs['rights']:
for key in right:
right[key] = str(right[key])
if cmp(sorted(kwargs['rights']), sorted(usergroup['rights'])) != 0:
if sorted(kwargs['rights']) != sorted(usergroup['rights']):
update_rights = True
else:
update_rights = True

View File

@ -47,6 +47,7 @@ import salt.config
import salt.loader
import salt.template
import salt.utils # Can be removed when pem_finger is moved
import salt.utils.compat
import salt.utils.event
import salt.utils.files
import salt.utils.platform
@ -3055,7 +3056,7 @@ def diff_node_cache(prov_dir, node, new_data, opts):
# Perform a simple diff between the old and the new data, and if it differs,
# return both dicts.
# TODO: Return an actual diff
diff = cmp(new_data, cache_data)
diff = salt.utils.compat.cmp(new_data, cache_data)
if diff != 0:
fire_event(
'event',

View File

@ -46,3 +46,15 @@ def deepcopy_bound(name):
finally:
copy._deepcopy_dispatch = pre_dispatch
return ret
def cmp(x, y):
'''
Compatibility helper function to replace the ``cmp`` function from Python 2. The
``cmp`` function is no longer available in Python 3.
cmp(x, y) -> integer
Return negative if x<y, zero if x==y, positive if x>y.
'''
return (x > y) - (x < y)

View File

@ -188,9 +188,8 @@ def memoize(func):
str_args.append(str(arg))
else:
str_args.append(arg)
args = str_args
args_ = ','.join(list(args) + ['{0}={1}'.format(k, kwargs[k]) for k in sorted(kwargs)])
args_ = ','.join(list(str_args) + ['{0}={1}'.format(k, kwargs[k]) for k in sorted(kwargs)])
if args_ not in cache:
cache[args_] = func(*args, **kwargs)
return cache[args_]

View File

@ -387,7 +387,7 @@ def dns(val, **kwargs):
def domainname(val, **kwargs): # pylint: disable=unused-argument
return _translate_stringlist(val)
return _translate_str(val)
def entrypoint(val, **kwargs): # pylint: disable=unused-argument

View File

@ -777,7 +777,8 @@ def _render(template, render, renderer, template_dict, opts):
blacklist = opts.get('renderer_blacklist')
whitelist = opts.get('renderer_whitelist')
ret = compile_template(template, rend, renderer, blacklist, whitelist, **template_dict)
ret = ret.read()
if salt.utils.stringio.is_readable(ret):
ret = ret.read()
if str(ret).startswith('#!') and not str(ret).startswith('#!/'):
ret = str(ret).split('\n', 1)[1]
return ret

View File

@ -274,6 +274,8 @@ class ReactWrap(object):
try:
f_call = salt.utils.format_call(l_fun, low)
kwargs = f_call.get('kwargs', {})
if 'arg' not in kwargs:
kwargs['arg'] = []
if 'kwarg' not in kwargs:
kwargs['kwarg'] = {}

View File

@ -726,6 +726,43 @@ class PkgTest(ModuleCase, SaltReturnAssertsMixin):
ret = self.run_state('pkg.removed', name=target)
self.assertSaltTrueReturn(ret)
def test_pkg_014_installed_missing_release(self, grains=None): # pylint: disable=unused-argument
'''
Tests that a version number missing the release portion still resolves
as correctly installed. For example, version 2.0.2 instead of 2.0.2-1.el7
'''
os_family = grains.get('os_family', '')
if os_family.lower() != 'redhat':
self.skipTest('Test only runs on RedHat OS family')
pkg_targets = _PKG_TARGETS.get(os_family, [])
# Make sure that we have targets that match the os_family. If this
# fails then the _PKG_TARGETS dict above needs to have an entry added,
# with two packages that are not installed before these tests are run
self.assertTrue(pkg_targets)
target = pkg_targets[0]
version = self.run_function('pkg.version', [target])
# If this assert fails, we need to find new targets, this test needs to
# be able to test successful installation of packages, so this package
# needs to not be installed before we run the states below
self.assertFalse(version)
ret = self.run_state(
'pkg.installed',
name=target,
version=salt.utils.str_version_to_evr(version)[1],
refresh=False,
)
self.assertSaltTrueReturn(ret)
# Clean up
ret = self.run_state('pkg.removed', name=target)
self.assertSaltTrueReturn(ret)
@requires_salt_modules('pkg.group_install')
@requires_system_grains
def test_group_installed_handle_missing_package_group(self, grains=None): # pylint: disable=unused-argument

View File

@ -821,7 +821,7 @@ class TranslateInputTestCase(TestCase):
expected
)
@assert_stringlist
@assert_string
def test_domainname(self):
'''
Should be a list of strings or converted to one