vcloud-net-spinner 0.1.2

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 (47) hide show
  1. data/.gitignore +2 -0
  2. data/.travis.yml +7 -0
  3. data/CHANGELOG +12 -0
  4. data/Gemfile +4 -0
  5. data/README.md +109 -0
  6. data/Rakefile +25 -0
  7. data/bin/vcloud-net-spinner +5 -0
  8. data/docs/find_network_url.md +56 -0
  9. data/docs/find_organisation_edgegateway_uuid.md +58 -0
  10. data/jenkins.sh +6 -0
  11. data/lib/component/firewall.rb +83 -0
  12. data/lib/component/load_balancer.rb +182 -0
  13. data/lib/component/nat.rb +78 -0
  14. data/lib/vcloud_network_configurator.rb +64 -0
  15. data/lib/vcloud_network_configurator/configure_task.rb +22 -0
  16. data/lib/vcloud_network_configurator/edge_gateway.rb +51 -0
  17. data/lib/vcloud_network_configurator/vcloud_auth_request.rb +39 -0
  18. data/lib/vcloud_network_configurator/vcloud_check_for_configure_task_request.rb +26 -0
  19. data/lib/vcloud_network_configurator/vcloud_configure_request.rb +58 -0
  20. data/lib/vcloud_network_configurator/vcloud_settings.rb +22 -0
  21. data/lib/vcloud_network_configurator/version.rb +1 -0
  22. data/spec/component/firewall.xml +45 -0
  23. data/spec/component/firewall_spec.rb +120 -0
  24. data/spec/component/lb.xml +567 -0
  25. data/spec/component/load_balancer_spec.rb +73 -0
  26. data/spec/component/nat.xml +146 -0
  27. data/spec/component/nat_spec.rb +35 -0
  28. data/spec/integration/authorization_failed_spec.rb +26 -0
  29. data/spec/integration/happy_path_firewall_spec.rb +74 -0
  30. data/spec/integration/happy_path_loadbalancer_spec.rb +173 -0
  31. data/spec/integration/happy_path_nat_spec.rb +78 -0
  32. data/spec/integration/test_data/happy_path_auth_response.xml +30 -0
  33. data/spec/integration/test_data/rules_dir/common_firewall.rb +6 -0
  34. data/spec/integration/test_data/rules_dir/common_lb.rb +10 -0
  35. data/spec/integration/test_data/rules_dir/common_nat.rb +0 -0
  36. data/spec/integration/test_data/rules_dir/preview/firewall.rb +0 -0
  37. data/spec/integration/test_data/rules_dir/preview/interfaces.yaml +2 -0
  38. data/spec/integration/test_data/rules_dir/preview/lb.rb +0 -0
  39. data/spec/integration/test_data/rules_dir/preview/nat.rb +4 -0
  40. data/spec/spec_helper.rb +9 -0
  41. data/spec/vcloud_network_configurator/configure_task_spec.rb +59 -0
  42. data/spec/vcloud_network_configurator/edge_gateway_spec.rb +41 -0
  43. data/spec/vcloud_network_configurator/vcloud_auth_request_spec.rb +20 -0
  44. data/spec/vcloud_network_configurator/vcloud_configure_request_spec.rb +22 -0
  45. data/spec/vcloud_network_configurator/vcloud_settings_spec.rb +19 -0
  46. data/vcloud-net-spinner.gemspec +33 -0
  47. metadata +215 -0
