* Added a sha256 method to module_common

* Added a sha256sum parameter to the get_url module to enable
cryptographic verification of downloaded files
* Fixed a few typos in the documentation
This commit is contained in:
Joshua Lund 2013-06-04 23:28:28 -06:00
parent 0b6570eef8
commit 2ce7f136b2
2 changed files with 58 additions and 12 deletions

View file

@ -86,11 +86,18 @@ try:
except ImportError: except ImportError:
pass pass
HAVE_HASHLIB=False
try: try:
from hashlib import md5 as _md5 from hashlib import md5 as _md5
HAVE_HASHLIB=True
except ImportError: except ImportError:
from md5 import md5 as _md5 from md5 import md5 as _md5
try:
from hashlib import sha256 as _sha256
except ImportError:
pass
try: try:
from systemd import journal from systemd import journal
has_journal = True has_journal = True
@ -787,13 +794,13 @@ class AnsibleModule(object):
or stat.S_IXGRP & os.stat(path)[stat.ST_MODE] or stat.S_IXGRP & os.stat(path)[stat.ST_MODE]
or stat.S_IXOTH & os.stat(path)[stat.ST_MODE]) or stat.S_IXOTH & os.stat(path)[stat.ST_MODE])
def md5(self, filename): def digest_from_file(self, filename, digest_method):
''' Return MD5 hex digest of local file, or None if file is not present. ''' ''' Return hex digest of local file for a given digest_method, or None if file is not present. '''
if not os.path.exists(filename): if not os.path.exists(filename):
return None return None
if os.path.isdir(filename): if os.path.isdir(filename):
self.fail_json(msg="attempted to take md5sum of directory: %s" % filename) self.fail_json(msg="attempted to take checksum of directory: %s" % filename)
digest = _md5() digest = digest_method
blocksize = 64 * 1024 blocksize = 64 * 1024
infile = open(filename, 'rb') infile = open(filename, 'rb')
block = infile.read(blocksize) block = infile.read(blocksize)
@ -803,6 +810,16 @@ class AnsibleModule(object):
infile.close() infile.close()
return digest.hexdigest() return digest.hexdigest()
def md5(self, filename):
''' Return MD5 hex digest of local file using digest_from_file(). '''
return self.digest_from_file(filename, _md5())
def sha256(self, filename):
''' Return SHA-256 hex digest of local file using digest_from_file(). '''
if not HAVE_HASHLIB:
self.fail_json(msg="SHA-256 checksums require hashlib, which is available in Python 2.5 and higher")
return self.digest_from_file(filename, _sha256())
def backup_local(self, fn): def backup_local(self, fn):
'''make a date-marked backup of the specified file, return True or False on success or failure''' '''make a date-marked backup of the specified file, return True or False on success or failure'''
# backups named basename-YYYY-MM-DD@HH:MM~ # backups named basename-YYYY-MM-DD@HH:MM~

View file

