cloud-mu 3.5.0 → 3.6.3

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 (245) hide show
  1. checksums.yaml +4 -4
  2. data/Berksfile +5 -2
  3. data/Berksfile.lock +135 -0
  4. data/ansible/roles/mu-base/README.md +33 -0
  5. data/ansible/roles/mu-base/defaults/main.yml +2 -0
  6. data/ansible/roles/mu-base/files/check_apm.cfg +1 -0
  7. data/ansible/roles/mu-base/files/check_apm.sh +18 -0
  8. data/ansible/roles/mu-base/files/check_disk.cfg +1 -0
  9. data/ansible/roles/mu-base/files/check_elastic_shards.cfg +1 -0
  10. data/ansible/roles/mu-base/files/check_elastic_shards.sh +12 -0
  11. data/ansible/roles/mu-base/files/check_logstash.cfg +1 -0
  12. data/ansible/roles/mu-base/files/check_logstash.sh +14 -0
  13. data/ansible/roles/mu-base/files/check_mem.cfg +1 -0
  14. data/ansible/roles/mu-base/files/check_updates.cfg +1 -0
  15. data/ansible/roles/mu-base/files/logrotate.conf +35 -0
  16. data/ansible/roles/mu-base/files/nrpe-apm-sudo +1 -0
  17. data/ansible/roles/mu-base/files/nrpe-elasticshards-sudo +2 -0
  18. data/ansible/roles/mu-base/handlers/main.yml +5 -0
  19. data/ansible/roles/mu-base/meta/main.yml +53 -0
  20. data/ansible/roles/mu-base/tasks/main.yml +113 -0
  21. data/ansible/roles/mu-base/templates/nrpe.cfg.j2 +231 -0
  22. data/ansible/roles/mu-base/tests/inventory +2 -0
  23. data/ansible/roles/mu-base/tests/test.yml +5 -0
  24. data/ansible/roles/mu-base/vars/main.yml +1 -0
  25. data/ansible/roles/mu-compliance/README.md +33 -0
  26. data/ansible/roles/mu-compliance/defaults/main.yml +2 -0
  27. data/ansible/roles/mu-compliance/files/U_MS_Windows_Server_2016_V2R1_STIG_SCAP_1-2_Benchmark.xml +15674 -0
  28. data/ansible/roles/mu-compliance/files/U_MS_Windows_Server_2019_V2R1_STIG_SCAP_1-2_Benchmark.xml +17553 -0
  29. data/ansible/roles/mu-compliance/handlers/main.yml +2 -0
  30. data/ansible/roles/mu-compliance/meta/main.yml +53 -0
  31. data/ansible/roles/mu-compliance/tasks/main.yml +45 -0
  32. data/ansible/roles/mu-compliance/tests/inventory +2 -0
  33. data/ansible/roles/mu-compliance/tests/test.yml +5 -0
  34. data/ansible/roles/mu-compliance/vars/main.yml +4 -0
  35. data/ansible/roles/mu-elastic/README.md +51 -0
  36. data/ansible/roles/mu-elastic/defaults/main.yml +2 -0
  37. data/ansible/roles/mu-elastic/files/jvm.options +93 -0
  38. data/ansible/roles/mu-elastic/handlers/main.yml +10 -0
  39. data/ansible/roles/mu-elastic/meta/main.yml +52 -0
  40. data/ansible/roles/mu-elastic/tasks/main.yml +186 -0
  41. data/ansible/roles/mu-elastic/templates/elasticsearch.yml.j2 +110 -0
  42. data/ansible/roles/mu-elastic/templates/kibana.yml.j2 +131 -0
  43. data/ansible/roles/mu-elastic/templates/password_set.expect.j2 +19 -0
  44. data/ansible/roles/mu-elastic/tests/inventory +2 -0
  45. data/ansible/roles/mu-elastic/tests/test.yml +5 -0
  46. data/ansible/roles/mu-elastic/vars/main.yml +2 -0
  47. data/ansible/roles/mu-logstash/README.md +51 -0
  48. data/ansible/roles/mu-logstash/defaults/main.yml +2 -0
  49. data/ansible/roles/mu-logstash/files/02-beats-input.conf +5 -0
  50. data/ansible/roles/mu-logstash/files/10-rails-filter.conf +16 -0
  51. data/ansible/roles/mu-logstash/files/jvm.options +84 -0
  52. data/ansible/roles/mu-logstash/files/logstash.yml +304 -0
  53. data/ansible/roles/mu-logstash/handlers/main.yml +20 -0
  54. data/ansible/roles/mu-logstash/meta/main.yml +52 -0
  55. data/ansible/roles/mu-logstash/tasks/main.yml +254 -0
  56. data/ansible/roles/mu-logstash/templates/20-cloudtrail.conf.j2 +28 -0
  57. data/ansible/roles/mu-logstash/templates/30-elasticsearch-output.conf.j2 +19 -0
  58. data/ansible/roles/mu-logstash/templates/apm-server.yml.j2 +33 -0
  59. data/ansible/roles/mu-logstash/templates/heartbeat.yml.j2 +29 -0
  60. data/ansible/roles/mu-logstash/templates/nginx/apm.conf.j2 +25 -0
  61. data/ansible/roles/mu-logstash/templates/nginx/default.conf.j2 +56 -0
  62. data/ansible/roles/mu-logstash/templates/nginx/elastic.conf.j2 +27 -0
  63. data/ansible/roles/mu-logstash/tests/inventory +2 -0
  64. data/ansible/roles/mu-logstash/tests/test.yml +5 -0
  65. data/ansible/roles/mu-logstash/vars/main.yml +2 -0
  66. data/ansible/roles/mu-rdp/README.md +33 -0
  67. data/ansible/roles/mu-rdp/meta/main.yml +53 -0
  68. data/ansible/roles/mu-rdp/tasks/main.yml +9 -0
  69. data/ansible/roles/mu-rdp/tests/inventory +2 -0
  70. data/ansible/roles/mu-rdp/tests/test.yml +5 -0
  71. data/ansible/roles/mu-windows/tasks/main.yml +3 -0
  72. data/bin/mu-ansible-secret +1 -1
  73. data/bin/mu-aws-setup +4 -3
  74. data/bin/mu-azure-setup +5 -5
  75. data/bin/mu-configure +25 -17
  76. data/bin/mu-firewall-allow-clients +1 -0
  77. data/bin/mu-gcp-setup +3 -3
  78. data/bin/mu-load-config.rb +1 -0
  79. data/bin/mu-node-manage +66 -33
  80. data/bin/mu-self-update +2 -2
  81. data/bin/mu-upload-chef-artifacts +6 -1
  82. data/bin/mu-user-manage +1 -1
  83. data/cloud-mu.gemspec +25 -23
  84. data/cookbooks/firewall/CHANGELOG.md +417 -224
  85. data/cookbooks/firewall/LICENSE +202 -0
  86. data/cookbooks/firewall/README.md +153 -126
  87. data/cookbooks/firewall/TODO.md +6 -0
  88. data/cookbooks/firewall/attributes/firewalld.rb +7 -0
  89. data/cookbooks/firewall/attributes/iptables.rb +3 -3
  90. data/cookbooks/firewall/chefignore +115 -0
  91. data/cookbooks/firewall/libraries/helpers.rb +5 -0
  92. data/cookbooks/firewall/libraries/helpers_firewalld.rb +1 -1
  93. data/cookbooks/firewall/libraries/helpers_firewalld_dbus.rb +72 -0
  94. data/cookbooks/firewall/libraries/helpers_iptables.rb +3 -3
  95. data/cookbooks/firewall/libraries/helpers_nftables.rb +170 -0
  96. data/cookbooks/firewall/libraries/helpers_ufw.rb +7 -0
  97. data/cookbooks/firewall/libraries/helpers_windows.rb +8 -9
  98. data/cookbooks/firewall/libraries/provider_firewall_firewalld.rb +9 -9
  99. data/cookbooks/firewall/libraries/provider_firewall_iptables.rb +7 -7
  100. data/cookbooks/firewall/libraries/provider_firewall_iptables_ubuntu.rb +12 -8
  101. data/cookbooks/firewall/libraries/provider_firewall_iptables_ubuntu1404.rb +13 -9
  102. data/cookbooks/firewall/libraries/provider_firewall_rule.rb +1 -1
  103. data/cookbooks/firewall/libraries/provider_firewall_ufw.rb +5 -5
  104. data/cookbooks/firewall/libraries/provider_firewall_windows.rb +4 -4
  105. data/cookbooks/firewall/libraries/resource_firewall_rule.rb +3 -3
  106. data/cookbooks/firewall/metadata.json +40 -1
  107. data/cookbooks/firewall/metadata.rb +15 -0
  108. data/cookbooks/firewall/recipes/default.rb +7 -7
  109. data/cookbooks/firewall/recipes/disable_firewall.rb +1 -1
  110. data/cookbooks/firewall/recipes/firewalld.rb +87 -0
  111. data/cookbooks/firewall/renovate.json +18 -0
  112. data/cookbooks/firewall/resources/firewalld.rb +28 -0
  113. data/cookbooks/firewall/resources/firewalld_config.rb +39 -0
  114. data/cookbooks/firewall/resources/firewalld_helpers.rb +106 -0
  115. data/cookbooks/firewall/resources/firewalld_icmptype.rb +88 -0
  116. data/cookbooks/firewall/resources/firewalld_ipset.rb +104 -0
  117. data/cookbooks/firewall/resources/firewalld_policy.rb +115 -0
  118. data/cookbooks/firewall/resources/firewalld_service.rb +98 -0
  119. data/cookbooks/firewall/resources/firewalld_zone.rb +118 -0
  120. data/cookbooks/firewall/resources/nftables.rb +71 -0
  121. data/cookbooks/firewall/resources/nftables_rule.rb +113 -0
  122. data/cookbooks/mu-activedirectory/Berksfile +1 -1
  123. data/cookbooks/mu-activedirectory/metadata.rb +1 -1
  124. data/cookbooks/mu-firewall/metadata.rb +2 -2
  125. data/cookbooks/mu-master/Berksfile +4 -3
  126. data/cookbooks/mu-master/attributes/default.rb +5 -2
  127. data/cookbooks/mu-master/files/default/check_elastic.sh +761 -0
  128. data/cookbooks/mu-master/files/default/check_kibana.rb +45 -0
  129. data/cookbooks/mu-master/libraries/mu.rb +24 -0
  130. data/cookbooks/mu-master/metadata.rb +5 -5
  131. data/cookbooks/mu-master/recipes/default.rb +31 -20
  132. data/cookbooks/mu-master/recipes/firewall-holes.rb +5 -0
  133. data/cookbooks/mu-master/recipes/init.rb +58 -19
  134. data/cookbooks/mu-master/recipes/update_nagios_only.rb +251 -178
  135. data/cookbooks/mu-master/templates/default/nagios.conf.erb +5 -11
  136. data/cookbooks/mu-master/templates/default/web_app.conf.erb +3 -0
  137. data/cookbooks/mu-php54/Berksfile +1 -1
  138. data/cookbooks/mu-php54/metadata.rb +2 -2
  139. data/cookbooks/mu-tools/Berksfile +2 -3
  140. data/cookbooks/mu-tools/attributes/default.rb +3 -4
  141. data/cookbooks/mu-tools/files/amazon/etc/bashrc +90 -0
  142. data/cookbooks/mu-tools/files/amazon/etc/login.defs +292 -0
  143. data/cookbooks/mu-tools/files/amazon/etc/profile +77 -0
  144. data/cookbooks/mu-tools/files/amazon/etc/security/limits.conf +63 -0
  145. data/cookbooks/mu-tools/files/amazon/etc/sysconfig/init +19 -0
  146. data/cookbooks/mu-tools/files/amazon/etc/sysctl.conf +82 -0
  147. data/cookbooks/mu-tools/files/amazon-2023/etc/login.defs +294 -0
  148. data/cookbooks/mu-tools/files/default/logrotate.conf +35 -0
  149. data/cookbooks/mu-tools/files/default/nrpe_conf_d.pp +0 -0
  150. data/cookbooks/mu-tools/libraries/helper.rb +21 -9
  151. data/cookbooks/mu-tools/metadata.rb +4 -4
  152. data/cookbooks/mu-tools/recipes/apply_security.rb +3 -2
  153. data/cookbooks/mu-tools/recipes/aws_api.rb +23 -5
  154. data/cookbooks/mu-tools/recipes/base_repositories.rb +4 -1
  155. data/cookbooks/mu-tools/recipes/gcloud.rb +56 -56
  156. data/cookbooks/mu-tools/recipes/nagios.rb +1 -1
  157. data/cookbooks/mu-tools/recipes/nrpe.rb +20 -2
  158. data/cookbooks/mu-tools/recipes/rsyslog.rb +12 -1
  159. data/cookbooks/mu-tools/recipes/set_local_fw.rb +1 -1
  160. data/data_bags/nagios_services/apm_backend_connect.json +5 -0
  161. data/data_bags/nagios_services/apm_listen.json +5 -0
  162. data/data_bags/nagios_services/elastic_shards.json +5 -0
  163. data/data_bags/nagios_services/logstash.json +5 -0
  164. data/data_bags/nagios_services/rhel7_updates.json +8 -0
  165. data/extras/image-generators/AWS/centos7.yaml +1 -0
  166. data/extras/image-generators/AWS/rhel7.yaml +21 -0
  167. data/extras/image-generators/AWS/win2k12r2.yaml +1 -0
  168. data/extras/image-generators/AWS/win2k16.yaml +1 -0
  169. data/extras/image-generators/AWS/win2k19.yaml +1 -0
  170. data/extras/list-stock-amis +0 -0
  171. data/extras/ruby_rpm/muby.spec +8 -5
  172. data/extras/vault_tools/export_vaults.sh +1 -1
  173. data/extras/vault_tools/recreate_vaults.sh +0 -0
  174. data/extras/vault_tools/test_vaults.sh +0 -0
  175. data/install/deprecated-bash-library.sh +1 -1
  176. data/install/installer +4 -2
  177. data/modules/mommacat.ru +3 -1
  178. data/modules/mu/adoption.rb +1 -1
  179. data/modules/mu/cloud/dnszone.rb +2 -2
  180. data/modules/mu/cloud/machine_images.rb +26 -25
  181. data/modules/mu/cloud/resource_base.rb +213 -182
  182. data/modules/mu/cloud/server_pool.rb +1 -1
  183. data/modules/mu/cloud/ssh_sessions.rb +7 -5
  184. data/modules/mu/cloud/wrappers.rb +2 -2
  185. data/modules/mu/cloud.rb +1 -1
  186. data/modules/mu/config/bucket.rb +1 -1
  187. data/modules/mu/config/function.rb +6 -1
  188. data/modules/mu/config/loadbalancer.rb +24 -2
  189. data/modules/mu/config/ref.rb +12 -0
  190. data/modules/mu/config/role.rb +1 -1
  191. data/modules/mu/config/schema_helpers.rb +42 -9
  192. data/modules/mu/config/server.rb +43 -27
  193. data/modules/mu/config/tail.rb +19 -10
  194. data/modules/mu/config.rb +6 -5
  195. data/modules/mu/defaults/AWS.yaml +78 -114
  196. data/modules/mu/deploy.rb +9 -2
  197. data/modules/mu/groomer.rb +12 -4
  198. data/modules/mu/groomers/ansible.rb +104 -20
  199. data/modules/mu/groomers/chef.rb +15 -6
  200. data/modules/mu/master.rb +9 -4
  201. data/modules/mu/mommacat/daemon.rb +4 -2
  202. data/modules/mu/mommacat/naming.rb +1 -2
  203. data/modules/mu/mommacat/storage.rb +7 -2
  204. data/modules/mu/mommacat.rb +33 -6
  205. data/modules/mu/providers/aws/database.rb +161 -8
  206. data/modules/mu/providers/aws/dnszone.rb +11 -6
  207. data/modules/mu/providers/aws/endpoint.rb +81 -6
  208. data/modules/mu/providers/aws/firewall_rule.rb +254 -172
  209. data/modules/mu/providers/aws/function.rb +65 -3
  210. data/modules/mu/providers/aws/loadbalancer.rb +39 -28
  211. data/modules/mu/providers/aws/log.rb +2 -1
  212. data/modules/mu/providers/aws/role.rb +25 -7
  213. data/modules/mu/providers/aws/server.rb +36 -12
  214. data/modules/mu/providers/aws/server_pool.rb +237 -127
  215. data/modules/mu/providers/aws/storage_pool.rb +7 -1
  216. data/modules/mu/providers/aws/user.rb +1 -1
  217. data/modules/mu/providers/aws/userdata/linux.erb +6 -2
  218. data/modules/mu/providers/aws/userdata/windows.erb +7 -5
  219. data/modules/mu/providers/aws/vpc.rb +49 -25
  220. data/modules/mu/providers/aws.rb +13 -8
  221. data/modules/mu/providers/azure/container_cluster.rb +1 -1
  222. data/modules/mu/providers/azure/loadbalancer.rb +2 -2
  223. data/modules/mu/providers/azure/server.rb +5 -2
  224. data/modules/mu/providers/azure/userdata/linux.erb +1 -1
  225. data/modules/mu/providers/azure.rb +11 -8
  226. data/modules/mu/providers/cloudformation/dnszone.rb +1 -1
  227. data/modules/mu/providers/google/container_cluster.rb +15 -2
  228. data/modules/mu/providers/google/folder.rb +2 -1
  229. data/modules/mu/providers/google/function.rb +130 -4
  230. data/modules/mu/providers/google/habitat.rb +2 -1
  231. data/modules/mu/providers/google/loadbalancer.rb +407 -160
  232. data/modules/mu/providers/google/role.rb +16 -3
  233. data/modules/mu/providers/google/server.rb +5 -1
  234. data/modules/mu/providers/google/user.rb +25 -18
  235. data/modules/mu/providers/google/userdata/linux.erb +1 -1
  236. data/modules/mu/providers/google/vpc.rb +53 -7
  237. data/modules/mu/providers/google.rb +39 -39
  238. data/modules/mu.rb +8 -8
  239. data/modules/tests/elk.yaml +46 -0
  240. data/test/mu-master-test/controls/all_in_one.rb +1 -1
  241. metadata +207 -112
  242. data/cookbooks/firewall/CONTRIBUTING.md +0 -2
  243. data/cookbooks/firewall/MAINTAINERS.md +0 -19
  244. data/cookbooks/firewall/libraries/matchers.rb +0 -30
  245. data/extras/image-generators/AWS/rhel71.yaml +0 -17
