Skip to content

Commit b497d55

Browse files
committed
Added server firewalls
1 parent 942a396 commit b497d55

8 files changed

Lines changed: 156 additions & 81 deletions

File tree

lib/softlayer/ProductItemCategory.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def initialize(package_item_data, price_item_data)
2626
self.recurringFee = price_item_data['recurringFee'] ? price_item_data['recurringFee'].to_f : 0.0
2727
self.hourlyRecurringFee = price_item_data['hourlyRecurringFee'] ? price_item_data['hourlyRecurringFee'].to_f : 0.0
2828
end
29-
29+
3030
# returns true if the configurtion option has no fees associated with it.
3131
def free?
3232
self.setupFee == 0 && self.laborFee == 0 && self.oneTimeFee == 0 && self.recurringFee == 0 && self.hourlyRecurringFee == 0

lib/softlayer/ProductPackage.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def datacenter_options
139139
def items_with_description(expected_description)
140140
filter = ObjectFilter.new { |filter| filter.accept("items.description").when_it is(expected_description) }
141141
items_data = self.service.object_filter(filter).getItems()
142-
142+
143143
items_data.collect do |item_data|
144144
first_price = item_data['prices'][0]
145145
ProductConfigurationOption.new(item_data, first_price)

lib/softlayer/ServerFirewall.rb

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#++
66

77
module SoftLayer
8-
98
##
109
# The ServerFirewall class represents a firewall in the
1110
# SoftLayer environment that exists in a 1 to 1 relationship
@@ -19,6 +18,14 @@ module SoftLayer
1918
class ServerFirewall < SoftLayer::ModelBase
2019
include ::SoftLayer::DynamicAttribute
2120

21+
##
22+
# :attr_reader:
23+
# The state of the firewall, includes whether or not the rules are
24+
# editable and whether or not the firewall rules are applied or bypassed
25+
# Can at least be 'allow_edit', 'bypass' or 'no_edit'.
26+
# This list may not be exhaustive
27+
sl_attr :status
28+
2229
##
2330
# :attr_reader:
2431
# The firewall rules assigned to this firewall. These rules will
@@ -34,10 +41,10 @@ class ServerFirewall < SoftLayer::ModelBase
3441
firewall_rules.to_update do
3542
rules_data = self.service.object_mask(self.class.default_rules_mask).getRules()
3643

37-
# For some reason (at the time of this writing) the object mask is not
38-
# applied to the rules properly. (this has been reported as a bug to the
39-
# proper development team). This extra step does filtering that should
40-
# have been done by the object mask.
44+
# At the time of this writing, the object mask sent to getRules is not
45+
# applied properly. This has been reported as a bug to the proper
46+
# development team. In the mean time, this extra step does filtering
47+
# that should have been done by the object mask.
4148
rules_keys = self.class.default_rules_mask_keys
4249
new_rules = rules_data.inject([]) do |new_rules, current_rule|
4350
new_rule = current_rule.delete_if { |key, value| !(rules_keys.include? key) }
@@ -71,6 +78,9 @@ class ServerFirewall < SoftLayer::ModelBase
7178
end
7279
end
7380

81+
##
82+
# Calls super to initialize the object then initializes some
83+
# properties
7484
def initialize(client, network_hash)
7585
super(client, network_hash)
7686
@protected_server = nil
@@ -102,11 +112,10 @@ def change_rules!(rules_data)
102112
self.softlayer_client[:Network_Firewall_Update_Request].createObject(change_object)
103113
end
104114

105-
106115
##
107116
# Locate and return all the server firewalls in the environment.
108117
#
109-
# These are a bit trick to track down. The strategy we take here is
118+
# These are a bit tricky to track down. The strategy we take here is
110119
# to look at the account and find all the VLANs that do NOT have their
111120
# "dedicatedFirewallFlag" set.
112121
#
@@ -132,10 +141,10 @@ def self.find_firewalls(client)
132141
bare_metal_firewalls_data = []
133142
virtual_firewalls_data = []
134143

