vcloud-network-configurator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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