@@ -107,6 +107,7 @@ module MU
107
107
  # Called by {MU::Deploy#createResources}
108
108
  def groom
109
109
  if !@config['rules'].nil? and @config['rules'].size > 0
110
+
110
111
  egress = false
111
112
  egress = true if !@vpc.nil?
112
113
  # XXX the egress logic here is a crude hack, this really needs to be
@@ -172,11 +173,13 @@ module MU
172
173
 
173
174
  begin
174
175
  if egress
176
+ MU.log ".authorize_security_group_egress 2", MU::NOTICE, details: ec2_rule
175
177
  MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).authorize_security_group_egress(
176
178
  group_id: @cloud_id,
177
179
  ip_permissions: ec2_rule
178
180
  )
179
181
  else
182
+ MU.log ".authorize_security_group_ingress 2", MU::NOTICE, details: ec2_rule
180
183
  MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).authorize_security_group_ingress(
181
184
  group_id: @cloud_id,
182
185
  ip_permissions: ec2_rule
@@ -188,11 +191,13 @@ module MU
188
191
  # existing rules
189
192
  if comment
190
193
  if egress
194
+ MU.log ".update_security_group_rule_descriptions_engress", MU::NOTICE, details: ec2_rule
191
195
  MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).update_security_group_rule_descriptions_egress(
192
196
  group_id: @cloud_id,
193
197
  ip_permissions: ec2_rule
194
198
  )
