cloud-mu 3.1.6 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/bin/mu-adopt +15 -12
  4. data/bin/mu-azure-tests +57 -0
  5. data/bin/mu-cleanup +2 -4
  6. data/bin/mu-configure +37 -1
  7. data/bin/mu-deploy +3 -3
  8. data/bin/mu-findstray-tests +25 -0
  9. data/bin/mu-gen-docs +2 -4
  10. data/bin/mu-load-config.rb +2 -1
  11. data/bin/mu-run-tests +37 -12
  12. data/cloud-mu.gemspec +4 -4
  13. data/cookbooks/mu-tools/attributes/default.rb +7 -0
  14. data/cookbooks/mu-tools/libraries/helper.rb +87 -3
  15. data/cookbooks/mu-tools/recipes/apply_security.rb +39 -23
  16. data/cookbooks/mu-tools/recipes/aws_api.rb +13 -0
  17. data/cookbooks/mu-tools/recipes/google_api.rb +4 -0
  18. data/cookbooks/mu-tools/recipes/rsyslog.rb +8 -1
  19. data/cookbooks/mu-tools/resources/disk.rb +33 -12
  20. data/cookbooks/mu-tools/resources/mommacat_request.rb +1 -2
  21. data/cookbooks/mu-tools/templates/centos-8/sshd_config.erb +215 -0
  22. data/extras/clean-stock-amis +10 -2
  23. data/extras/generate-stock-images +7 -3
  24. data/extras/image-generators/AWS/centos7.yaml +19 -16
  25. data/extras/image-generators/AWS/{rhel7.yaml → rhel71.yaml} +0 -0
  26. data/extras/image-generators/AWS/{win2k12.yaml → win2k12r2.yaml} +0 -0
  27. data/modules/mommacat.ru +2 -2
  28. data/modules/mu.rb +84 -97
  29. data/modules/mu/adoption.rb +359 -59
  30. data/modules/mu/cleanup.rb +67 -44
  31. data/modules/mu/cloud.rb +108 -1754
  32. data/modules/mu/cloud/database.rb +49 -0
  33. data/modules/mu/cloud/dnszone.rb +44 -0
  34. data/modules/mu/cloud/machine_images.rb +212 -0
  35. data/modules/mu/cloud/providers.rb +81 -0
  36. data/modules/mu/cloud/resource_base.rb +929 -0
  37. data/modules/mu/cloud/server.rb +40 -0
  38. data/modules/mu/cloud/server_pool.rb +1 -0
  39. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  40. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  41. data/modules/mu/cloud/wrappers.rb +178 -0
  42. data/modules/mu/config.rb +122 -80
  43. data/modules/mu/config/alarm.rb +2 -6
  44. data/modules/mu/config/bucket.rb +32 -3
  45. data/modules/mu/config/cache_cluster.rb +2 -2
  46. data/modules/mu/config/cdn.rb +100 -0
  47. data/modules/mu/config/collection.rb +1 -1
  48. data/modules/mu/config/container_cluster.rb +2 -2
  49. data/modules/mu/config/database.rb +84 -105
  50. data/modules/mu/config/database.yml +1 -2
  51. data/modules/mu/config/dnszone.rb +5 -4
  52. data/modules/mu/config/doc_helpers.rb +4 -5
  53. data/modules/mu/config/endpoint.rb +2 -1
  54. data/modules/mu/config/firewall_rule.rb +3 -19
  55. data/modules/mu/config/folder.rb +1 -1
  56. data/modules/mu/config/function.rb +17 -8
  57. data/modules/mu/config/group.rb +1 -1
  58. data/modules/mu/config/habitat.rb +1 -1
  59. data/modules/mu/config/job.rb +89 -0
  60. data/modules/mu/config/loadbalancer.rb +57 -11
  61. data/modules/mu/config/log.rb +1 -1
  62. data/modules/mu/config/msg_queue.rb +1 -1
  63. data/modules/mu/config/nosqldb.rb +1 -1
  64. data/modules/mu/config/notifier.rb +8 -19
  65. data/modules/mu/config/ref.rb +81 -9
  66. data/modules/mu/config/role.rb +1 -1
  67. data/modules/mu/config/schema_helpers.rb +30 -34
  68. data/modules/mu/config/search_domain.rb +1 -1
  69. data/modules/mu/config/server.rb +5 -13
  70. data/modules/mu/config/server_pool.rb +3 -7
  71. data/modules/mu/config/storage_pool.rb +1 -1
  72. data/modules/mu/config/tail.rb +10 -0
  73. data/modules/mu/config/user.rb +1 -1
  74. data/modules/mu/config/vpc.rb +13 -17
  75. data/modules/mu/defaults/AWS.yaml +106 -106
  76. data/modules/mu/defaults/Azure.yaml +1 -0
  77. data/modules/mu/defaults/Google.yaml +1 -0
  78. data/modules/mu/deploy.rb +33 -19
  79. data/modules/mu/groomer.rb +15 -0
  80. data/modules/mu/groomers/chef.rb +3 -0
  81. data/modules/mu/logger.rb +120 -144
  82. data/modules/mu/master.rb +22 -1
  83. data/modules/mu/mommacat.rb +71 -26
  84. data/modules/mu/mommacat/daemon.rb +23 -14
  85. data/modules/mu/mommacat/naming.rb +82 -3
  86. data/modules/mu/mommacat/search.rb +59 -16
  87. data/modules/mu/mommacat/storage.rb +119 -48
  88. data/modules/mu/{clouds → providers}/README.md +1 -1
  89. data/modules/mu/{clouds → providers}/aws.rb +248 -62
  90. data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
  91. data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
  92. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
  93. data/modules/mu/providers/aws/cdn.rb +782 -0
  94. data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
  95. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +65 -63
  96. data/modules/mu/providers/aws/database.rb +1747 -0
  97. data/modules/mu/{clouds → providers}/aws/dnszone.rb +26 -12
  98. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  99. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +39 -32
  100. data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
  101. data/modules/mu/{clouds → providers}/aws/function.rb +291 -133
  102. data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
  103. data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
  104. data/modules/mu/providers/aws/job.rb +469 -0
  105. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +77 -47
  106. data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
  107. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
  108. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
  109. data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
  110. data/modules/mu/{clouds → providers}/aws/role.rb +112 -78
  111. data/modules/mu/{clouds → providers}/aws/search_domain.rb +172 -41
  112. data/modules/mu/{clouds → providers}/aws/server.rb +120 -145
  113. data/modules/mu/{clouds → providers}/aws/server_pool.rb +42 -60
  114. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
  115. data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
  116. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  117. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  118. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
  119. data/modules/mu/{clouds → providers}/aws/vpc.rb +141 -73
  120. data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
  121. data/modules/mu/{clouds → providers}/azure.rb +4 -1
  122. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  123. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  124. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  125. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  126. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  127. data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
  128. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  129. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  130. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  131. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  132. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  133. data/modules/mu/{clouds → providers}/cloudformation.rb +1 -1
  134. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  135. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  136. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  137. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  138. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  139. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  140. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  141. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  142. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  143. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  144. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  145. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  146. data/modules/mu/{clouds → providers}/google.rb +15 -6
  147. data/modules/mu/{clouds → providers}/google/bucket.rb +2 -2
  148. data/modules/mu/{clouds → providers}/google/container_cluster.rb +29 -14
  149. data/modules/mu/{clouds → providers}/google/database.rb +2 -9
  150. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +3 -3
  151. data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
  152. data/modules/mu/{clouds → providers}/google/function.rb +4 -4
  153. data/modules/mu/{clouds → providers}/google/group.rb +9 -17
  154. data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
  155. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +2 -2
  156. data/modules/mu/{clouds → providers}/google/role.rb +46 -35
  157. data/modules/mu/{clouds → providers}/google/server.rb +26 -11
  158. data/modules/mu/{clouds → providers}/google/server_pool.rb +11 -11
  159. data/modules/mu/{clouds → providers}/google/user.rb +32 -22
  160. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  161. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  162. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  163. data/modules/mu/{clouds → providers}/google/vpc.rb +38 -3
  164. data/modules/tests/aws-jobs-functions.yaml +46 -0
  165. data/modules/tests/centos6.yaml +15 -0
  166. data/modules/tests/centos7.yaml +15 -0
  167. data/modules/tests/centos8.yaml +12 -0
  168. data/modules/tests/ecs.yaml +2 -2
  169. data/modules/tests/eks.yaml +1 -1
  170. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  171. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  172. data/modules/tests/microservice_app.yaml +288 -0
  173. data/modules/tests/rds.yaml +108 -0
  174. data/modules/tests/regrooms/rds.yaml +123 -0
  175. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  176. data/modules/tests/super_complex_bok.yml +2 -2
  177. data/modules/tests/super_simple_bok.yml +2 -2
  178. data/spec/mu/clouds/azure_spec.rb +2 -2
  179. metadata +126 -98
  180. data/modules/mu/clouds/aws/database.rb +0 -1974
  181. data/modules/mu/clouds/aws/endpoint.rb +0 -596