@ -33,7 +33,7 @@ description:
server I(must) have direct access to the remote resource. server I(must) have direct access to the remote resource.
- By default, if an environment variable C(<protocol>_proxy) is set on - By default, if an environment variable C(<protocol>_proxy) is set on
the target host, requests will be sent through that proxy. This the target host, requests will be sent through that proxy. This
behaviour can be overriden by setting a variable for this task behaviour can be overridden by setting a variable for this task
(see `setting the environment (see `setting the environment
<http://ansible.cc/docs/playbooks2.html#setting-the-environment-and-working-with-proxies>`_), <http://ansible.cc/docs/playbooks2.html#setting-the-environment-and-working-with-proxies>`_),
or by using the use_proxy option. or by using the use_proxy option.
@ -53,19 +53,26 @@ options:
default: null default: null
force: force:
description: description:
- if C(yes), will download the file every time and replace the - If C(yes), will download the file every time and replace the
file if the contents change. If C(no), the file will only be downloaded if file if the contents change. If C(no), the file will only be downloaded if
the destination does not exist. Generally should be C(yes) only for small the destination does not exist. Generally should be C(yes) only for small
local files. prior to 0.6, acts if C(yes) by default. local files. Prior to 0.6, this module behaved as if C(yes) was the default.
version_added: "0.7" version_added: "0.7"
required: false required: false
choices: [ "yes", "no" ] choices: [ "yes", "no" ]
default: "no" default: "no"
aliases: [ "thirsty" ] aliases: [ "thirsty" ]
sha256sum:
description:
- If a SHA-256 checksum is passed to this parameter, the digest of the
destination file will be calculated after it is downloaded to ensure
its integrity and verify that the transfer completed successfully.
required: false
default: null
use_proxy: use_proxy:
description: description:
- if C(no), it will not use a proxy, even if one is defined by - if C(no), it will not use a proxy, even if one is defined in
in an environment variable on the target hosts. an environment variable on the target hosts.
required: false required: false
default: 'yes' default: 'yes'
choices: ['yes', 'no'] choices: ['yes', 'no']
@ -81,20 +88,30 @@ author: Jan-Piet Mens
''' '''
EXAMPLES=''' EXAMPLES='''
# Download file.conf to /etc/foo.conf with read permissions set for the user and group
get_url: url=http://example.com/path/file.conf dest=/etc/foo.conf mode=0440 get_url: url=http://example.com/path/file.conf dest=/etc/foo.conf mode=0440
# Download file.conf to /etc/foo.conf and ensure that it matches the specified SHA-256 checksum
get_url: url=http://example.com/path/file.conf dest=/etc/foo.conf sha256sum=b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c
''' '''
HAS_URLLIB2 = True try:
import hashlib
HAS_HASHLIB=True
except ImportError:
HAS_HASHLIB=False
try: try:
import urllib2 import urllib2
HAS_URLLIB2 = True
except ImportError: except ImportError:
HAS_URLLIB2 = False HAS_URLLIB2 = False
HAS_URLPARSE = True
try: try:
import urlparse import urlparse
import socket import socket
HAS_URLPARSE = True
except ImportError: except ImportError:
HAS_URLPARSE=False HAS_URLPARSE=False
@ -217,6 +234,7 @@ def main():
url = dict(required=True), url = dict(required=True),
dest = dict(required=True), dest = dict(required=True),
force = dict(default='no', aliases=['thirsty'], type='bool'), force = dict(default='no', aliases=['thirsty'], type='bool'),
sha256sum = dict(default=''),
use_proxy = dict(default='yes', type='bool') use_proxy = dict(default='yes', type='bool')
), ),
add_file_common_args=True add_file_common_args=True
@ -225,6 +243,7 @@ def main():
url = module.params['url'] url = module.params['url']
dest = os.path.expanduser(module.params['dest']) dest = os.path.expanduser(module.params['dest'])
force = module.params['force'] force = module.params['force']
sha256sum = module.params['sha256sum']
use_proxy = module.params['use_proxy'] use_proxy = module.params['use_proxy']
if os.path.isdir(dest): if os.path.isdir(dest):
@ -273,6 +292,16 @@ def main():
else: else:
changed = False changed = False
# Check the digest of the destination file and ensure that it matches the
# sha256sum parameter if it is present
if sha256sum != '':
if not HAS_HASHLIB:
os.remove(dest)
module.fail_json(msg="The sha256sum parameter requires hashlib, which is available in Python 2.5 and higher")
if sha256sum != module.sha256(dest):
os.remove(dest)
module.fail_json(msg="The SHA-256 checksum for %s did not match %s" % (dest, sha256sum))
os.remove(tmpsrc) os.remove(tmpsrc)
# allow file attribute changes # allow file attribute changes
@ -283,7 +312,7 @@ def main():
# Mission complete # Mission complete
module.exit_json(url=url, dest=dest, src=tmpsrc, md5sum=md5sum_src, module.exit_json(url=url, dest=dest, src=tmpsrc, md5sum=md5sum_src,
changed=changed, msg=info.get('msg', '')) sha256sum=sha256sum, changed=changed, msg=info.get('msg', ''))
# this is magic, see lib/ansible/module_common.py # this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>> #<<INCLUDE_ANSIBLE_MODULE_COMMON>>