195
199
  else
200
+ MU.log ".update_security_group_rule_descriptions_ingress", MU::NOTICE, details: ec2_rule
196
201
  MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).update_security_group_rule_descriptions_ingress(
197
202
  group_id: @cloud_id,
198
203
  ip_permissions: ec2_rule
@@ -702,15 +707,14 @@ module MU
702
707
 
703
708
  private
704
709
 
705
- def purge_extraneous_rules(ec2_rules, ext_permissions)
710
+ def purge_extraneous_rules(ec2_rules, ext_permissions, egress: false)
706
711
  # Purge any old rules that we're sure we created (check the comment)
707
- # but which are no longer configured.
712
+ # but which are no longer configured. Also purge rules that overlap
713
+ # with new rules.
708
714
  ext_permissions.each { |ext_rule|
709
715
  haverule = false
710
716
  ec2_rules.each { |rule|
711
- if rule[:from_port] == ext_rule[:from_port] and
712
- rule[:to_port] == ext_rule[:to_port] and
713
- rule[:ip_protocol] == ext_rule[:ip_protocol]
717
+ if rules_match(rule, ext_rule)
714
718
  haverule = true
715
719
  break
716
720
  end
@@ -719,7 +723,7 @@ module MU
719
723
 
720
724
  mu_comments = false
721
725
  (ext_rule[:user_id_group_pairs] + ext_rule[:ip_ranges]).each { |entry|
722
- if entry[:description] == "Added by Mu"
726
+ if entry[:description] and !entry[:description].empty?
723
727
  mu_comments = true
724
728
  else
725
729
  mu_comments = false
@@ -733,22 +737,60 @@ module MU
733
737
  ext_rule.delete(k)
734
738
  end
735
739
  }
736
- MU.log "Removing unconfigured rule in #{@mu_name}", MU::WARN, details: ext_rule
737
- MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).revoke_security_group_ingress(
738
- group_id: @cloud_id,
739
- ip_permissions: [ext_rule]
740
- )
740
+ MU.log "Removing changed #{egress ? "egress" : "ingress"} rule in #{@mu_name}", MU::WARN, details: ext_rule
741
+ begin
742
+ if !egress
743
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).revoke_security_group_ingress(
744
+ group_id: @cloud_id,
745
+ ip_permissions: [ext_rule]
746
+ )
747
+ else
748
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).revoke_security_group_egress(
749
+ group_id: @cloud_id,
750
+ ip_permissions: [ext_rule]
751
+ )
752
+ end
753
+ rescue Aws::EC2::Errors::InvalidPermissionNotFound
754
+ end
741
755
  end
