· 8 years ago · Nov 21, 2017, 05:18 PM
1#!/usr/bin/python
2"""
3Generate AWS4 authentication headers for your protected files
4
5This module is meant to plug into munki.
6https://github.com/munki/munki/wiki
7
8This is just a modified version of this
9http://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html#sig-v4-examples-get-auth-header
10
11"""
12import subprocess
13import re
14
15 import datetime
16 import hashlib
17 import hmac
18 import socket
19 from urlparse import urlparse
20 # pylint: disable=E0611
21 from Foundation import CFPreferencesCopyAppValue
22 # pylint: enable=E0611
23 BUNDLE_ID = 'ManagedInstalls'
24 __version__ = '1.2'
25 METHOD = 'GET'
26 SERVICE = 's3'
27 def pref(pref_name):
28 """Return a preference. See munkicommon.py for details
29 """
30 pref_value = CFPreferencesCopyAppValue(pref_name, BUNDLE_ID)
31 return pref_value
32
33 ACCESS_KEY = pref('AccessKey')
34 SECRET_KEY = pref('SecretKey')
35 REGION = pref('Region')
36 S3_ENDPOINT = pref('S3Endpoint') or 's3.amazonaws.com'
37
38 def sign(key, msg):
39 return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
40
41 def get_signature_key(key, datestamp, region, service):
42 kdate = sign(('AWS4' + key).encode('utf-8'), datestamp)
43 kregion = sign(kdate, region)
44 kservice = sign(kregion, service)
45 ksigning = sign(kservice, 'aws4_request')
46 return ksigning
47
48 def uri_from_url(url):
49 parse = urlparse(url)
50 return parse.path
51
52 def host_from_url(url):
53 parse = urlparse(url)
54 return parse.hostname
55
56 def s3_auth_headers(url):
57 """
58 Returns a dict that contains all the required header information.
59 Each header is unique to the url requested.
60 """
61
62 # Create a date for headers and the credential string
63 time_now = datetime.datetime.utcnow()
64 amzdate = time_now.strftime('%Y%m%dT%H%M%SZ')
65 datestamp = time_now.strftime('%Y%m%d') # Date w/o time, used in credential scope
66 uri = uri_from_url(url)
67 host = host_from_url(url)
68 canonical_uri = uri
69 canonical_querystring = ''
70 canonical_headers = 'host:{}\nx-amz-date:{}\n'.format(host, amzdate)
71 signed_headers = 'host;x-amz-date'
72 payload_hash = hashlib.sha256('').hexdigest()
73 canonical_request = '{}\n{}\n{}\n{}\n{}\n{}'.format(METHOD, canonical_uri, canonical_querystring, canonical_headers, signed_headers, payload_hash)
74 algorithm = 'AWS4-HMAC-SHA256'
75 credential_scope = '{}/{}/{}/aws4_request'.format(datestamp, REGION, SERVICE)
76 hashed_request = hashlib.sha256(canonical_request).hexdigest()
77 string_to_sign = '{}\n{}\n{}\n{}'.format(algorithm, amzdate, credential_scope, hashed_request)
78 signing_key = get_signature_key(SECRET_KEY, datestamp, REGION, SERVICE)
79 signature = hmac.new(signing_key, (string_to_sign).encode('utf-8'), hashlib.sha256).hexdigest()
80 authorization_header = ("{} Credential={}/{}," " SignedHeaders={}, Signature={}").format(algorithm, ACCESS_KEY, credential_scope, signed_headers, signature)
81 headers = {'x-amz-date': amzdate,
82 'x-amz-content-sha256': payload_hash,
83 'Authorization': authorization_header}
84 return headers
85
86def change_url(url):
87 try:
88 txt = subprocess.check_output(['nslookup', '-q=txt', 'munki-s3'])
89 url_search = re.search('\ttext = “(.*)â€\n’, txt)
90 if url_search:
91 new_url = uri_search.group(1)
92 return url.replace(S3_ENDPOINT, new_url)
93 except subprocess.CalledProcessError:
94 pass
95 return url
96
97 def process_request_options(options):
98 """Make changes to options dict and return it.
99 This is the fuction that munki calls."""
100 if S3_ENDPOINT in options['url']:
101 options[‘url’] = change_url(options[‘url’])
102 headers = s3_auth_headers(options['url'])
103 options['additional_headers'].update(headers)
104 return options