@@ -27,8 +27,8 @@ module MU
27
27
 
28
28
  # Called automatically by {MU::Deploy#createResources}
29
29
  def create
30
- MU::Cloud::AWS.sns(region: @config['region'], credentials: @config['credentials']).create_topic(name: @mu_name)
31
30
  @cloud_id = @mu_name
31
+ MU::Cloud::AWS.sns(region: @config['region'], credentials: @config['credentials']).create_topic(name: @cloud_id)
32
32
  MU.log "Created SNS topic #{@mu_name}"
33
33
  end
34
34
 
@@ -36,17 +36,48 @@ module MU
36
36
  def groom
37
37
  if @config['subscriptions']
38
38
  @config['subscriptions'].each { |sub|
39
- MU::Cloud::AWS::Notifier.subscribe(
40
- arn: arn,
41
- endpoint: sub['endpoint'],
42
- region: @config['region'],
43
- credentials: @config['credentials'],
44
- protocol: sub['type']
45
- )
39
+ if sub['resource'] and !sub['endpoint']
40
+ endpoint_obj = nil
41
+ MU.retrier([], max: 5, wait: 9, loop_if: Proc.new { endpoint_obj.nil? }) {
42
+ endpoint_obj = MU::Config::Ref.get(sub['resource']).kitten(@deploy)
43
+ }
44
+ sub['endpoint'] = endpoint_obj.arn
45
+ end
46
+ subscribe(sub['endpoint'], sub['type'])
46
47
  }
47
48
  end
48
49
  end
49
50
 
51
+ # Subscribe something to this SNS topic
52
+ # @param endpoint [String]: The address, identifier, or ARN of the resource being subscribed
53
+ # @param protocol [String]: The protocol being subscribed
54
+ def subscribe(endpoint, protocol)
55
+ self.class.subscribe(arn, endpoint, protocol, region: @config['region'], credentials: @credentials)
56
+ end
57
+
58
+ # Subscribe something to an SNS topic
59
+ # @param cloud_id [String]: The short name or ARN of an existing SNS topic
60
+ # @param endpoint [String]: The address, identifier, or ARN of the resource being subscribed
61
+ # @param protocol [String]: The protocol being subscribed
62
+ # @param region [String]: The region of the target SNS topic
63
+ # @param credentials [String]:
64
+ def self.subscribe(cloud_id, endpoint, protocol, region: nil, credentials: nil)
65
+ topic = find(cloud_id: cloud_id, region: region, credentials: credentials).values.first
66
+ if !topic
67
+ raise MuError, "Failed to find SNS Topic #{cloud_id} in #{region}"
68
+ end
69
+ arn = topic["TopicArn"]
70
+
71
+ resp = MU::Cloud::AWS.sns(region: region, credentials: credentials).list_subscriptions_by_topic(topic_arn: arn).subscriptions
72
+
73
+ resp.each { |subscription|
74
+ return subscription if subscription.protocol == protocol and subscription.endpoint == endpoint
75
+ }
76
+
77
+ MU.log "Subscribing #{endpoint} (#{protocol}) to SNS topic #{arn}", MU::NOTICE
78
+ MU::Cloud::AWS.sns(region: region, credentials: credentials).subscribe(topic_arn: arn, protocol: protocol, endpoint: endpoint)
79
+ end
80
+
50
81
  # Does this resource type exist as a global (cloud-wide) artifact, or