742
756
  }
743
757
  end
744
758
 
745
- #########################################################################
759
+ def rules_match(rule_a, rule_b)
760
+ (
761
+ rule_a[:from_port] == rule_b[:from_port] and
762
+ rule_a[:to_port] == rule_b[:to_port] and
763
+ rule_a[:ip_protocol] == rule_b[:ip_protocol] and
764
+ (
765
+ (
766
+ (rule_a[:ip_ranges].nil? or rule_a[:ip_ranges].empty?) and
767
+ (rule_b[:ip_ranges].nil? or rule_b[:ip_ranges].empty?)
768
+ ) or
769
+ (
770
+ !rule_a[:ip_ranges].nil? and !rule_b[:ip_ranges].nil? and
771
+ rule_a[:ip_ranges].sort == rule_b[:ip_ranges].sort
772
+ )
773
+ ) and
774
+ (
775
+ (
776
+ (rule_a[:user_id_group_pairs].nil? or rule_a[:user_id_group_pairs].empty?) and
777
+ (rule_b[:user_id_group_pairs].nil? or rule_b[:user_id_group_pairs].empty?)
778
+ ) or
779
+ (
780
+ !rule_a[:user_id_group_pairs].nil? and !rule_b[:user_id_group_pairs].nil? and
781
+ rule_a[:user_id_group_pairs].sort == rule_b[:user_id_group_pairs].sort
782
+ )
783
+ )
784
+ )
785
+ end
786
+
787
+ ########################################################################
746
788
  # Manufacture an EC2 security group. The second parameter, rules, is an
747
789
  # "ingress_rules" structure parsed and validated by MU::Config.
748
- #########################################################################
790
+ ########################################################################
749
791
  def setRules(rules, add_to_self: false, ingress: true, egress: false)
750
792
  # XXX warn about attempt to set rules before we exist
751
- return if rules.nil? or rules.size == 0 or !@cloud_id
793
+ return if rules.nil? or rules.empty? or !@cloud_id
752
794
 
753
795
  # add_to_self means that this security is a "member" of its own rules
754
796
  # (which is to say, objects that have this SG are allowed in my these
@@ -766,73 +808,94 @@ module MU
766
808
 