@@ -0,0 +1,78 @@
1
+ require 'rubygems'
2
+ require 'nokogiri'
3
+
4
+ module Component
5
+ class NAT
6
+ attr_reader :rules
7
+
8
+ def initialize
9
+ @rules = []
10
+ @count = 65537
11
+ end
12
+
13
+ def dnat(options)
14
+ rule(options.merge(:type => 'DNAT'))
15
+ end
16
+
17
+ def snat(options)
18
+ rule(options.merge(:type => 'SNAT'))
19
+ end
20
+
21
+ def rule(options)
22
+ @count += 1;
23
+ defaults = { :enabled => true, :protocol => 'tcp', :id=>@count}
24
+ options = defaults.merge(options)
25
+ rules << options
26
+ end
27
+
28
+ def self.reset
29
+ @nat = nil
30
+ end
31
+
32
+ def self.instance
33
+ @nat ||= NAT.new
34
+ end
35
+
36
+ def self.generate_xml interfaces
37
+ return if NAT.instance.rules.nil? or NAT.instance.rules.empty?
38
+ Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
39
+ xml.EdgeGatewayServiceConfiguration('xmlns' => "http://www.vmware.com/vcloud/v1.5", 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance", 'xsi:schemaLocation' => "http://www.vmware.com/vcloud/v1.5 http://vendor-api-url.net/v1.5/schema/master.xsd") {
40
+ xml.NatService {
41
+ xml.IsEnabled "true"
42
+
43
+ NAT.instance.rules.each do |rule|
44
+ xml.NatRule {
45
+ xml.RuleType rule[:type]
46
+ xml.IsEnabled rule[:enabled]
47
+ xml.Id rule[:id]
48
+ xml.GatewayNatRule {
49
+ xml.Interface('type' => "application/vnd.vmware.admin.network+xml", 'name' => rule[:interface], 'href' => interfaces[rule[:interface]])
50
+ xml.OriginalIp rule[:original][:ip]
51
+
52
+ if rule[:original][:port]
53
+ xml.OriginalPort rule[:original][:port]
54
+ end
55
+
56
+ xml.TranslatedIp rule[:translated][:ip]
57
+
58
+ if rule[:translated][:port]
59
+ xml.TranslatedPort rule[:translated][:port]
60
+ end
61
+
62
+ if rule[:type] == "DNAT"
63
+ xml.Protocol rule[:protocol]
64
+ end
65
+
66
+ }
67
+ }
68
+ end
69
+ }
70
+ }
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def nat (&block)
77
+ Component::NAT.instance.instance_eval(&block)
78
+ end
@@ -0,0 +1,64 @@
1
+ require 'optparse'
2
+ require 'component/firewall'
3
+ require 'component/load_balancer'
4
+ require 'component/nat'
5
+ require 'vcloud_network_configurator/edge_gateway'
6
+
7
+ class VcloudNetworkConfigurator
8
+ def initialize args
9
+ @args = args
10
+ @options = {}
11
+ end
12
+
13
+ def execute
14
+ parse @args
15
+ EdgeGateway.new(@options).apply_configuration
16
+ end
17
+
18
+ private
19
+ def parse args
20
+ optparser = OptionParser.new do |o|
21
+ o.banner = "Usage: vcloud-net-spinner [options] API_URL"
22
+ o.summary_width = 40
23
+
24
+ o.on("-u", "--username=U", String, "Vcloud Username") do |v|
25
+ @options[:username] = v
26
+ end
27
+
28
+ o.on("-p", "--password=P", String, "Vcloud Password") do |v|
29
+ @options[:password] = v
30
+ end
31
+
32
+ o.on("-e", "--env=E", String, "Environment: name by which you would refer your environment as (also used for tree structure)") do |v|
33
+ @options[:environment] = v
34
+ end
35
+
36
+ o.on("-U", "--organization-edgegateway-uuid=U",
37
+ "UID: This is required to configure edgegateway services. For more info refer to docs/find_organisation_edgegateway_uuid") do |v|
38
+ @options[:org_edgegateway_uuid] = v
39
+ end
40
+
41
+ o.on("-c", "--component=c", ["lb", "firewall", "nat"], "Environment: lb|firewall|nat") do |v|
42
+ @options[:component] = v
43
+ end
44
+
45
+ o.on("-o", "--organization=o", "Organization: optional. Will default to environment") do |v|
46
+ @options[:organization] = v
47
+ end
48
+
49
+ o.on("-d", "--rule-directory=d", "Rules Directory: From where to read the NAT/Firewal/LB rules") do |v|
50
+ @options[:rules_directory] = v
51
+ end
52
+ end
53
+
54
+ optparser.parse!(@args)
55
+ if !args.empty?
56
+ @options[:api_url] = args[0]
57
+ else
58
+ raise Exception.new("No API_URL provided. See help for more details")
59
+ end
60
+
61
+ @options[:organization] ||= @options[:environment]
62
+ end
63
+
64
+ end
@@ -0,0 +1,22 @@
1
+ require 'nokogiri'
2
+
3
+ class ConfigureTask
4
+ def initialize configure_xml
5
+ @configure_xml = Nokogiri::XML(configure_xml)
6
+ @configure_xml.remove_namespaces!
7
+ end
8
+
9
+ def url
10
+ @configure_xml.xpath("//Task/@href").to_s
11
+ end
12
+
13
+ def complete?
14
+ @configure_xml.xpath("//Task/@status").to_s == "success"
15
+ end
16
+
17
+ def error?
18
+ puts @configure_xml.xpath("//Task/Error/@majorErrorCode")
19
+
20
+ !@configure_xml.xpath("//Task/Error/@majorErrorCode").empty?
21
+ end
22
+ end
@@ -0,0 +1,51 @@
1
+ require 'vcloud_network_configurator/vcloud_auth_request'
2
+ require 'vcloud_network_configurator/vcloud_configure_request'
3
+ require 'vcloud_network_configurator/vcloud_check_for_configure_task_request'
4
+ require 'vcloud_network_configurator/configure_task'
5
+
6
+ class EdgeGateway
7
+ def initialize options
8
+ @options = options
9
+ @vcloud_settings = VcloudSettings.new( { url: @options[:api_url], edge_gateway_uuid: @options[:org_edgegateway_uuid] } )
10
+ end
11
+
12
+ def apply_configuration
13
+ auth_header = authorize_request
14
+ configure_request = VcloudConfigureRequest.new(@vcloud_settings, auth_header, @options[:environment], @options[:component], @options[:rules_directory])
15
+ configure_request.submit
16
+
17
+ if configure_request.success?
18
+ check_for_success auth_header, ConfigureTask.new(configure_request.response_body)
19
+ return true
20
+ else
21
+ puts "Failed to configure the edge gateway"
22
+ return false
23
+ end
24
+ end
25
+
26
+ private
27
+ def authorize_request
28
+ auth_request = VcloudAuthRequest.new(@vcloud_settings, "#{@options[:username]}@#{@options[:organization]}", @options[:password])
29
+ auth_request.submit
30
+ abort("Could not authenticate user") unless auth_request.authenticated?
31
+
32
+ auth_request.auth_response["x-vcloud-authorization"]
33
+ end
34
+
35
+ def check_for_success auth_header, configure_task
36
+ begin
37
+ puts "\n\n\nSleeping for 10 seconds before the next check for success \n\n\n"
38
+ sleep(10) unless ENV['GEM_ENV'] == "test"
39
+ response = VcloudCheckForConfigureTaskRequest.new(auth_header, configure_task.url).submit
40
+
41
+ configure_task = ConfigureTask.new(response.body)
42
+
43
+ if configure_task.error?
44
+ abort("Failed to configure the edge gateway")
45
+ end
46
+
47
+ end while not configure_task.complete?
48
+
49
+ puts "\n\n\nSuccessfully configured the edge gateway"
50
+ end
51
+ end
@@ -0,0 +1,39 @@
1
+ require "net/http"
2
+ require "vcloud_network_configurator/vcloud_settings"
3
+
4
+ class VcloudAuthRequest
5
+
6
+ def initialize vcloud_settings, username, password
7
+ @user_name = username
8
+ @password = password
9
+ @vcloud_settings = vcloud_settings
10
+ @response = nil
11
+ end
12
+
13
+ def submit
14
+ puts "Submitting auth request at #{@vcloud_settings.sessions_url}\n"
15
+ url = URI(@vcloud_settings.sessions_url)
16
+ request = Net::HTTP::Post.new url.request_uri
17
+ request['Accept'] = VcloudSettings.request_headers['Accept']
18
+ request.basic_auth @user_name, @password
19
+ session = Net::HTTP.new(url.host, url.port)
20
+ session.use_ssl = true
21
+
22
+ response = session.start do |http|
23
+ http.request request
24
+ end
25
+
26
+ puts "HTTP #{response.code}"
27
+ puts response
28
+ @response = response
29
+ end
30
+
31
+ def authenticated?
32
+ auth_response.code == "200"
33
+ end
34
+
35
+ def auth_response
36
+ @response
37
+ end
38
+
39
+ end
@@ -0,0 +1,26 @@
1
+ require "net/http"
2
+
3
+ class VcloudCheckForConfigureTaskRequest
4
+
5
+ def initialize auth_header, task_url
6
+ @auth_header = auth_header
7
+ @task_url = task_url
8
+ end
9
+
10
+ def submit
11
+ url = URI(@task_url)
12
+ request = Net::HTTP::Get.new url.request_uri
13
+ request['Accept'] = 'application/*+xml;version=5.1'
14
+ request['x-vcloud-authorization'] = @auth_header
15
+
16
+ puts "Submitting request at #{@task_url}"
17
+
18
+ session = Net::HTTP.new(url.host, url.port)
19
+ session.use_ssl = true
20
+ response = session.start do |http|
21
+ http.request request
22
+ end
23
+ puts response
24
+ return response
25
+ end
26
+ end
@@ -0,0 +1,58 @@
1
+ require "net/http"
2
+ require 'yaml'
3
+
4
+ class VcloudConfigureRequest
5
+ def initialize vcloud_settings, auth_header, environment, component, rules_directory
6
+ @auth_header = auth_header
7
+ @config_url = vcloud_settings.edge_gateway_config_url
8
+ @environment = environment
9
+ @component = component
10
+ @response = nil
11
+
12
+
13
+ @interfaces = File.file?("#{rules_directory}/#{@environment}/interfaces.yaml") ?
14
+ YAML::load_file("#{rules_directory}/#{@environment}/interfaces.yaml")['interfaces'] : {}
15
+
16
+ require "#{rules_directory}/common_#{component}.rb" if File.file?("#{rules_directory}/common_#{component}.rb")
17
+ require "#{rules_directory}/#{@environment}/#{component}" if File.file?("#{rules_directory}/#{@environment}/#{component}.rb")
18
+ end
19
+
20
+ def components
21
+ { "firewall" => "Firewall", "nat" => "NAT", "lb" => "LoadBalancer" }
22
+ end
23
+
24
+ def submit
25
+ generated_xml = Kernel.const_get("Component").const_get(components[@component]).
26
+ generate_xml(@interfaces)
27
+ abort "No rules found. exiting" if generated_xml.nil?
28
+
29
+ url = URI(@config_url)
30
+ request = Net::HTTP::Post.new url.request_uri
31
+ request['Accept'] = VcloudSettings.request_headers['Accept']
32
+ request['Content-Type'] = VcloudSettings.request_headers['Content-Type']
33
+ request['x-vcloud-authorization'] = @auth_header
34
+
35
+ request.body = generated_xml.to_xml
36
+
37
+ puts "Reading configuration from #{@config_file}"
38
+ puts "Submitting request at #{@config_url}\n"
39
+
40
+ session = Net::HTTP.new(url.host, url.port)
41
+ session.use_ssl = true
42
+ response = session.start do |http|
43
+ http.request request
44
+ end
45
+
46
+ puts "HTTP #{response.code}"
47
+ puts response
48
+ @response = response
49
+ end
50
+
51
+ def success?
52
+ @response.code == "202"
53
+ end
54
+
55
+ def response_body
56
+ @response.body
57
+ end
58
+ end
@@ -0,0 +1,22 @@
1
+ class VcloudSettings
2
+
3
+ def initialize options = {}
4
+ @api_url = options[:url]
5
+ @edge_gateway_uuid = options[:edge_gateway_uuid]
6
+ end
7
+
8
+ def sessions_url
9
+ @api_url + "/sessions"
10
+ end
11
+
12
+ def edge_gateway_config_url
13
+ @api_url + "/admin/edgeGateway/" + @edge_gateway_uuid + "/action/configureServices"
14
+ end
15
+
16
+ def self.request_headers
17
+ {
18
+ 'Accept' => 'application/*+xml;version=5.1',
19
+ 'Content-Type' => 'application/vnd.vmware.admin.edgeGatewayServiceConfiguration+xml'
20
+ }
21
+ end
22
+ end
@@ -0,0 +1 @@
1
+ VERSION = '0.1.2'
@@ -0,0 +1,45 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <EdgeGatewayServiceConfiguration xmlns="http://www.vmware.com/vcloud/v1.5"
3
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+ xsi:schemaLocation="http://www.vmware.com/vcloud/v1.5 http://vendor-api-url.net/v1.5/schema/master.xsd">
5
+ <FirewallService>
6
+ <IsEnabled>true</IsEnabled>
7
+ <DefaultAction>drop</DefaultAction>
8
+ <LogDefaultAction>false</LogDefaultAction>
9
+ <FirewallRule>
10
+ <Id>1</Id>
11
+ <IsEnabled>true</IsEnabled>
12
+ <MatchOnTranslate>false</MatchOnTranslate>
13
+ <Description>Oubound Traffic</Description>
14
+ <Policy>allow</Policy>
15
+ <Protocols>
16
+ <Tcp>true</Tcp>
17
+ <Udp>true</Udp>
18
+ </Protocols>
19
+ <Port>-1</Port>
20
+ <DestinationPortRange>Any</DestinationPortRange>
21
+ <DestinationIp>external</DestinationIp>
22
+ <SourcePort>-1</SourcePort>
23
+ <SourcePortRange>Any</SourcePortRange>
24
+ <SourceIp>Any</SourceIp>
25
+ <EnableLogging>false</EnableLogging>
26
+ </FirewallRule>
27
+ <FirewallRule>
28
+ <Id>2</Id>
29
+ <IsEnabled>true</IsEnabled>
30
+ <MatchOnTranslate>false</MatchOnTranslate>
31
+ <Description>ssh access to jumpbox1</Description>
32
+ <Policy>allow</Policy>
33
+ <Protocols>
34
+ <Tcp>true</Tcp>
35
+ </Protocols>
36
+ <Port>22</Port>
37
+ <DestinationPortRange>22</DestinationPortRange>
38
+ <DestinationIp>200.11.99.70</DestinationIp>
39
+ <SourcePort>-1</SourcePort>
40
+ <SourcePortRange>Any</SourcePortRange>
41
+ <SourceIp>Any</SourceIp>
42
+ <EnableLogging>false</EnableLogging>
43
+ </FirewallRule>
44
+ </FirewallService>
45
+ </EdgeGatewayServiceConfiguration>
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+ require 'nokogiri'
3
+ require 'equivalent-xml'
4
+ require 'component/firewall'
5
+
6
+ module Component
7
+ describe "firewall" do
8
+ before :each do
9
+ @interfaces = {
10
+ "TestData" => "https://vendor-api-url.net/admin/network/1000"
11
+ }
12
+ end
13
+
14
+ it "should not generate xml if no rules present" do
15
+ Firewall.reset
16
+ Firewall.generate_xml(@interfaces).should be_nil
17
+ end
18
+
19
+ it "should be able to generate XML that matches what we created directly through the control panel" do
20
+ Firewall.reset
21
+ firewall do
22
+ rule "Oubound Traffic", :protocols => [:tcp, :udp] do
23
+ source :ip => "Any", :port => "Any"
24
+ destination :ip => "external", :port => "Any"
25
+ end
26
+
27
+ rule "ssh access to jumpbox1", :protocols => [:tcp] do
28
+ source :ip => "Any", :port => "Any"
29
+ destination :ip => "200.11.99.70", :port => 22
30
+ end
31
+ end
32
+
33
+ Nokogiri::XML(Firewall.generate_xml(@interfaces).doc.root.to_s).should be_equivalent_to Nokogiri::XML(File.open("spec/component/firewall.xml"))
34
+ end
35
+
36
+ it "should default the protocol to tcp" do
37
+ Firewall.reset
38
+ firewall do
39
+ rule "tcp only" do
40
+ source :ip => "Any", :port => "Any"
41
+ destination :ip => "Any", :port => "Any"
42
+ end
43
+ end
44
+
45
+ expected = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
46
+ xml.EdgeGatewayServiceConfiguration('xmlns' => "http://www.vmware.com/vcloud/v1.5", 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance", 'xsi:schemaLocation' => "http://www.vmware.com/vcloud/v1.5 http://vendor-api-url.net/v1.5/schema/master.xsd") {
47
+ xml.FirewallService {
48
+ xml.IsEnabled "true"
49
+ xml.DefaultAction "drop"
50
+ xml.LogDefaultAction "false"
51
+
52
+ xml.FirewallRule {
53
+ xml.Id "1"
54
+ xml.IsEnabled "true"
55
+ xml.MatchOnTranslate "false"
56
+ xml.Description "tcp only"
57
+ xml.Policy "allow"
58
+
59
+ xml.Protocols {
60
+ xml.Tcp "true"
61
+ }
62
+
63
+ xml.Port "-1"
64
+ xml.DestinationPortRange "Any"
65
+ xml.DestinationIp "Any"
66
+ xml.SourcePort "-1"
67
+ xml.SourcePortRange "Any"
68
+ xml.SourceIp "Any"
69
+ xml.EnableLogging "false"
70
+ }
71
+ }
72
+ }
73
+ end
74
+
75
+ Nokogiri::XML(Firewall.generate_xml(@interfaces).doc.root.to_s).should be_equivalent_to(expected.doc.root.to_s)
76
+ end
77
+
78
+ it "should default the source to Any" do
79
+ Firewall.reset
80
+ firewall do
81
+ rule "source port any" do
82
+ source :ip => "Any"
83
+ destination :ip => "Any", :port => "Any"
84
+ end
85
+ end
86
+
87
+ expected = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
88
+ xml.EdgeGatewayServiceConfiguration('xmlns' => "http://www.vmware.com/vcloud/v1.5", 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance", 'xsi:schemaLocation' => "http://www.vmware.com/vcloud/v1.5 http://vendor-api-url.net/v1.5/schema/master.xsd") {
89
+ xml.FirewallService {
90
+ xml.IsEnabled "true"
91
+ xml.DefaultAction "drop"
92
+ xml.LogDefaultAction "false"
93
+
94
+ xml.FirewallRule {
95
+ xml.Id "1"
96
+ xml.IsEnabled "true"
97
+ xml.MatchOnTranslate "false"
98
+ xml.Description "source port any"
99
+ xml.Policy "allow"
100
+
101
+ xml.Protocols {
102
+ xml.Tcp "true"
103
+ }
104
+
105
+ xml.Port "-1"
106
+ xml.DestinationPortRange "Any"
107
+ xml.DestinationIp "Any"
108
+ xml.SourcePort "-1"
109
+ xml.SourcePortRange "Any"
110
+ xml.SourceIp "Any"
111
+ xml.EnableLogging "false"
112
+ }
113
+ }
114
+ }
115
+ end
116
+
117
+ Nokogiri::XML(Firewall.generate_xml(@interfaces).doc.root.to_s).should be_equivalent_to(expected.doc.root.to_s)
118
+ end
119
+ end
120
+ end