vcloud-network-configurator 0.1.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 (44) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +4 -0
  3. data/README.md +116 -0
  4. data/Rakefile +25 -0
  5. data/bin/vcloud_configure_edge_gateway +5 -0
  6. data/docs/find_network_url.md +56 -0
  7. data/docs/find_organisation_edgegateway_uuid.md +58 -0
  8. data/jenkins.sh +6 -0
  9. data/lib/component/firewall.rb +82 -0
  10. data/lib/component/load_balancer.rb +181 -0
  11. data/lib/component/nat.rb +73 -0
  12. data/lib/vcloud_network_configurator.rb +64 -0
  13. data/lib/vcloud_network_configurator/configure_task.rb +22 -0
  14. data/lib/vcloud_network_configurator/edge_gateway.rb +51 -0
  15. data/lib/vcloud_network_configurator/vcloud_auth_request.rb +39 -0
  16. data/lib/vcloud_network_configurator/vcloud_check_for_configure_task_request.rb +26 -0
  17. data/lib/vcloud_network_configurator/vcloud_configure_request.rb +51 -0
  18. data/lib/vcloud_network_configurator/vcloud_settings.rb +22 -0
  19. data/lib/vcloud_network_configurator/version.rb +1 -0
  20. data/spec/component/firewall.xml +45 -0
  21. data/spec/component/firewall_spec.rb +115 -0
  22. data/spec/component/lb.xml +567 -0
  23. data/spec/component/load_balancer_spec.rb +67 -0
  24. data/spec/component/nat.xml +146 -0
  25. data/spec/component/nat_spec.rb +28 -0
  26. data/spec/integration/authorization_failed_spec.rb +26 -0
  27. data/spec/integration/happy_path_firewall_spec.rb +74 -0
  28. data/spec/integration/happy_path_loadbalancer_spec.rb +173 -0
  29. data/spec/integration/happy_path_nat_spec.rb +78 -0
  30. data/spec/integration/test_data/happy_path_auth_response.xml +30 -0
  31. data/spec/integration/test_data/rules_dir/common_firewall.rb +6 -0
  32. data/spec/integration/test_data/rules_dir/common_lb.rb +10 -0
  33. data/spec/integration/test_data/rules_dir/common_nat.rb +0 -0
  34. data/spec/integration/test_data/rules_dir/preview/firewall.rb +0 -0
  35. data/spec/integration/test_data/rules_dir/preview/interfaces.yaml +2 -0
  36. data/spec/integration/test_data/rules_dir/preview/lb.rb +0 -0
  37. data/spec/integration/test_data/rules_dir/preview/nat.rb +4 -0
  38. data/spec/spec_helper.rb +9 -0
  39. data/spec/vcloud_network_configurator/configure_task_spec.rb +59 -0
  40. data/spec/vcloud_network_configurator/edge_gateway_spec.rb +41 -0
  41. data/spec/vcloud_network_configurator/vcloud_auth_request_spec.rb +20 -0
  42. data/spec/vcloud_network_configurator/vcloud_settings_spec.rb +19 -0
  43. data/vcloud-network-configurator.gemspec +33 -0
  44. metadata +212 -0
