Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/awscli/customizations/cloudfront.py
1566 views
1
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License"). You
4
# may not use this file except in compliance with the License. A copy of
5
# the License is located at
6
#
7
# http://aws.amazon.com/apache2.0/
8
#
9
# or in the "license" file accompanying this file. This file is
10
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
# ANY KIND, either express or implied. See the License for the specific
12
# language governing permissions and limitations under the License.
13
import sys
14
import time
15
import random
16
17
import rsa
18
from botocore.utils import parse_to_aware_datetime
19
from botocore.signers import CloudFrontSigner
20
21
from awscli.arguments import CustomArgument
22
from awscli.customizations.utils import validate_mutually_exclusive_handler
23
from awscli.customizations.commands import BasicCommand
24
from awscli.utils import create_nested_client
25
26
27
def register(event_handler):
28
event_handler.register('building-command-table.cloudfront', _add_sign)
29
30
# Provides a simpler --paths for ``aws cloudfront create-invalidation``
31
event_handler.register(
32
'building-argument-table.cloudfront.create-invalidation', _add_paths)
33
event_handler.register(
34
'operation-args-parsed.cloudfront.create-invalidation',
35
validate_mutually_exclusive_handler(['invalidation_batch'], ['paths']))
36
37
event_handler.register(
38
'operation-args-parsed.cloudfront.create-distribution',
39
validate_mutually_exclusive_handler(
40
['default_root_object', 'origin_domain_name'],
41
['distribution_config']))
42
event_handler.register(
43
'building-argument-table.cloudfront.create-distribution',
44
lambda argument_table, **kwargs: argument_table.__setitem__(
45
'origin-domain-name', OriginDomainName(argument_table)))
46
event_handler.register(
47
'building-argument-table.cloudfront.create-distribution',
48
lambda argument_table, **kwargs: argument_table.__setitem__(
49
'default-root-object', CreateDefaultRootObject(argument_table)))
50
51
context = {}
52
event_handler.register(
53
'top-level-args-parsed', context.update, unique_id='cloudfront')
54
event_handler.register(
55
'operation-args-parsed.cloudfront.update-distribution',
56
validate_mutually_exclusive_handler(
57
['default_root_object'], ['distribution_config']))
58
event_handler.register(
59
'building-argument-table.cloudfront.update-distribution',
60
lambda argument_table, **kwargs: argument_table.__setitem__(
61
'default-root-object', UpdateDefaultRootObject(
62
context=context, argument_table=argument_table)))
63
64
65
def unique_string(prefix='cli'):
66
return '%s-%s-%s' % (prefix, int(time.time()), random.randint(1, 1000000))
67
68
69
def _add_paths(argument_table, **kwargs):
70
argument_table['invalidation-batch'].required = False
71
argument_table['paths'] = PathsArgument()
72
73
74
class PathsArgument(CustomArgument):
75
76
def __init__(self):
77
doc = (
78
'The space-separated paths to be invalidated.'
79
' Note: --invalidation-batch and --paths are mutually exclusive.'
80
)
81
super(PathsArgument, self).__init__('paths', nargs='+', help_text=doc)
82
83
def add_to_params(self, parameters, value):
84
if value is not None:
85
parameters['InvalidationBatch'] = {
86
"CallerReference": unique_string(),
87
"Paths": {"Quantity": len(value), "Items": value},
88
}
89
90
91
class ExclusiveArgument(CustomArgument):
92
DOC = '%s This argument and --%s are mutually exclusive.'
93
94
def __init__(self, name, argument_table,
95
exclusive_to='distribution-config', help_text=''):
96
argument_table[exclusive_to].required = False
97
super(ExclusiveArgument, self).__init__(
98
name, help_text=self.DOC % (help_text, exclusive_to))
99
100
def distribution_config_template(self):
101
return {
102
"CallerReference": unique_string(),
103
"Origins": {"Quantity": 0, "Items": []},
104
"DefaultCacheBehavior": {
105
"TargetOriginId": "placeholder",
106
"ForwardedValues": {
107
"QueryString": False,
108
"Cookies": {"Forward": "none"},
109
},
110
"TrustedSigners": {
111
"Enabled": False,
112
"Quantity": 0
113
},
114
"ViewerProtocolPolicy": "allow-all",
115
"MinTTL": 0
116
},
117
"Enabled": True,
118
"Comment": "",
119
}
120
121
122
class OriginDomainName(ExclusiveArgument):
123
def __init__(self, argument_table):
124
super(OriginDomainName, self).__init__(
125
'origin-domain-name', argument_table,
126
help_text='The domain name for your origin.')
127
128
def add_to_params(self, parameters, value):
129
if value is None:
130
return
131
parameters.setdefault(
132
'DistributionConfig', self.distribution_config_template())
133
origin_id = unique_string(prefix=value)
134
item = {"Id": origin_id, "DomainName": value, "OriginPath": ''}
135
if item['DomainName'].endswith('.s3.amazonaws.com'):
136
# We do not need to detect '.s3[\w-].amazonaws.com' as S3 buckets,
137
# because CloudFront treats GovCloud S3 buckets as custom domain.
138
# http://docs.aws.amazon.com/govcloud-us/latest/UserGuide/setting-up-cloudfront.html
139
item["S3OriginConfig"] = {"OriginAccessIdentity": ""}
140
else:
141
item["CustomOriginConfig"] = {
142
'HTTPPort': 80, 'HTTPSPort': 443,
143
'OriginProtocolPolicy': 'http-only'}
144
parameters['DistributionConfig']['Origins'] = {
145
"Quantity": 1, "Items": [item]}
146
parameters['DistributionConfig']['DefaultCacheBehavior'][
147
'TargetOriginId'] = origin_id
148
149
150
class CreateDefaultRootObject(ExclusiveArgument):
151
def __init__(self, argument_table, help_text=''):
152
super(CreateDefaultRootObject, self).__init__(
153
'default-root-object', argument_table, help_text=help_text or (
154
'The object that you want CloudFront to return (for example, '
155
'index.html) when a viewer request points to your root URL.'))
156
157
def add_to_params(self, parameters, value):
158
if value is not None:
159
parameters.setdefault(
160
'DistributionConfig', self.distribution_config_template())
161
parameters['DistributionConfig']['DefaultRootObject'] = value
162
163
164
class UpdateDefaultRootObject(CreateDefaultRootObject):
165
def __init__(self, context, argument_table):
166
super(UpdateDefaultRootObject, self).__init__(
167
argument_table, help_text=(
168
'The object that you want CloudFront to return (for example, '
169
'index.html) when a viewer request points to your root URL. '
170
'CLI will automatically make a get-distribution-config call '
171
'to load and preserve your other settings.'))
172
self.context = context
173
174
def add_to_params(self, parameters, value):
175
if value is not None:
176
client = create_nested_client(
177
self.context['session'],
178
'cloudfront',
179
region_name=self.context['parsed_args'].region,
180
endpoint_url=self.context['parsed_args'].endpoint_url,
181
verify=self.context['parsed_args'].verify_ssl)
182
response = client.get_distribution_config(Id=parameters['Id'])
183
parameters['IfMatch'] = response['ETag']
184
parameters['DistributionConfig'] = response['DistributionConfig']
185
parameters['DistributionConfig']['DefaultRootObject'] = value
186
187
188
def _add_sign(command_table, session, **kwargs):
189
command_table['sign'] = SignCommand(session)
190
191
192
class SignCommand(BasicCommand):
193
NAME = 'sign'
194
DESCRIPTION = 'Sign a given url.'
195
DATE_FORMAT = """Supported formats include:
196
YYYY-MM-DD (which means 0AM UTC of that day),
197
YYYY-MM-DDThh:mm:ss (with default timezone as UTC),
198
YYYY-MM-DDThh:mm:ss+hh:mm or YYYY-MM-DDThh:mm:ss-hh:mm (with offset),
199
or EpochTime (which always means UTC).
200
Do NOT use YYYYMMDD, because it will be treated as EpochTime."""
201
ARG_TABLE = [
202
{
203
'name': 'url',
204
'no_paramfile': True, # To disable the default paramfile behavior
205
'required': True,
206
'help_text': 'The URL to be signed',
207
},
208
{
209
'name': 'key-pair-id',
210
'required': True,
211
'help_text': (
212
"The active CloudFront key pair Id for the key pair "
213
"that you're using to generate the signature."),
214
},
215
{
216
'name': 'private-key',
217
'required': True,
218
'help_text': 'file://path/to/your/private-key.pem',
219
},
220
{
221
'name': 'date-less-than', 'required': True,
222
'help_text':
223
'The expiration date and time for the URL. ' + DATE_FORMAT,
224
},
225
{
226
'name': 'date-greater-than',
227
'help_text':
228
'An optional start date and time for the URL. ' + DATE_FORMAT,
229
},
230
{
231
'name': 'ip-address',
232
'help_text': (
233
'An optional IP address or IP address range to allow client '
234
'making the GET request from. Format: x.x.x.x/x or x.x.x.x'),
235
},
236
]
237
238
def _run_main(self, args, parsed_globals):
239
signer = CloudFrontSigner(
240
args.key_pair_id, RSASigner(args.private_key).sign)
241
date_less_than = parse_to_aware_datetime(args.date_less_than)
242
date_greater_than = args.date_greater_than
243
if date_greater_than is not None:
244
date_greater_than = parse_to_aware_datetime(date_greater_than)
245
if date_greater_than is not None or args.ip_address is not None:
246
policy = signer.build_policy(
247
args.url, date_less_than, date_greater_than=date_greater_than,
248
ip_address=args.ip_address)
249
sys.stdout.write(signer.generate_presigned_url(
250
args.url, policy=policy))
251
else:
252
sys.stdout.write(signer.generate_presigned_url(
253
args.url, date_less_than=date_less_than))
254
return 0
255
256
257
class RSASigner(object):
258
def __init__(self, private_key):
259
self.priv_key = rsa.PrivateKey.load_pkcs1(private_key.encode('utf8'))
260
261
def sign(self, message):
262
return rsa.sign(message, self.priv_key, 'SHA-1')
263
264