767
809
  ec2_rules = convertToEc2(rules)
768
810
  return if ec2_rules.nil?
811
+ ec2_rules.uniq!
769
812
 
770
- ext_permissions = MU.structToHash(cloud_desc(use_cache: false).ip_permissions)
813
+ fresh_desc = cloud_desc(use_cache: false)
814
+ directions = []
815
+ directions << :ingress if ingress
816
+ directions << :egress if egress
771
817
 
772
- purge_extraneous_rules(ec2_rules, ext_permissions)
818
+ directions.each { |dir|
819
+ ext_permissions = MU.structToHash(dir == :ingress ? fresh_desc.ip_permissions : fresh_desc.ip_permissions_egress)
820
+ purge_extraneous_rules(ec2_rules, ext_permissions, egress: (dir == :egress))
773
821
 
774
- ec2_rules.uniq!
775
- ec2_rules.each { |rule|
776
- haverule = nil
777
- different = false
778
- ext_permissions.each { |ext_rule|
779
- if rule[:from_port] == ext_rule[:from_port] and
780
- rule[:to_port] == ext_rule[:to_port] and
781
- rule[:ip_protocol] == ext_rule[:ip_protocol]
782
- haverule = ext_rule
783
- ext_rule.keys.each { |k|
784
- if ext_rule[k].nil? or ext_rule[k] == []
785
- haverule.delete(k)
786
- end
787
- different = true if rule[k] != ext_rule[k]
788
- }
789
- break
822
+ ec2_rules.each { |rule|
823
+ haverule = nil
824
+ different = false
825
+ ext_permissions.each { |ext_rule|
826
+ if rules_match(rule, ext_rule)
827
+ haverule = ext_rule
828
+ ext_rule.keys.each { |k|
829
+ if ext_rule[k].nil? or ext_rule[k] == []
830
+ haverule.delete(k)
831
+ end
832
+ different = true if rule[k] != ext_rule[k]
833
+ }
834
+ break
835
+ end
836
+ }
837
+ if haverule and !different
838
+ MU.log "Security Group rule already up-to-date in #{@mu_name}", MU::DEBUG, details: rule
839
+ next
790
840
  end
791
- }
792
- if haverule and !different
793
- MU.log "Security Group rule already up-to-date in #{@mu_name}", MU::DEBUG, details: rule
794
- next
795
- end
796
841
 
797
- MU.log "Setting #{ingress ? "ingress" : "egress"} rule in Security Group #{@mu_name} (#{@cloud_id})", MU::NOTICE, details: rule
842
+ MU.log "Setting #{dir.to_s} rule in Security Group #{@mu_name} (#{@cloud_id})", MU::NOTICE, details: rule
798
843
 
799
- MU.retrier([Aws::EC2::Errors::InvalidGroupNotFound], max: 10, wait: 10, ignoreme: [Aws::EC2::Errors::InvalidPermissionDuplicate]) {
800
- if ingress
844
+ MU.retrier([Aws::EC2::Errors::InvalidGroupNotFound], max: 10, wait: 10) {
801
845
  if haverule
802
846
  begin
803
- MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).revoke_security_group_ingress(
804
- group_id: @cloud_id,
805
- ip_permissions: [haverule]
806
- )
847
+ params = {
848
+ :group_id => @cloud_id,
849
+ :ip_permissions => [haverule]
850
+ }
851
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).send("revoke_security_group_#{dir.to_s}".to_sym, params)
807
852
  rescue Aws::EC2::Errors::InvalidPermissionNotFound
808
853
  end
809
854
  end
855
+ remove_me = nil
856
+ removals = 0
810
857
  begin
811
- MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).authorize_security_group_ingress(
812
- group_id: @cloud_id,
813
- ip_permissions: [rule]
814
- )
858
+ if remove_me
859
+ removals += 1
860
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).send("revoke_security_group_#{dir.to_s}".to_sym, remove_me)
861
+ end
862
+ params = {
863
+ :group_id => @cloud_id,
864
+ :ip_permissions => [rule]
865
+ }
866
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).send("authorize_security_group_#{dir.to_s}".to_sym, params)
867
+ rescue Aws::EC2::Errors::InvalidPermissionDuplicate => e
868
+
869
+ if e.message =~ /"peer: (\d+\.\d+\.\d+\.\d+\/\d+), (TCP|UDP|ICMP|), from port: (\d+), to port: (\d+), ALLOW"/ and removals < 50
870
+ MU.log "FirewallRule #{@mu_name} attempted to add a duplicate rule: #{e.message}, will attempt to remove", MU::NOTICE
871
+ remove_me = {
872
+ :group_id => @cloud_id,
873
+ :ip_permissions => [
874
+ {
875
+ :from_port => Regexp.last_match[3].to_i,
876
+ :to_port => Regexp.last_match[3].to_i,
877
+ :ip_protocol => Regexp.last_match[2].downcase,
878
+ :ip_ranges => [
879
+ :cidr_ip => Regexp.last_match[1]
880
+ ]
881
+ }
882
+ ]
883
+ }
884
+ retry
885
+ else
886
+ MU.log "FirewallRule #{@mu_name} attempted to add a duplicate rule: #{e.message}", MU::WARN, details: [cloud_desc, params]
887
+ end
888
+ rescue Aws::EC2::Errors::InvalidParameterValue => e
889
+ if e.message =~ /The same permission must not appear multiple times/
890
+ MU.log "FirewallRule #{@mu_name} attempted to add a duplicate rule: #{e.message}", MU::WARN, details: [cloud_desc, params]
891
+ else
892
+ raise e
893
+ end
815
894
  rescue Aws::EC2::Errors::InvalidParameterCombination => e
816
895
  MU.log "FirewallRule #{@mu_name} had a bogus rule: #{e.message}", MU::ERR, details: rule
817
896
  raise e
818
897
  end
819
- end
820
-
821
- if egress
822
- if haverule
823
- begin
824
- MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).revoke_security_group_egress(
825
- group_id: @cloud_id,
826
- ip_permissions: [haverule]
827
- )
828
- rescue Aws::EC2::Errors::InvalidPermissionNotFound
829
- end
830
- end
831
- MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).authorize_security_group_egress(
832
- group_id: @cloud_id,
833
- ip_permissions: [rule]
834
- )
835
- end
898
+ }
836
899
  }