51
82
  # is it localized to a region/zone?
52
83
  # @return [Boolean]
@@ -65,12 +96,12 @@ module MU
65
96
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
66
97
  # @param region [String]: The cloud provider region
67
98
  # @return [void]
68
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
99
+ def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
69
100
  MU.log "AWS::Notifier.cleanup: need to support flags['known']", MU::DEBUG, details: flags
70
101
  MU.log "Placeholder: AWS Notifier artifacts do not support tags, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: ignoremaster
71
102
 
72
103
  MU::Cloud::AWS.sns(region: region, credentials: credentials).list_topics.topics.each { |topic|
73
- if topic.topic_arn.match(MU.deploy_id)
104
+ if topic.topic_arn.match(deploy_id)
74
105
  # We don't have a way to tag our SNS topics, so we will delete any topic that has the MU-ID in its ARN.
75
106
  # This may fail to find notifier groups in some cases (eg. cache_cluster) so we might want to delete from each API as well.
76
107
  MU.log "Deleting SNS topic: #{topic.topic_arn}"
@@ -91,6 +122,7 @@ module MU
91
122
  # Return the metadata for this user cofiguration
92
123
  # @return [Hash]
93
124
  def notify
125
+ return nil if !@cloud_id or !cloud_desc(use_cache: false)
94
126
  desc = MU::Cloud::AWS.sns(region: @config["region"], credentials: @config["credentials"]).get_topic_attributes(topic_arn: arn).attributes
95
127
  MU.structToHash(desc)
96
128
  end
@@ -101,9 +133,16 @@ module MU
101
133
  found = {}
102
134
 
103
135
  if args[:cloud_id]
104
- arn = "arn:"+(MU::Cloud::AWS.isGovCloud?(args[:region]) ? "aws-us-gov" : "aws")+":sns:"+args[:region]+":"+MU::Cloud::AWS.credToAcct(args[:credentials])+":"+args[:cloud_id]
105
- desc = MU::Cloud::AWS.sns(region: args[:region], credentials: args[:credentials]).get_topic_attributes(topic_arn: arn).attributes
106
- found[args[:cloud_id]] = desc if desc
136
+ arn = if args[:cloud_id].match(/^arn:/)
137
+ args[:cloud_id]
138
+ else
139
+ "arn:"+(MU::Cloud::AWS.isGovCloud?(args[:region]) ? "aws-us-gov" : "aws")+":sns:"+args[:region]+":"+MU::Cloud::AWS.credToAcct(args[:credentials])+":"+args[:cloud_id]
140
+ end
141
+ begin
142
+ desc = MU::Cloud::AWS.sns(region: args[:region], credentials: args[:credentials]).get_topic_attributes(topic_arn: arn).attributes
143
+ found[args[:cloud_id]] = desc if desc
144
+ rescue ::Aws::SNS::Errors::NotFound
145
+ end
107
146
  else
108
147
  next_token = nil
109
148
  begin
@@ -120,6 +159,58 @@ module MU
120
159
  found
121
160
  end
122
161
 
162
+ # Reverse-map our cloud description into a runnable config hash.
163
+ # We assume that any values we have in +@config+ are placeholders, and
164
+ # calculate our own accordingly based on what's live in the cloud.
165
+ def toKitten(**_args)
166
+ bok = {
167
+ "cloud" => "AWS",
168
+ "credentials" => @config['credentials'],
169
+ "cloud_id" => @cloud_id,
170
+ "region" => @config['region']
171
+ }
172
+
173
+ if !cloud_desc
174
+ MU.log "toKitten failed to load a cloud_desc from #{@cloud_id}", MU::ERR, details: @config
175
+ return nil
176
+ end
177
+
178
+ bok['name'] = cloud_desc["DisplayName"].empty? ? @cloud_id : cloud_desc["DisplayName"]
179
+ svcmap = {
180
+ "lambda" => "functions",
181
+ "sqs" => "msg_queues"
182
+ }
183
+ MU::Cloud::AWS.sns(region: @config['region'], credentials: @credentials).list_subscriptions_by_topic(topic_arn: cloud_desc["TopicArn"]).subscriptions.each { |sub|
184
+ bok['subscriptions'] ||= []
185
+
186
+ bok['subscriptions'] << if sub.endpoint.match(/^arn:[^:]+:(sqs|lambda):([^:]+):(\d+):.*?([^:\/]+)$/)
187
+ _wholestring, service, region, account, id = Regexp.last_match.to_a
188
+ {
189
+ "type" => sub.protocol,
190
+ "resource" => MU::Config::Ref.get(
191
+ type: svcmap[service],
192
+ region: region,
193
+ credentials: @credentials,
194
+ id: id,
195
+ cloud: "AWS",
196
+ habitat: MU::Config::Ref.get(
197
+ id: account,
198
+ cloud: "AWS",
199
+ credentials: @credentials
200
+ )
201
+ )
202
+ }
203
+ else
204
+ {
205
+ "type" => sub.protocol,
206
+ "endpoint" => sub.endpoint
207
+ }
208
+ end
209
+ }
210
+
211
+ bok
212
+ end
213
+
123
214
  # Cloud-specific configuration properties.
124
215
  # @param _config [MU::Config]: The calling MU::Config object
125
216
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
@@ -130,11 +221,10 @@ module MU
130
221
  "type" => "array",
131
222
  "items" => {
132
223
  "type" => "object",
133
- "required" => ["endpoint"],
134
224
  "properties" => {
135
225
  "type" => {
136
226
  "type" => "string",
137
- "description" => "",
227
+ "description" => "Type of endpoint or resource which should receive notifications. If not specified, will attempt to auto-detect.",
138
228
  "enum" => ["http", "https", "email", "email-json", "sms", "sqs", "application", "lambda"]
139
229
  }
140
230
  }
@@ -148,26 +238,42 @@ module MU
148
238
  # Cloud-specific pre-processing of {MU::Config::BasketofKittens::notifier}, bare and unvalidated.
149
239
 
150
240
  # @param notifier [Hash]: The resource to process and validate
151
- # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
241
+ # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
152
242
  # @return [Boolean]: True if validation succeeded, False otherwise
153
- def self.validateConfig(notifier, _configurator)
243
+ def self.validateConfig(notifier, configurator)
154
244
  ok = true
155
245
 
156
246
  if notifier['subscriptions']
157
247
  notifier['subscriptions'].each { |sub|
248
+ if sub['resource'] and configurator.haveLitterMate?(sub['resource']['name'], sub['resource']['type'])
249
+ sub['resource']['cloud'] = "AWS"
250
+ MU::Config.addDependency(notifier, sub['resource']['name'], sub['resource']['type'])
251
+ end
158
252
  if !sub["type"]
159
- if sub["endpoint"].match(/^http:/i)
160
- sub["type"] = "http"
161
- elsif sub["endpoint"].match(/^https:/i)
162
- sub["type"] = "https"
163
- elsif sub["endpoint"].match(/^sqs:/i)
164
- sub["type"] = "sqs"
165
- elsif sub["endpoint"].match(/^\+?[\d\-]+$/)
166
- sub["type"] = "sms"
167
- elsif sub["endpoint"].match(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
168
- sub["type"] = "email"
169
- else
170
- MU.log "Notifier #{notifier['name']} subscription #{sub['endpoint']} did not specify a type, and I'm unable to guess one", MU::ERR
253
+ sub['type'] = if sub['resource']
254
+ if sub['resource']['type'] == "functions"
255
+ "lambda"
256
+ elsif sub['resource']['type'] == "msg_queues"
257
+ "sqs"
258
+ end
259
+ elsif sub['endpoint']
260
+ if sub["endpoint"].match(/^http:/i)
261
+ "http"
262
+ elsif sub["endpoint"].match(/^https:/i)
263
+ "https"
264
+ elsif sub["endpoint"].match(/:sqs:/i)
265
+ "sqs"
266
+ elsif sub["endpoint"].match(/:lambda:/i)
267
+ "lambda"
268
+ elsif sub["endpoint"].match(/^\+?[\d\-]+$/)
269
+ "sms"
270
+ elsif sub["endpoint"].match(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
271
+ "email"
272
+ end
273
+ end
274
+
275
+ if !sub['type']
276
+ MU.log "Notifier #{notifier['name']} subscription did not specify a type, and I'm unable to guess one", MU::ERR, details: sub
171
277
  ok = false
172
278
  end
173
279
  end
@@ -178,40 +284,6 @@ module MU
178
284
  end
179
285
 
180
286
 
181
- # Subscribe to a notifier group. This can either be an email address, SQS queue, application endpoint, etc...
182
- # Will create the subscription only if it doesn't already exist.
183
- # @param arn [String]: The cloud provider's identifier of the notifier group.
184
- # @param protocol [String]: The type of the subscription (eg. email,https, etc..).
185
- # @param endpoint [String]: The endpoint of the subscription. This will depend on the 'protocol' (as an example if protocol is email, endpoint will be the email address) ..
186
- # @param region [String]: The cloud provider region.
187
- def self.subscribe(arn: nil, protocol: nil, endpoint: nil, region: MU.curRegion, credentials: nil)
188
- retries = 0
189
- begin
190
- resp = MU::Cloud::AWS.sns(region: region, credentials: credentials).list_subscriptions_by_topic(topic_arn: arn).subscriptions
191
- rescue Aws::SNS::Errors::NotFound
192
- if retries < 5
193
- MU.log "Couldn't find topic #{arn}, retrying several times in case of a lagging resource"
194
- retries += 1
195
- sleep 30
196
- retry
197
- else
198
- raise MuError, "Couldn't find topic #{arn}, giving up"
199
- end
200
- end
201
-
202
- already_subscribed = false
203
- if resp && !resp.empty?
204
- resp.each { |subscription|
205
- already_subscribed = true if subscription.protocol == protocol && subscription.endpoint == endpoint
206
- }
207
- end
208
-
209
- unless already_subscribed
210
- MU::Cloud::AWS.sns(region: region, credentials: credentials).subscribe(topic_arn: arn, protocol: protocol, endpoint: endpoint)
211
- MU.log "Subscribed #{endpoint} to SNS topic #{arn}"
212
- end
213
- end
214
-
215
287
  # Test if a notifier group exists
216
288
  # Create a new notifier group. Will check if the group exists before creating it.
217
289
  # @param topic_name [String]: The cloud provider's name for the notifier group.
@@ -30,7 +30,7 @@ module MU
30
30
  end
31
31
  end
32
32
 
33
- @mu_name ||= @deploy.getResourceName(@config["name"])
33
+ @mu_name ||= @deploy.getResourceName(@config["name"], max_length: 64)
34
34
  end
35
35
 
36
36
  # Called automatically by {MU::Deploy#createResources}
@@ -43,7 +43,7 @@ module MU
43
43
 
44
44
  policy_name = @mu_name+"-"+policy.keys.first.upcase
45
45
  MU.log "Creating IAM policy #{policy_name}"
46
- MU::Cloud::AWS.iam(credentials: @config['credentials']).create_policy(
46
+ MU::Cloud::AWS.iam(credentials: @credentials).create_policy(
47
47
  policy_name: policy_name,
48
48
  path: "/"+@deploy.deploy_id+"/",
49
49
  policy_document: JSON.generate(policy.values.first),
@@ -53,16 +53,18 @@ module MU
53
53
  end
54
54
 
55
55
  if !@config['bare_policies']
56
- MU.log "Creating IAM role #{@mu_name}"
57
56
  @cloud_id = @mu_name
58
57
  path = @config['strip_path'] ? nil : "/"+@deploy.deploy_id+"/"
59
- MU::Cloud::AWS.iam(credentials: @config['credentials']).create_role(
60
- path: path,
61
- role_name: @mu_name,
62
- description: "Generated by Mu",
63
- assume_role_policy_document: gen_assume_role_policy_doc,
64
- tags: get_tag_params
65
- )
58
+ params = {
59
+ :path => path,
60
+ :role_name => @mu_name,
61
+ :description => "Generated by Mu",
62
+ :assume_role_policy_document => gen_assume_role_policy_doc,
63
+ :tags => get_tag_params
64
+ }
65
+
66
+ MU.log "Creating IAM role #{@mu_name} (#{@credentials})", details: params
67
+ MU::Cloud::AWS.iam(credentials: @credentials).create_role(params)
66
68
  end
67
69
  end
68
70
 
@@ -75,7 +77,7 @@ module MU
75
77
  end
76
78
 
77
79
  if !@config['bare_policies']
78
- resp = MU::Cloud::AWS.iam(credentials: @config['credentials']).get_role(
80
+ resp = MU::Cloud::AWS.iam(credentials: @credentials).get_role(
79
81
  role_name: @mu_name
80
82
  ).role
81
83
  ext_tags = resp.tags.map { |t| t.to_h }
@@ -84,7 +86,7 @@ module MU
84
86
 
85
87
  if tag_param.size > 0
86
88
  MU.log "Updating tags on IAM role #{@mu_name}", MU::NOTICE, details: tag_param
87
- MU::Cloud::AWS.iam(credentials: @config['credentials']).tag_role(role_name: @mu_name, tags: tag_param)
89
+ MU::Cloud::AWS.iam(credentials: @credentials).tag_role(role_name: @mu_name, tags: tag_param)
88
90
  end
89
91
  end
90
92
 
@@ -92,13 +94,14 @@ module MU
92
94
  configured_policies = []
93
95
 
94
96
  if @config['raw_policies']
97
+ MU.log "Attaching #{@config['raw_policies'].size.to_s} raw #{@config['raw_policies'].size > 1 ? "policies" : "policy"} to role #{@mu_name}", MU::NOTICE
95
98
  configured_policies = @config['raw_policies'].map { |p|
96
99
  @mu_name+"-"+p.keys.first.upcase
97
100
  }
98
101
  end
99
102
 
100
103
  if @config['attachable_policies']
101
- MU.log "Attaching #{@config['attachable_policies'].size.to_s} #{@config['attachable_policies'].size > 1 ? "policies" : "policy"} to role #{@mu_name}", MU::NOTICE
104
+ MU.log "Attaching #{@config['attachable_policies'].size.to_s} external #{@config['attachable_policies'].size > 1 ? "policies" : "policy"} to role #{@mu_name}", MU::NOTICE
102
105
  configured_policies.concat(@config['attachable_policies'].map { |p|
103
106
  id = if p.is_a?(MU::Config::Ref)
104
107
  p.cloud_id
@@ -109,18 +112,17 @@ module MU
109
112
  end
110
113
  id.gsub(/.*?\/([^:\/]+)$/, '\1')
111
114
  })
112
- configured_policies.each { |pol|
113
- }
114
115
  end
115
116
 
117
+ # Purge anything that doesn't belong
116
118
  if !@config['bare_policies']
117
- attached_policies = MU::Cloud::AWS.iam(credentials: @config['credentials']).list_attached_role_policies(
119
+ attached_policies = MU::Cloud::AWS.iam(credentials: @credentials).list_attached_role_policies(
118
120
  role_name: @mu_name
119
121
  ).attached_policies
120
122
  attached_policies.each { |a|
121
123
  if !configured_policies.include?(a.policy_name)
122
- MU.log "Removing IAM policy #{a.policy_name} from role #{@mu_name}", MU::NOTICE
123
- MU::Cloud::AWS::Role.purgePolicy(a.policy_arn, @config['credentials'])
124
+ MU.log "Removing IAM policy #{a.policy_name} from role #{@mu_name}", MU::NOTICE, details: configured_policies
125
+ MU::Cloud::AWS::Role.purgePolicy(a.policy_arn, @credentials)
124
126
  end
125
127
  }
126
128
  end
@@ -153,8 +155,8 @@ module MU
153
155
  policy.values.each { |p|
154
156
  p["Version"] ||= "2012-10-17"
155
157
  }
156
- policy_name = basename+"-"+policy.keys.first.upcase
157
158
 
159
+ policy_name = basename+"-"+policy.keys.first.upcase
158
160
  arn = "arn:"+(MU::Cloud::AWS.isGovCloud? ? "aws-us-gov" : "aws")+":iam::"+MU::Cloud::AWS.credToAcct(credentials)+":policy#{path}/#{policy_name}"
159
161
  resp = begin
160
162
  desc = MU::Cloud::AWS.iam(credentials: credentials).get_policy(policy_arn: arn)
@@ -184,12 +186,17 @@ module MU
184
186
 
185
187
  rescue Aws::IAM::Errors::NoSuchEntity
186
188
  MU.log "Creating IAM policy #{policy_name}", details: policy.values.first
187
- MU::Cloud::AWS.iam(credentials: credentials).create_policy(
189
+ desc = MU::Cloud::AWS.iam(credentials: credentials).create_policy(
188
190
  policy_name: policy_name,
189
191
  path: path+"/",
190
192
  policy_document: JSON.generate(policy.values.first),
191
193
  description: "Raw policy from #{basename}"
192
194
  )
195
+ MU.retrier([Aws::IAM::Errors::NoSuchEntity], loop_if: Proc.new { desc.nil? }) {
196
+ desc = MU::Cloud::AWS.iam(credentials: credentials).get_policy(policy_arn: arn)
197
+ pp desc
198
+ }
199
+ desc
193
200
  end
194
201
  arns << resp.policy.arn
195
202
  }
@@ -216,7 +223,22 @@ module MU
216
223
  # populated with one or both depending on what this resource has
217
224
  # defined.
218
225
  def cloud_desc(use_cache: true)
219
- return @cloud_desc_cache if @cloud_desc_cache and use_cache
226
+
227
+ # we might inherit a naive cached description from the base cloud
228
+ # layer; rearrange it to our tastes
229
+ if @cloud_desc_cache.is_a?(::Aws::IAM::Types::Role)
230
+ new_desc = {
231
+ "role" => @cloud_desc_cache
232
+ }
233
+ @cloud_desc_cache = new_desc
234
+ elsif @cloud_desc_cache.is_a?(::Aws::IAM::Types::Policy)
235
+ new_desc = {
236
+ "policies" => [@cloud_desc_cache]
237
+ }
238
+ @cloud_desc_cache = new_desc
239
+ end
240
+
241
+ return @cloud_desc_cache if @cloud_desc_cache and !@cloud_desc_cache.empty? and use_cache
220
242
 
221
243
  @cloud_desc_cache = {}
222
244
  if @config['bare_policies']
@@ -301,7 +323,7 @@ end
301
323
  my_policies.each { |p|
302
324
  if p.policy_name == policy
303
325
  seen_policy = true
304
- old = MU::Cloud::AWS.iam(credentials: @config['credentials']).get_policy_version(
326
+ old = MU::Cloud::AWS.iam(credentials: @credentials).get_policy_version(
305
327
  policy_arn: p.arn,
306
328
  version_id: p.default_version_id
307
329
  ).policy_version
@@ -419,14 +441,14 @@ end
419
441
  # @param noop [Boolean]: If true, will only print what would be done
420
442
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
421
443
  # @return [void]
422
- def self.cleanup(noop: false, ignoremaster: false, credentials: nil, flags: {})
444
+ def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, flags: {})
423
445
 
424
446
  resp = MU::Cloud::AWS.iam(credentials: credentials).list_policies(
425
- path_prefix: "/"+MU.deploy_id+"/"
447
+ path_prefix: "/"+deploy_id+"/"
426
448
  )
427
449
  if resp and resp.policies
428
450
  resp.policies.each { |policy|
429
- MU.log "Deleting IAM policy /#{MU.deploy_id}/#{policy.policy_name}"
451
+ MU.log "Deleting IAM policy /#{deploy_id}/#{policy.policy_name}"
430
452
  if !noop
431
453
  purgePolicy(policy.arn, credentials)
432
454
  end
@@ -437,19 +459,23 @@ end
437
459
  roles = MU::Cloud::AWS::Role.find(credentials: credentials).values
438
460
  roles.each { |r|
439
461
  next if !r.respond_to?(:role_name)
440
- if r.path.match(/^\/#{Regexp.quote(MU.deploy_id)}/)
462
+ if r.path.match(/^\/#{Regexp.quote(deploy_id)}/)
441
463
  deleteme << r
442
464
  next
443
465
  end
444
466
  # For some dumb reason, the list output that .find gets doesn't
445
467
  # include the tags, so we need to fetch each role individually to
446
468
  # check tags. Hardly seems efficient.
447
- desc = MU::Cloud::AWS.iam(credentials: credentials).get_role(role_name: r.role_name)
469
+ desc = begin
470
+ MU::Cloud::AWS.iam(credentials: credentials).get_role(role_name: r.role_name)
471
+ rescue Aws::IAM::Errors::NoSuchEntity
472
+ next
473
+ end
448
474
  if desc.role and desc.role.tags and desc.role.tags
449
475
  master_match = false
450
476
  deploy_match = false
451
477
  desc.role.tags.each { |t|
452
- if t.key == "MU-ID" and t.value == MU.deploy_id
478
+ if t.key == "MU-ID" and t.value == deploy_id
453
479
  deploy_match = true
454
480
  elsif t.key == "MU-MASTER-IP" and t.value == MU.mu_public_ip
455
481
  master_match = true
@@ -516,7 +542,7 @@ end
516
542
 
517
543
  begin
518
544
  # managed policies get fetched by ARN, roles by plain name. Ok!
519
- if args[:cloud_id].match(/^arn:/)
545
+ if args[:cloud_id].match(/^arn:.*?:policy\//)
520
546
  resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).get_policy(
521
547
  policy_arn: args[:cloud_id]
522
548
  )
@@ -525,39 +551,26 @@ end
525
551
  end
526
552
  else
527
553
  resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).get_role(
528
- role_name: args[:cloud_id]
554
+ role_name: args[:cloud_id].sub(/^arn:.*?\/([^:\/]+)$/, '\1') # XXX if it's an ARN, actually parse it and look in the correct account when applicable
529
555
  )
556
+
530
557
  if resp and resp.role
531
- found[args[:cloud_id]] = resp.role
558
+ found[resp.role.role_name] = resp.role
532
559
  end
533
560
  end
534
561
  rescue ::Aws::IAM::Errors::NoSuchEntity
535
562
  end
536
563
 
537
564
  else
538
- marker = nil
539
- begin
540
- resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).list_roles(
541
- marker: marker
542
- )
543
- break if !resp or !resp.roles
544
- resp.roles.each { |role|
545
- found[role.role_name] = role
546
- }
547
- marker = resp.marker
548
- end while marker
565
+ resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).list_roles
566
+ resp.roles.each { |role|
567
+ found[role.role_name] = role
568
+ }
549
569
 
550
- begin
551
- resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).list_policies(
552
- scope: "Local",
553
- marker: marker
554
- )
555
- break if !resp or !resp.policies
556
- resp.policies.each { |pol|
557
- found[pol.arn] = pol
558
- }
559
- marker = resp.marker
560
- end while marker
570
+ resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).list_policies(scope: "Local")
571
+ resp.policies.each { |pol|
572
+ found[pol.arn] = pol
573
+ }
561
574
  end
562
575
 
563
576
  found
@@ -569,7 +582,7 @@ end
569
582
  def toKitten(**_args)
570
583
  bok = {
571
584
  "cloud" => "AWS",
572
- "credentials" => @config['credentials'],
585
+ "credentials" => @credentials,
573
586
  "cloud_id" => @cloud_id
574
587
  }
575
588
 
@@ -615,7 +628,6 @@ end
615
628
  )
616
629
  JSON.parse(URI.decode(version.policy_version.document))
617
630
  end
618
-
619
631
  bok["policies"] = MU::Cloud::AWS::Role.doc2MuPolicies(pol.policy_name, doc, bok["policies"])
620
632
  end
621
633
  }
@@ -695,6 +707,7 @@ end
695
707
  end
696
708
 
697
709
  bok["attachable_policies"].uniq! if bok["attachable_policies"]
710
+ bok["name"].gsub!(/[^a-zA-Z0-9_\-]/, "_")
698
711
 
699
712
  bok
700
713
  end
@@ -707,6 +720,10 @@ end
707
720
  def self.doc2MuPolicies(basename, doc, policies = [])
708
721
  policies ||= []
709
722
 
723
+ if !doc["Statement"].is_a?(Array)
724
+ doc["Statement"] = [doc["Statement"]]
725
+ end
726
+
710
727
  doc["Statement"].each { |s|
711
728
  if !s["Action"]
712
729
  MU.log "Statement in policy document for #{basename} didn't have an Action field", MU::WARN, details: doc
@@ -758,12 +775,12 @@ end
758
775
  def bindTo(entitytype, entityname)
759
776
  if entitytype == "instance_profile"
760
777
  begin
761
- resp = MU::Cloud::AWS.iam(credentials: @config['credentials']).get_instance_profile(
778
+ resp = MU::Cloud::AWS.iam(credentials: @credentials).get_instance_profile(
762
779
  instance_profile_name: entityname
763
780
  ).instance_profile
764
781
 
765
782
  if !resp.roles.map { |r| r.role_name}.include?(@mu_name)
766
- MU::Cloud::AWS.iam(credentials: @config['credentials']).add_role_to_instance_profile(
783
+ MU::Cloud::AWS.iam(credentials: @credentials).add_role_to_instance_profile(
767
784
  instance_profile_name: entityname,
768
785
  role_name: @mu_name
769
786
  )
@@ -773,7 +790,7 @@ end
773
790
  raise e
774
791
  end
775
792
  elsif ["user", "group", "role"].include?(entitytype)
776
- mypolicies = MU::Cloud::AWS.iam(credentials: @config['credentials']).list_policies(
793
+ mypolicies = MU::Cloud::AWS.iam(credentials: @credentials).list_policies(
777
794
  path_prefix: "/"+@deploy.deploy_id+"/"
778
795
  ).policies
779
796
  mypolicies.reject! { |p|
@@ -791,7 +808,7 @@ end
791
808
 
792
809
  subpaths = ["service-role", "aws-service-role", "job-function"]
793
810
  begin
794
- mypolicies << MU::Cloud::AWS.iam(credentials: @config['credentials']).get_policy(
811
+ mypolicies << MU::Cloud::AWS.iam(credentials: @credentials).get_policy(
795
812
  policy_arn: p_arn
796
813
  ).policy
797
814
  rescue Aws::IAM::Errors::NoSuchEntity => e
@@ -804,39 +821,52 @@ end
804
821
  }
805
822
  end
806
823
 
824
+ if @config['raw_policies']
825
+ raw_arns = MU::Cloud::AWS::Role.manageRawPolicies(
826
+ @config['raw_policies'],
827
+ basename: @deploy.getResourceName(@config['name']),
828
+ credentials: @credentials
829
+ )
830
+ raw_arns.each { |p_arn|
831
+ mypolicies << MU::Cloud::AWS.iam(credentials: @credentials).get_policy(
832
+ policy_arn: p_arn
833
+ ).policy
834
+ }
835
+ end
836
+
807
837
  mypolicies.each { |p|
808
838
  if entitytype == "user"
809
- resp = MU::Cloud::AWS.iam(credentials: @config['credentials']).list_attached_user_policies(
839
+ resp = MU::Cloud::AWS.iam(credentials: @credentials).list_attached_user_policies(
810
840
  path_prefix: "/"+@deploy.deploy_id+"/",
811
841
  user_name: entityname
812
842
  )
813
843
  if !resp or !resp.attached_policies.map { |a_p| a_p.policy_name }.include?(p.policy_name)
814
844
  MU.log "Attaching IAM policy #{p.policy_name} to user #{entityname}", MU::NOTICE
815
- MU::Cloud::AWS.iam(credentials: @config['credentials']).attach_user_policy(
845
+ MU::Cloud::AWS.iam(credentials: @credentials).attach_user_policy(
816
846
  policy_arn: p.arn,
817
847
  user_name: entityname
818
848
  )
819
849
  end
820
850
  elsif entitytype == "group"
821
- resp = MU::Cloud::AWS.iam(credentials: @config['credentials']).list_attached_group_policies(
851
+ resp = MU::Cloud::AWS.iam(credentials: @credentials).list_attached_group_policies(
822
852
  path_prefix: "/"+@deploy.deploy_id+"/",
823
853
  group_name: entityname
824
854
  )
825
855
  if !resp or !resp.attached_policies.map { |a_p| a_p.policy_name }.include?(p.policy_name)
826
856
  MU.log "Attaching policy #{p.policy_name} to group #{entityname}", MU::NOTICE
827
- MU::Cloud::AWS.iam(credentials: @config['credentials']).attach_group_policy(
857
+ MU::Cloud::AWS.iam(credentials: @credentials).attach_group_policy(
828
858
  policy_arn: p.arn,
829
859
  group_name: entityname
830
860
  )
831
861
  end
832
862
  elsif entitytype == "role"
833
- resp = MU::Cloud::AWS.iam(credentials: @config['credentials']).list_attached_role_policies(
863
+ resp = MU::Cloud::AWS.iam(credentials: @credentials).list_attached_role_policies(
834
864
  role_name: entityname
835
865
  )
836
866
 
837
867
  if !resp or !resp.attached_policies.map { |a_p| a_p.policy_name }.include?(p.policy_name)
838
868
  MU.log "Attaching policy #{p.policy_name} to role #{entityname}", MU::NOTICE
839
- MU::Cloud::AWS.iam(credentials: @config['credentials']).attach_role_policy(
869
+ MU::Cloud::AWS.iam(credentials: @credentials).attach_role_policy(
840
870
  policy_arn: p.arn,
841
871
  role_name: entityname
842
872
  )
@@ -857,19 +887,19 @@ end
857
887
  end
858
888
 
859
889
  resp = begin
860
- MU.log "Creating instance profile #{@mu_name} #{@config['credentials']}"
861
- MU::Cloud::AWS.iam(credentials: @config['credentials']).create_instance_profile(
890
+ MU.log "Creating instance profile #{@mu_name} #{@credentials}"
891
+ MU::Cloud::AWS.iam(credentials: @credentials).create_instance_profile(
862
892
  instance_profile_name: @mu_name
863
893
  )
864
894
  rescue Aws::IAM::Errors::EntityAlreadyExists
865
- MU::Cloud::AWS.iam(credentials: @config['credentials']).get_instance_profile(
895
+ MU::Cloud::AWS.iam(credentials: @credentials).get_instance_profile(
866
896
  instance_profile_name: @mu_name
867
897
  )
868
898
  end
869
899
 
870
900
  # make sure it's really there before moving on
871
901
  begin
872
- MU::Cloud::AWS.iam(credentials: @config['credentials']).get_instance_profile(instance_profile_name: @mu_name)
902
+ MU::Cloud::AWS.iam(credentials: @credentials).get_instance_profile(instance_profile_name: @mu_name)
873
903
  rescue Aws::IAM::Errors::NoSuchEntity => e
874
904
  MU.log e.inspect, MU::WARN
875
905
  sleep 10
@@ -925,7 +955,7 @@ end
925
955
  toplevel_required = []
926
956
  aws_resource_types = MU::Cloud.resource_types.keys.reject { |t|
927
957
  begin
928
- MU::Cloud.loadCloudType("AWS", t)
958
+ MU::Cloud.resourceClass("AWS", t)
929
959
  false
930
960
  rescue MuCloudResourceNotImplemented
931
961
  true
@@ -1087,11 +1117,7 @@ end
1087
1117
  role['policies'].each { |policy|
1088
1118
  policy['targets'].each { |target|
1089
1119
  if target['type']
1090
- role['dependencies'] ||= []
1091
- role['dependencies'] << {
1092
- "name" => target['identifier'],
1093
- "type" => target['type']
1094
- }
1120
+ MU::Config.addDependency(role, target['identifier'], target['type'], no_create_wait: true)
1095
1121
  end
1096
1122
  }
1097
1123
  }
@@ -1107,13 +1133,14 @@ end
1107
1133
  # @param policies [Array<Hash>]: One or more policy chunks
1108
1134
  # @param deploy_obj [MU::MommaCat]: Deployment object to use when looking up sibling Mu resources
1109
1135
  # @return [Array<Hash>]
1110
- def self.genPolicyDocument(policies, deploy_obj: nil, bucket_style: false)
1136
+ def self.genPolicyDocument(policies, deploy_obj: nil, bucket_style: false, version: "2012-10-17", doc_id: nil)
1111
1137
  if policies
1112
1138
  name = nil
1113
1139
  doc = {
1114
- "Version" => "2012-10-17",
1140
+ "Version" => version,
1115
1141
  "Statement" => []
1116
1142
  }
1143
+ doc["Id"] = doc_id if doc_id
1117
1144
  policies.each { |policy|
1118
1145
  policy["flag"] ||= "Allow"
1119
1146
  statement = {
@@ -1154,7 +1181,14 @@ end
1154
1181
  raise MuError, "Couldn't find a #{grantee["type"]} named #{grantee["identifier"]} when generating IAM policy"
1155
1182
  end
1156
1183
  else
1157
- bucket_prefix = grantee["identifier"].match(/^[^\.]+\.amazonaws\.com$/) ? "Service" : "AWS"
1184
+ bucket_prefix = if grantee["identifier"].match(/^[^\.]+\.amazonaws\.com$/)
1185
+ "Service"
1186
+ elsif grantee["identifier"] =~ /^[a-f0-9]+$/
1187
+ "CanonicalUser"
1188
+ else
1189
+ "AWS"
1190
+ end
1191
+
1158
1192
  if bucket_style
1159
1193
  statement["Principal"] << { bucket_prefix => grantee["identifier"] }
1160
1194
  else