@@ -0,0 +1,73 @@
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.instance
29
+ @nat ||= NAT.new
30
+ end
31
+
32
+ def self.generate_xml interfaces
33
+ Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
34
+ 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") {
35
+ xml.NatService {
36
+ xml.IsEnabled "true"
37
+
38
+ NAT.instance.rules.each do |rule|
39
+ xml.NatRule {
40
+ xml.RuleType rule[:type]
41
+ xml.IsEnabled rule[:enabled]
42
+ xml.Id rule[:id]
43
+ xml.GatewayNatRule {
44
+ xml.Interface('type' => "application/vnd.vmware.admin.network+xml", 'name' => rule[:interface], 'href' => interfaces[rule[:interface]])
45
+ xml.OriginalIp rule[:original][:ip]
46
+
47
+ if rule[:original][:port]
48
+ xml.OriginalPort rule[:original][:port]
49
+ end
50
+
51
+ xml.TranslatedIp rule[:translated][:ip]
52
+
53
+ if rule[:translated][:port]
54
+ xml.TranslatedPort rule[:translated][:port]
55
+ end
56
+
57
+ if rule[:type] == "DNAT"
58
+ xml.Protocol rule[:protocol]
59
+ end
60
+
61
+ }
62
+ }
63
+ end
64
+ }
65
+ }
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ def nat (&block)
72
+ Component::NAT.instance.instance_eval(&block)
73
+ 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_configure_edge_gateway [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,51 @@
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
+ @interfaces = YAML::load_file("#{rules_directory}/#{@environment}/interfaces.yaml")['interfaces']
12
+
13
+ require "#{rules_directory}/common_#{component}.rb"
14
+ require "#{rules_directory}/#{@environment}/#{component}"
15
+ end
16
+
17
+ def components
18
+ { "firewall" => "Firewall", "nat" => "NAT", "lb" => "LoadBalancer" }
19
+ end
20
+
21
+ def submit
22
+ url = URI(@config_url)
23
+ request = Net::HTTP::Post.new url.request_uri
24
+ request['Accept'] = VcloudSettings.request_headers['Accept']
25
+ request['Content-Type'] = VcloudSettings.request_headers['Content-Type']
26
+ request['x-vcloud-authorization'] = @auth_header
27
+
28
+ request.body = Kernel.const_get("Component").const_get(components[@component]).generate_xml(@interfaces).to_xml
29
+
30
+ puts "Reading configuration from #{@config_file}"
31
+ puts "Submitting request at #{@config_url}\n"
32
+
33
+ session = Net::HTTP.new(url.host, url.port)
34
+ session.use_ssl = true
35
+ response = session.start do |http|
36
+ http.request request
37
+ end
38
+
39
+ puts "HTTP #{response.code}"
40
+ puts response
41
+ @response = response
42
+ end
43
+
44
+ def success?
45
+ @response.code == "202"
46
+ end
47
+
48
+ def response_body
49
+ @response.body
50
+ end
51
+ 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.0'
@@ -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,115 @@
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 be able to generate XML that matches what we created directly through the control panel" do
15
+ Firewall.reset
16
+ firewall do
17
+ rule "Oubound Traffic", :protocols => [:tcp, :udp] do
18
+ source :ip => "Any", :port => "Any"
19
+ destination :ip => "external", :port => "Any"
20
+ end
21
+
22
+ rule "ssh access to jumpbox1", :protocols => [:tcp] do
23
+ source :ip => "Any", :port => "Any"
24
+ destination :ip => "200.11.99.70", :port => 22
25
+ end
26
+ end
27
+
28
+ Nokogiri::XML(Firewall.generate_xml(@interfaces).doc.root.to_s).should be_equivalent_to Nokogiri::XML(File.open("spec/component/firewall.xml"))
29
+ end
30
+
31
+ it "should default the protocol to tcp" do
32
+ Firewall.reset
33
+ firewall do
34
+ rule "tcp only" do
35
+ source :ip => "Any", :port => "Any"
36
+ destination :ip => "Any", :port => "Any"
37
+ end
38
+ end
39
+
40
+ expected = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
41
+ 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") {
42
+ xml.FirewallService {
43
+ xml.IsEnabled "true"
44
+ xml.DefaultAction "drop"
45
+ xml.LogDefaultAction "false"
46
+
47
+ xml.FirewallRule {
48
+ xml.Id "1"
49
+ xml.IsEnabled "true"
50
+ xml.MatchOnTranslate "false"
51
+ xml.Description "tcp only"
52
+ xml.Policy "allow"
53
+
54
+ xml.Protocols {
55
+ xml.Tcp "true"
56
+ }
57
+
58
+ xml.Port "-1"
59
+ xml.DestinationPortRange "Any"
60
+ xml.DestinationIp "Any"
61
+ xml.SourcePort "-1"
62
+ xml.SourcePortRange "Any"
63
+ xml.SourceIp "Any"
64
+ xml.EnableLogging "false"
65
+ }
66
+ }
67
+ }
68
+ end
69
+
70
+ Nokogiri::XML(Firewall.generate_xml(@interfaces).doc.root.to_s).should be_equivalent_to(expected.doc.root.to_s)
71
+ end
72
+
73
+ it "should default the source to Any" do
74
+ Firewall.reset
75
+ firewall do
76
+ rule "source port any" do
77
+ source :ip => "Any"
78
+ destination :ip => "Any", :port => "Any"
79
+ end
80
+ end
81
+
82
+ expected = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
83
+ 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") {
84
+ xml.FirewallService {
85
+ xml.IsEnabled "true"
86
+ xml.DefaultAction "drop"
87
+ xml.LogDefaultAction "false"
88
+
89
+ xml.FirewallRule {
90
+ xml.Id "1"
91
+ xml.IsEnabled "true"
92
+ xml.MatchOnTranslate "false"
93
+ xml.Description "source port any"
94
+ xml.Policy "allow"
95
+
96
+ xml.Protocols {
97
+ xml.Tcp "true"
98
+ }
99
+
100
+ xml.Port "-1"
101
+ xml.DestinationPortRange "Any"
102
+ xml.DestinationIp "Any"
103
+ xml.SourcePort "-1"
104
+ xml.SourcePortRange "Any"
105
+ xml.SourceIp "Any"
106
+ xml.EnableLogging "false"
107
+ }
108
+ }
109
+ }
110
+ end
111
+
112
+ Nokogiri::XML(Firewall.generate_xml(@interfaces).doc.root.to_s).should be_equivalent_to(expected.doc.root.to_s)
113
+ end
114
+ end
115
+ end