837
900
  }
838
901
 
@@ -843,131 +906,150 @@ module MU
843
906
  # Amazon's. Our rule structure is as defined in MU::Config.
844
907
  #######################################################################
845
908
  def convertToEc2(rules)
909
+ return [] if rules.nil?
846
910
  ec2_rules = []
847
- if rules != nil
848
- rules.uniq!
849
911
 
850
- rules.each { |rule|
851
- ec2_rule = {}
852
- rule["comment"] ||= "Added by Mu"
853
-
854
-
855
- rule['proto'] ||= "tcp"
856
- ec2_rule[:ip_protocol] = rule['proto']
857
-
858
- p_start = nil
859
- p_end = nil
860
- if rule['port_range']
861
- p_start, p_end = rule['port_range'].to_s.split(/\s*-\s*/)
862
- elsif rule['port']
863
- p_start = rule['port'].to_i
864
- p_end = rule['port'].to_i
865
- elsif rule['proto'] != "icmp"
866
- MU.log "Can't create a TCP or UDP security group rule without specifying ports, assuming 'all'", MU::WARN, details: rule
867
- p_start = "0"
868
- p_end = "65535"
869
- end
912
+ rules.uniq!
870
913
 
871
- if rule['proto'] != "icmp"
872
- if p_start.nil? or p_end.nil?
873
- raise MuError, "Got nil ports out of rule #{rule}"
874
- end
875
- ec2_rule[:from_port] = p_start.to_i
876
- ec2_rule[:to_port] = p_end.to_i
877
- else
878
- ec2_rule[:from_port] = -1
879
- ec2_rule[:to_port] = -1
880
- end
914
+ rules.each { |rule|
915
+ ec2_rule = {}
916
+ rule["comment"] ||= "Added by Mu"
881
917
 
882
- if (!defined? rule['hosts'] or !rule['hosts'].is_a?(Array)) and
883
- (!defined? rule['firewall_rules'] or !rule['firewall_rules'].is_a?(Array)) and
884
- (!defined? rule['loadbalancers'] or !rule['loadbalancers'].is_a?(Array))
885
- rule['hosts'] = ["0.0.0.0/0"]
886
- end
887
- ec2_rule[:ip_ranges] = []
888
- ec2_rule[:user_id_group_pairs] = []
889
-
890
- if !rule['hosts'].nil?
891
- rule['hosts'].uniq!
892
- rule['hosts'].each { |cidr|
893
- next if cidr.nil? # XXX where is that coming from?
894
- cidr = cidr + "/32" if cidr.match(/^\d+\.\d+\.\d+\.\d+$/)
895
- ec2_rule[:ip_ranges] << {cidr_ip: cidr, description: rule['comment']}
896
- }
897
- end
898
918
 
899
- if !rule['loadbalancers'].nil?
900
- rule['loadbalancers'].uniq!
901
- rule['loadbalancers'].each { |lb|
902
- lb_ref = MU::Config::Ref.get(lb)
919
+ rule['proto'] ||= "tcp"
920
+ ec2_rule[:ip_protocol] = rule['proto']
903
921
 
904
- if !lb_ref.kitten or !lb_ref.kitten.cloud_desc
905
- MU.log "Security Group #{@mu_name} failed to get cloud descriptor for referenced load balancer", MU::ERR, details: lb_ref
906
- next
907
- end
922
+ p_start = nil
923
+ p_end = nil
924
+ if rule['port_range']
925
+ p_start, p_end = rule['port_range'].to_s.split(/\s*-\s*/)
926
+ elsif rule['port']
927
+ p_start = rule['port'].to_i
928
+ p_end = rule['port'].to_i
929
+ elsif rule['proto'] != "icmp"
930
+ MU.log "Can't create a TCP or UDP security group rule without specifying ports, assuming 'all'", MU::WARN, details: rule
931
+ p_start = "0"
932
+ p_end = "65535"
933
+ end
908
934
 
909
- lb_ref.kitten.cloud_desc.security_groups.each { |lb_sg|
910
- # XXX this probably has to infer things like region,
911
- # credentials, etc from the load balancer ref
912
- lb_sg_desc = MU::Cloud::AWS::FirewallRule.find(cloud_id: lb_sg)
913
- owner_id = if lb_sg_desc and lb_sg_desc.size == 1
914
- lb_sg_desc.values.first.owner_id
915
- else
916
- MU::Cloud::AWS.credToAcct(@credentials)
917
- end
918
- ec2_rule[:user_id_group_pairs] << {
919
- user_id: owner_id,
920
- group_id: lb_sg,
921
- description: rule['comment']
922
- }
923
- }
924
- }
935
+ if rule['proto'] != "icmp"
936
+ if p_start.nil? or p_end.nil?
937
+ raise MuError, "Got nil ports out of rule #{rule}"
925
938
  end
939
+ ec2_rule[:from_port] = p_start.to_i
940
+ ec2_rule[:to_port] = p_end.to_i
941
+ else
942
+ ec2_rule[:from_port] = -1
943
+ ec2_rule[:to_port] = -1
944
+ end
926
945
 
927
- if !rule['firewall_rules'].nil?
928
- rule['firewall_rules'].uniq!
929
- rule['firewall_rules'].each { |sg|
930
- sg_ref = MU::Config::Ref.get(sg)
946
+ if (!defined? rule['hosts'] or !rule['hosts'].is_a?(Array)) and
947
+ (!defined? rule['firewall_rules'] or !rule['firewall_rules'].is_a?(Array)) and
948
+ (!defined? rule['loadbalancers'] or !rule['loadbalancers'].is_a?(Array))
949
+ rule['hosts'] = ["0.0.0.0/0"]
950
+ end
951
+ ec2_rule[:ip_ranges] = []
952
+ ec2_rule[:user_id_group_pairs] = []
953
+
954
+ if !rule['hosts'].nil?
955
+ rule['hosts'].uniq!
956
+ rule['hosts'].each { |cidr|
957
+ next if cidr.nil? # XXX where is that coming from?
958
+ cidr = cidr + "/32" if cidr.match(/^\d+\.\d+\.\d+\.\d+$/)
959
+ ec2_rule[:ip_ranges] << {cidr_ip: cidr, description: rule['comment']}
960
+ }
961
+ end
931
962
 
932
- if !sg_ref.kitten or !sg_ref.kitten.cloud_desc
933
- MU.log "Security Group #{@mu_name} failed to get cloud descriptor for referenced Security Group", MU::ERR, details: sg_ref
934
- next
935
- end
963
+ if !rule['loadbalancers'].nil?
964
+ rule['loadbalancers'].uniq!
965
+ rule['loadbalancers'].each { |lb|
966
+ lb_ref = MU::Config::Ref.get(lb)
936
967
 
968
+ if !lb_ref.kitten or !lb_ref.kitten.cloud_desc
969
+ MU.log "Security Group #{@mu_name} failed to get cloud descriptor for referenced load balancer", MU::ERR, details: lb_ref
970
+ next
971
+ end
972
+
973
+ lb_ref.kitten.cloud_desc.security_groups.each { |lb_sg|
974
+ # XXX this probably has to infer things like region,
975
+ # credentials, etc from the load balancer ref
976
+ lb_sg_desc = MU::Cloud::AWS::FirewallRule.find(cloud_id: lb_sg)
977
+ owner_id = if lb_sg_desc and lb_sg_desc.size == 1
978
+ lb_sg_desc.values.first.owner_id
979
+ else
980
+ MU::Cloud::AWS.credToAcct(@credentials)
981
+ end
937
982
  ec2_rule[:user_id_group_pairs] << {
938
- user_id: sg_ref.kitten.cloud_desc.owner_id,
939
- group_id: sg_ref.cloud_id,
983
+ user_id: owner_id,
984
+ group_id: lb_sg,
940
985
  description: rule['comment']
941
986
  }
987
+ }
988
+ }
989
+ end
990
+
991
+ if !rule['firewall_rules'].nil?
992
+ rule['firewall_rules'].uniq!
993
+ rule['firewall_rules'].each { |sg|
994
+ sg_ref = MU::Config::Ref.get(sg)
995
+
996
+ if !sg_ref.kitten or !sg_ref.kitten.cloud_desc
997
+ MU.log "Security Group #{@mu_name} failed to get cloud descriptor for referenced Security Group", MU::ERR, details: sg_ref
998
+ next
999
+ end
942
1000
 
1001
+ ec2_rule[:user_id_group_pairs] << {
1002
+ user_id: sg_ref.kitten.cloud_desc.owner_id,
1003
+ group_id: sg_ref.cloud_id,
1004
+ description: rule['comment']
943
1005
  }
944
- end
945
1006
 
946
- ec2_rule[:user_id_group_pairs].uniq!
947
- ec2_rule[:ip_ranges].uniq!
948
- ec2_rule.delete(:ip_ranges) if ec2_rule[:ip_ranges].empty?
949
- ec2_rule.delete(:user_id_group_pairs) if ec2_rule[:user_id_group_pairs].empty?
950
-
951
- # if !ec2_rule[:user_id_group_pairs].nil? and
952
- # ec2_rule[:user_id_group_pairs].size > 0 and
953
- # !ec2_rule[:ip_ranges].nil? and
954
- # ec2_rule[:ip_ranges].size > 0
955
- # MU.log "Cannot specify ip_ranges and user_id_group_pairs", MU::ERR
956
- # raise MuError, "Cannot specify ip_ranges and user_id_group_pairs"
957
- # end
958
-
959
- # if !ec2_rule[:user_id_group_pairs].nil? and
960
- # ec2_rule[:user_id_group_pairs].size > 0
961
- # ec2_rule.delete(:ip_ranges)
962
- # ec2_rule[:user_id_group_pairs].uniq!
963
- # elsif !ec2_rule[:ip_ranges].nil? and
964
- # ec2_rule[:ip_ranges].size > 0
965
- # ec2_rule.delete(:user_id_group_pairs)
966
- # ec2_rule[:ip_ranges].uniq!
967
- # end
968
- ec2_rules << ec2_rule
1007
+ }
1008
+ end
1009
+
1010
+ ec2_rule[:user_id_group_pairs].uniq!
1011
+ ec2_rule[:ip_ranges].uniq!
1012
+ ec2_rule.delete(:ip_ranges) if ec2_rule[:ip_ranges].empty?
1013
+ ec2_rule.delete(:user_id_group_pairs) if ec2_rule[:user_id_group_pairs].empty?
1014
+
1015
+ # if !ec2_rule[:user_id_group_pairs].nil? and
1016
+ # ec2_rule[:user_id_group_pairs].size > 0 and
1017
+ # !ec2_rule[:ip_ranges].nil? and
1018
+ # ec2_rule[:ip_ranges].size > 0
1019
+ # MU.log "Cannot specify ip_ranges and user_id_group_pairs", MU::ERR
1020
+ # raise MuError, "Cannot specify ip_ranges and user_id_group_pairs"
1021
+ # end
1022
+
1023
+ # if !ec2_rule[:user_id_group_pairs].nil? and
1024
+ # ec2_rule[:user_id_group_pairs].size > 0
1025
+ # ec2_rule.delete(:ip_ranges)
1026
+ # ec2_rule[:user_id_group_pairs].uniq!
1027
+ # elsif !ec2_rule[:ip_ranges].nil? and
1028
+ # ec2_rule[:ip_ranges].size > 0
1029
+ # ec2_rule.delete(:user_id_group_pairs)
1030
+ # ec2_rule[:ip_ranges].uniq!
1031
+ # end
1032
+ ec2_rules << ec2_rule
1033
+ }
1034
+
1035
+ # condense rules by protocol/port the way EC2 would read it back
1036
+ grouped = {}
1037
+ ec2_rules.each { |r|
1038
+ key = r.dup
1039
+ [:ip_ranges, :user_id_group_pairs].each { |t|
1040
+ key.delete(t)
969
1041
  }
970
- end
1042
+ grouped[key] ||= {}
1043
+ [:ip_ranges, :user_id_group_pairs].each { |t|
1044
+ next if !r[t]
1045
+ grouped[key][t] ||= []
1046
+ grouped[key][t].concat(r[t])
1047
+ grouped[key][t].sort.uniq!
1048
+ }
1049
+ }
1050
+ ec2_rules = grouped.keys.map { |k|
1051
+ k.merge(grouped[k])
1052
+ }
971
1053
 
972
1054
  ec2_rules.uniq
973
1055
  end
@@ -85,6 +85,7 @@ module MU
85
85
  changes[k] = v if v != old_props[k]
86
86
  }
87
87
  if !changes.empty?
88
+ wait_for_active
88
89
  MU.log "Updating Lambda #{@mu_name}", MU::NOTICE, details: changes
89
90
  MU::Cloud::AWS.lambda(region: @region, credentials: @credentials).update_function_configuration(new_props)
90
91
  end
@@ -129,6 +130,25 @@ module MU
129
130
 
130
131
  end
131
132
 
133
+ # If we have a loadbalancer configured, attach us to it
134
+ if !@config['loadbalancers'].nil?
135
+ if @loadbalancers.nil?
136
+ raise MuError, "#{@mu_name} is configured to use LoadBalancers, but none have been loaded by dependencies()"
137
+ end
138
+
139
+ @loadbalancers.each { |lb|
140
+ if !lb.targetgroups
141
+ MU.retrier([], max: 6, wait: 15, loop_if: Proc.new { !lb.targetgroups }) {
142
+ lb.cloud_desc(use_cache: false)
143
+ }
144
+ end
145
+ lb.targetgroups.each_pair { |tg_name, tg|
146
+ addTrigger(tg.target_group_arn, "elasticloadbalancing", tg_name)
147
+ }
148
+ lb.registerTarget(arn)
149
+ }
150
+ end
151
+
132
152
  if @config['invoke_on_completion']
133
153
  invoke_params = {
134
154
  function_name: @cloud_id,
@@ -158,9 +178,34 @@ module MU
158
178
  statement_id: "#{calling_service}-#{calling_name.gsub(/[^a-z0-9\-_]/i, '_')}",
159
179
  }
160
180
 
181
+ # Just return if we already have this condition
182
+ begin
183
+ pol = MU::Cloud::AWS.lambda(region: @region, credentials: @credentials).get_policy(function_name: @cloud_id).policy
184
+ if pol
185
+ pol = JSON.parse(pol)
186
+ pol["Statement"].each { |s|
187
+ if !s["Condition"] or !s["Condition"]["ArnLike"] or
188
+ !s["Condition"]["ArnLike"]["AWS:SourceArn"] or !s["Effect"] or
189
+ !s["Principal"] or !s["Action"]
190
+ next
191
+ end
192
+ if s["Effect"] == "Allow" and
193
+ s["Sid"] == trigger[:statement_id] and
194
+ s["Principal"] == { "Service" => trigger[:principal] } and
195
+ s["Action"] == trigger[:action] and
196
+ s["Condition"]["ArnLike"]["AWS:SourceArn"] == trigger[:source_arn]
197
+ return
198
+
199
+ end
200
+ }
201
+ end
202
+ rescue Aws::Lambda::Errors::ResourceNotFoundException
203
+ end
204
+
161
205
  begin
162
206
  # XXX There doesn't seem to be an API call to list or view existing
163
207
  # permissions, wtaf. This means we can't intelligently guard this.
208
+ MU.log "Adding permission for Lambda function #{@cloud_id}", MU::NOTICE, details: trigger
164
209
  MU::Cloud::AWS.lambda(region: @region, credentials: @credentials).add_permission(trigger)
165
210
  rescue Aws::Lambda::Errors::ValidationException => e
166
211
  MU.log e.message+" (calling_arn: #{calling_arn}, calling_service: #{calling_service}, calling_name: #{calling_name})", MU::ERR, details: trigger
@@ -416,7 +461,6 @@ module MU
416
461
 
417
462
  begin
418
463
  pol = MU::Cloud::AWS.lambda(region: @region, credentials: @credentials).get_policy(function_name: @cloud_id).policy
419
- MU.log @cloud_id, MU::WARN, details: JSON.parse(pol) if @cloud_id == "ESPIER-DEV-2020080900-LN-ON-DEMAND-SCANNER"
420
464
  if pol
421
465
  bok['triggers'] ||= []
422
466
  JSON.parse(pol)["Statement"].each { |s|
@@ -619,7 +663,16 @@ MU.log @cloud_id, MU::WARN, details: JSON.parse(pol) if @cloud_id == "ESPIER-DEV
619
663
  end
620
664
 
621
665
  if function['role']['name']
622
- MU::Config.addDependency(function, function['role']['name'], "role")
666
+ MU::Config.addDependency(function, function['role']['name'], "role", my_phase: "groom", their_phase: "groom")
667
+ end
668
+
669
+ if !function["loadbalancers"].nil?
670
+ function["loadbalancers"].each { |lb|
671
+ lb["name"] ||= lb["concurrent_load_balancer"]
672
+ if lb["name"]
673
+ MU::Config.addDependency(function, lb["name"], "loadbalancer")
674
+ end
675
+ }
623
676
  end
624
677
 
625
678
  ok
@@ -627,6 +680,15 @@ MU.log @cloud_id, MU::WARN, details: JSON.parse(pol) if @cloud_id == "ESPIER-DEV
627
680
 
628
681
  private
629
682
 
683
+ def wait_for_active
684
+ check = Proc.new {
685
+ resp = MU::Cloud::AWS.lambda(region: @region, credentials: @credentials).get_function(function_name: @cloud_id)
686
+ resp.configuration.state != "Active"
687
+ }
688
+ MU.retrier([], max: 40, wait: 15, loop_if: check) {
689
+ }
690
+ end
691
+
630
692
  def get_properties
631
693
  role_obj = MU::Config::Ref.get(@config['role']).kitten(@deploy, cloud: "AWS")
632
694
  raise MuError.new "Failed to fetch object from role reference", details: @config['role'].to_h if !role_obj
@@ -724,7 +786,7 @@ MU.log @cloud_id, MU::WARN, details: JSON.parse(pol) if @cloud_id == "ESPIER-DEV
724
786
  raise MuError, "Function #{@config['name']} had a VPC configured, but none was loaded"
725
787
  end
726
788
  lambda_properties[:vpc_config] = {
727
- :subnet_ids => @vpc.subnets.map { |s| s.cloud_id },
789
+ :subnet_ids => mySubnets.map { |s| s.cloud_id },
728
790
  :security_group_ids => sgs
729
791
  }
730
792
  end