135-
shared_vlans = softlayer_client[:Account].object_mask("mask[firewallNetworkComponents[id,status,networkComponent[downlinkComponent[hardwareId]]],firewallGuestNetworkComponents[id,status,guestNetworkComponent[id,guest.id]]]").object_filter(shared_vlans_filter).getNetworkVlans
144+
shared_vlans = softlayer_client[:Account].object_mask(network_vlan_mask).object_filter(shared_vlans_filter).getNetworkVlans
136145
shared_vlans.each do |vlan_data|
137-
bare_metal_firewalls_data.concat vlan_data["firewallNetworkComponents"].select { |network_component| network_component['status'] == 'allow_edit'}
138-
virtual_firewalls_data.concat vlan_data["firewallGuestNetworkComponents"].select { |network_component| network_component['status'] == 'allow_edit'}
146+
bare_metal_firewalls_data.concat vlan_data["firewallNetworkComponents"].select { |network_component| network_component['status'] != 'no_edit'}
147+
virtual_firewalls_data.concat vlan_data["firewallGuestNetworkComponents"].select { |network_component| network_component['status'] != 'no_edit'}
139148
end
140149

141150
bare_metal_firewalls = bare_metal_firewalls_data.collect { |bare_metal_firewall_data|
@@ -152,9 +161,24 @@ def self.find_firewalls(client)
152161
def service
153162
self.softlayer_client[:Network_Component_Firewall].object_with_id(self.id)
154163
end
164+
165+
def softlayer_properties(object_mask = nil)
166+
service = self.service
167+
service = service.object_mask(object_mask) if object_mask
168+
169+
if self.has_sl_property?('networkComponent')
170+
service.object_mask("mask[id,status,networkComponent.downlinkComponent.hardwareId]").getObject
171+
else
172+
service.object_mask("mask[id,status,guestNetworkComponent.guest.id]").getObject
173+
end
174+
end
155175

156176
private
157177

178+
def self.network_vlan_mask
179+
"mask[firewallNetworkComponents[id,status,networkComponent.downlinkComponent.hardwareId],firewallGuestNetworkComponents[id,status,guestNetworkComponent.guest.id]]"
180+
end
181+
158182
def self.default_rules_mask
159183
return { "mask" => default_rules_mask_keys }.to_sl_object_mask
160184
end

lib/softlayer/Service.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ def call_softlayer_api_with_params(method_name, parameters, args)
236236
# The client knows about authentication, so ask him for the auth headers
237237
authentication_headers = self.client.authentication_headers
238238
additional_headers.merge!(authentication_headers)
239-
239+
240240
if parameters && parameters.server_object_filter
241241
additional_headers.merge!("#{@service_name}ObjectFilter" => parameters.server_object_filter)
242242
end
@@ -324,5 +324,5 @@ def http
324324

325325
@xmlrpc_client
326326
end
327-
end # Service class
327+
end # Service class
328328
end # module SoftLayer

lib/softlayer/VLANFirewall.rb

Lines changed: 114 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,88 @@
55
#++
66

77
module SoftLayer
8+
# The VLANFirewall class represents the firewall that protects
9+
# all the servers on a VLAN in the SoftLayer Environment. It is
10+
# also known as a "Dedicated Firewall" in some documentation.
11+
#
12+
# Instances of this class are a bit odd because they actually represent
13+
# VLANs (the VLAN protected by the firewall) and not the physical hardware
14+
# implementing the firewall itself. (although the device is accessible as
15+
# the "networkVlanFirewall" property)
16+
#
17+
# As a result, instances of this class correspond to certain instances
18+
# in the SoftLayer_Network_Vlan service.
19+
#
820
class VLANFirewall < SoftLayer::ModelBase
21+
include ::SoftLayer::DynamicAttribute
22+
23+
##
24+
#:attr_reader:
25+
#
26+
# The number of the VLAN protected by this firewall
27+
#
928
sl_attr :VLAN_number, 'vlanNumber'
1029

1130
##
12-
# return the name of the router the firewall is attached to
31+
# :attr_reader:
32+
#
33+
# The set of rules applied by this firewall to incoming traffic.
34+
# The object will retrieve the rules from the network API every
35+
# time you ask it for the rules.
36+
#
37+
# The code will sort the rules by their "orderValue" which is the
38+
# order that the firewall applies the rules, however please see
39+
# the important note in change_rules! concerning the "orderValue"
40+
# property of the rules.
41+
sl_dynamic_attr :rules do |firewall_rules|
42+
firewall_rules.should_update? do
43+
# firewall rules update every time you ask for them.
44+
return true
45+
end
46+
47+
firewall_rules.to_update do
48+
acl_id = rules_ACL_id()
49+
rules_data = self.softlayer_client[:Network_Firewall_AccessControlList].object_with_id(acl_id).object_mask(self.class.default_rules_mask).getRules
50+
rules_data.sort { |lhs, rhs| lhs['orderValue'] <=> rhs['orderValue'] }
51+
end
52+
end
53+
54+
##
55+
# Change the set of rules for the firewall.
56+
# The rules_data parameter should be an array of hashes where
57+
# each hash gives the conditions of the rule. The keys of the
58+
# hashes should be entries from the array returned by
59+
# SoftLayer::ServerFirewall.default_rules_mask_keys
60+
#
61+
# *NOTE!* The rules themselves have an "orderValue" property.
62+
# It is this property, and *not* the order that the rules are
63+
# found in the rules_data array, which will determine in which
64+
# order the firewall applies it's rules to incomming traffic.
65+
#
66+
# *NOTE!* Changes to the rules are not applied immediately
67+
# on the server side. Instead, they are enqueued by the
68+
# firewall update service and updated periodically. A typical
69+
# update will take about one minute to apply, but times may vary
70+
# depending on the system load and other circumstances.
71+
def change_rules!(rules_data)
72+
change_object = {
73+
"firewallContextAccessControlListId" => self.id,
74+
"rules" => rules_data
75+
}
76+
77+
self.softlayer_client[:Network_Firewall_Update_Request].createObject(change_object)
78+
end
79+
80+
##
81+
# Returns the name of the primary router the firewall is attached to.
82+
# This is often a "customer router" in one of the datacenters.
1383
def primaryRouter
1484
return self['primaryRouter']['hostname']
1585
end
1686

1787
##
18-
# The fully qualified domain name of the firewall
88+
# The fully qualified domain name of the physical device the
89+
# firewall is implemented by.
1990
def fullyQualifiedDomainName
2091
if self.has_sl_property?('networkVlanFirewall')
2192
return self['networkVlanFirewall']['fullyQualifiedDomainName']
@@ -25,51 +96,71 @@ def fullyQualifiedDomainName
2596
end
2697

2798
##
28-
# returns true if the firewall has the high availability flag set
29-
#
99+
# Returns true if this is a "high availability" firewall, that is a firewall
100+
# that exists as one member of a redundant pair.
30101
def high_availability?
31102
# note that highAvailabilityFirewallFlag is a boolean in the softlayer hash
32103
return self.has_sl_property?('highAvailabilityFirewallFlag') && self['highAvailabilityFirewallFlag']
33104
end
34105

35-
def rule_set
36-
rule_set = nil
37-
38-
# Search down through the firewall's data to find the AccessControlList (ACL) for the
39-
# "outside" interface which handles "in"-wardly directed traffic. This is the list that
40-
# has the rules we're interested in.
41-
outside_interface_data = self["firewallInterfaces"].find { |firewall_interface_data| firewall_interface_data['name'] == 'outside' }
42-
if outside_interface_data
43-
incoming_ACL = outside_interface_data['firewallContextAccessControlLists'].find { |firewallACL_data| firewallACL_data['direction'] == 'in' }
44-
45-
firewall_ACL = self.softlayer_client[:Network_Firewall_AccessControlList].object_with_id(incoming_ACL['id']).getObject
46-
rule_set = VLANFirewallRuleset.new(self.softlayer_client, firewall_ACL)
47-
end
48-
49-
return rule_set
50-
end
51-
52106
##
53-
# collect a list of the firewalls on the account
107+
# Collect a list of the firewalls on the account.
54108
#
109+
# This list is obtained by asking the account for all the VLANs
110+
# it has that also have a networkVlanFirewall component.
55111
def self.find_firewalls(client)
56112
softlayer_client = client || Client.default_client
57113
raise "#{__method__} requires a client but none was given and Client::default_client is not set" if !softlayer_client
58114

115+
# only VLAN firewallas have a networkVlanFirewall component
59116
vlan_firewall_filter = SoftLayer::ObjectFilter.new() { |filter|
60117
filter.accept("networkVlans.networkVlanFirewall").when_it is_not_null
61118
}
62119

63120
vlan_firewalls = client[:Account].object_mask(vlan_firewall_mask).object_filter(vlan_firewall_filter).getNetworkVlans
64121
vlan_firewalls.collect { |firewall_data| SoftLayer::VLANFirewall.new(client, firewall_data)}
65122
end
123+
124+
def service
125+
# Objects of this class are a bit odd because they actually represent VLANs (the VLAN protected by the firewall)
126+
# and not the physical hardware implementing the firewall itself. (although the device is accessible as the
127+
# "networkVlanFirewall" property)
128+
self.softlayer_client[:Network_Vlan].object_with_id(self.id)
129+
end
130+
131+
def softlayer_properties(object_mask = nil)
132+
service = self.service
133+
service = service.object_mask(object_mask) if object_mask
134+
service.object_mask(self.class.vlan_firewall_mask).getObject
135+
end
66136

67137
private
68138

139+
# Searches the set of access control lists for the firewall device in order to locate the one that
140+
# sits on the "outside" side of the network and handles 'in'coming traffic.
141+
def rules_ACL_id
142+
outside_interface_data = self['firewallInterfaces'].find { |interface_data| interface_data['name'] == 'outside' }
143+
incoming_ACL = outside_interface_data['firewallContextAccessControlLists'].find { |firewallACL_data| firewallACL_data['direction'] == 'in' } if outside_interface_data
144+
145+
if incoming_ACL
146+
return incoming_ACL['id']
147+
else
148+
return nil
149+
end
150+
end
151+
69152
def self.vlan_firewall_mask
70-
return "mask[primaryRouter,dedicatedFirewallFlag,highAvailabilityFirewallFlag,"+
153+
return "mask[primaryRouter,highAvailabilityFirewallFlag,"+
71154
"firewallInterfaces.firewallContextAccessControlLists," +
72155
"networkVlanFirewall[id, datacenter, primaryIpAddress, firewallType, fullyQualifiedDomainName]]"
73156
end
157+
158+
def self.default_rules_mask
159+
return { "mask" => default_rules_mask_keys }.to_sl_object_mask
160+
end
161+
162+
def self.default_rules_mask_keys
163+
['orderValue','action','destinationIpAddress','destinationIpSubnetMask',"protocol","destinationPortRangeStart","destinationPortRangeEnd",'sourceIpAddress',"sourceIpSubnetMask","version"]
164+
end
74165
end # class Firewall
75166
end # module SoftLayer

lib/softlayer/VLANFirewallRuleset.rb

Lines changed: 0 additions & 40 deletions
This file was deleted.

lib/softlayer_api.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@
2626
require 'softlayer/BareMetalServer'
2727
require 'softlayer/BareMetalServerOrder'
2828
require 'softlayer/BareMetalServerOrder_Package'
29-
require 'softlayer/VLANFirewall'
30-
require 'softlayer/VLANFirewallRuleset'
3129
require 'softlayer/ImageTemplate'
3230
require 'softlayer/NetworkComponent'
3331
require 'softlayer/ProductPackage'
3432
require 'softlayer/ProductItemCategory'
33+
require 'softlayer/ServerFirewall'
3534
require 'softlayer/Ticket'
3635
require 'softlayer/VirtualServer'
3736
require 'softlayer/VirtualServerOrder'
3837
require 'softlayer/VirtualServerUpgradeOrder'
38+
require 'softlayer/VLANFirewall'

spec/VLANFirewall_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
require 'softlayer_api'
1111
require 'rspec'
1212

13-
describe SoftLayer::Firewall do
13+
describe SoftLayer::VLANFirewall do
1414
it "should have a class representing a firewall" do
15-
expect{ SoftLayer::Firewall.new("not really a client", { "id" => 12345 }) }.to_not raise_error
15+
expect{ SoftLayer::VLANFirewall.new("not really a client", { "id" => 12345 }) }.to_not raise_error
1616
end
1717
end

0 commit comments

Comments
 (0)