PageRenderTime 46ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/puppet/type/firewall.rb

https://github.com/blentz/puppetlabs-firewall
Ruby | 731 lines | 555 code | 142 blank | 34 comment | 62 complexity | 293f55c1adaa7b5a3ea240e618ff7a9f MD5 | raw file
Possible License(s): Apache-2.0
  1. # See: #10295 for more details.
  2. #
  3. # This is a workaround for bug: #4248 whereby ruby files outside of the normal
  4. # provider/type path do not load until pluginsync has occured on the puppetmaster
  5. #
  6. # In this case I'm trying the relative path first, then falling back to normal
  7. # mechanisms. This should be fixed in future versions of puppet but it looks
  8. # like we'll need to maintain this for some time perhaps.
  9. $LOAD_PATH.unshift(File.join(File.dirname(__FILE__),"..",".."))
  10. require 'puppet/util/firewall'
  11. Puppet::Type.newtype(:firewall) do
  12. include Puppet::Util::Firewall
  13. @doc = <<-EOS
  14. This type provides the capability to manage firewall rules within
  15. puppet.
  16. **Autorequires:**
  17. If Puppet is managing the iptables or ip6tables chains specified in the
  18. `chain` or `jump` parameters, the firewall resource will autorequire
  19. those firewallchain resources.
  20. If Puppet is managing the iptables or iptables-persistent packages, and
  21. the provider is iptables or ip6tables, the firewall resource will
  22. autorequire those packages to ensure that any required binaries are
  23. installed.
  24. EOS
  25. feature :rate_limiting, "Rate limiting features."
  26. feature :snat, "Source NATing"
  27. feature :dnat, "Destination NATing"
  28. feature :interface_match, "Interface matching"
  29. feature :icmp_match, "Matching ICMP types"
  30. feature :owner, "Matching owners"
  31. feature :state_match, "Matching stateful firewall states"
  32. feature :reject_type, "The ability to control reject messages"
  33. feature :log_level, "The ability to control the log level"
  34. feature :log_prefix, "The ability to add prefixes to log messages"
  35. feature :mark, "Set the netfilter mark value associated with the packet"
  36. feature :tcp_flags, "The ability to match on particular TCP flag settings"
  37. feature :pkttype, "Match a packet type"
  38. feature :socket, "Match open sockets"
  39. feature :isfragment, "Match fragments"
  40. # provider specific features
  41. feature :iptables, "The provider provides iptables features."
  42. ensurable do
  43. desc <<-EOS
  44. Manage the state of this rule. The default action is *present*.
  45. EOS
  46. newvalue(:present) do
  47. provider.insert
  48. end
  49. newvalue(:absent) do
  50. provider.delete
  51. end
  52. defaultto :present
  53. end
  54. newparam(:name) do
  55. desc <<-EOS
  56. The canonical name of the rule. This name is also used for ordering
  57. so make sure you prefix the rule with a number:
  58. 000 this runs first
  59. 999 this runs last
  60. Depending on the provider, the name of the rule can be stored using
  61. the comment feature of the underlying firewall subsystem.
  62. EOS
  63. isnamevar
  64. # Keep rule names simple - they must start with a number
  65. newvalues(/^\d+[[:alpha:][:digit:][:punct:][:space:]]+$/)
  66. end
  67. newproperty(:action) do
  68. desc <<-EOS
  69. This is the action to perform on a match. Can be one of:
  70. * accept - the packet is accepted
  71. * reject - the packet is rejected with a suitable ICMP response
  72. * drop - the packet is dropped
  73. If you specify no value it will simply match the rule but perform no
  74. action unless you provide a provider specific parameter (such as *jump*).
  75. EOS
  76. newvalues(:accept, :reject, :drop)
  77. end
  78. # Generic matching properties
  79. newproperty(:source) do
  80. desc <<-EOS
  81. The source address. For example:
  82. source => '192.168.2.0/24'
  83. The source can also be an IPv6 address if your provider supports it.
  84. EOS
  85. munge do |value|
  86. begin
  87. @resource.host_to_ip(value)
  88. rescue Exception => e
  89. self.fail("host_to_ip failed for #{value}, exception #{e}")
  90. end
  91. end
  92. end
  93. newproperty(:destination) do
  94. desc <<-EOS
  95. The destination address to match. For example:
  96. destination => '192.168.1.0/24'
  97. The destination can also be an IPv6 address if your provider supports it.
  98. EOS
  99. munge do |value|
  100. begin
  101. @resource.host_to_ip(value)
  102. rescue Exception => e
  103. self.fail("host_to_ip failed for #{value}, exception #{e}")
  104. end
  105. end
  106. end
  107. newproperty(:sport, :array_matching => :all) do
  108. desc <<-EOS
  109. The source port to match for this filter (if the protocol supports
  110. ports). Will accept a single element or an array.
  111. For some firewall providers you can pass a range of ports in the format:
  112. <start_number>-<ending_number>
  113. For example:
  114. 1-1024
  115. This would cover ports 1 to 1024.
  116. EOS
  117. munge do |value|
  118. @resource.string_to_port(value, :proto)
  119. end
  120. def is_to_s(value)
  121. should_to_s(value)
  122. end
  123. def should_to_s(value)
  124. value = [value] unless value.is_a?(Array)
  125. value.join(',')
  126. end
  127. end
  128. newproperty(:dport, :array_matching => :all) do
  129. desc <<-EOS
  130. The destination port to match for this filter (if the protocol supports
  131. ports). Will accept a single element or an array.
  132. For some firewall providers you can pass a range of ports in the format:
  133. <start_number>-<ending_number>
  134. For example:
  135. 1-1024
  136. This would cover ports 1 to 1024.
  137. EOS
  138. munge do |value|
  139. @resource.string_to_port(value, :proto)
  140. end
  141. def is_to_s(value)
  142. should_to_s(value)
  143. end
  144. def should_to_s(value)
  145. value = [value] unless value.is_a?(Array)
  146. value.join(',')
  147. end
  148. end
  149. newproperty(:port, :array_matching => :all) do
  150. desc <<-EOS
  151. The destination or source port to match for this filter (if the protocol
  152. supports ports). Will accept a single element or an array.
  153. For some firewall providers you can pass a range of ports in the format:
  154. <start_number>-<ending_number>
  155. For example:
  156. 1-1024
  157. This would cover ports 1 to 1024.
  158. EOS
  159. munge do |value|
  160. @resource.string_to_port(value, :proto)
  161. end
  162. def is_to_s(value)
  163. should_to_s(value)
  164. end
  165. def should_to_s(value)
  166. value = [value] unless value.is_a?(Array)
  167. value.join(',')
  168. end
  169. end
  170. newproperty(:proto) do
  171. desc <<-EOS
  172. The specific protocol to match for this rule. By default this is
  173. *tcp*.
  174. EOS
  175. newvalues(:tcp, :udp, :icmp, :"ipv6-icmp", :esp, :ah, :vrrp, :igmp, :ipencap, :ospf, :gre, :all)
  176. defaultto "tcp"
  177. end
  178. # tcp-specific
  179. newproperty(:tcp_flags, :required_features => :tcp_flags) do
  180. desc <<-EOS
  181. Match when the TCP flags are as specified.
  182. Is a string with a list of comma-separated flag names for the mask,
  183. then a space, then a comma-separated list of flags that should be set.
  184. The flags are: SYN ACK FIN RST URG PSH ALL NONE
  185. Note that you specify them in the order that iptables --list-rules
  186. would list them to avoid having puppet think you changed the flags.
  187. Example: FIN,SYN,RST,ACK SYN matches packets with the SYN bit set and the
  188. ACK,RST and FIN bits cleared. Such packets are used to request
  189. TCP connection initiation.
  190. EOS
  191. end
  192. # Iptables specific
  193. newproperty(:chain, :required_features => :iptables) do
  194. desc <<-EOS
  195. Name of the chain to use. Can be one of the built-ins:
  196. * INPUT
  197. * FORWARD
  198. * OUTPUT
  199. * PREROUTING
  200. * POSTROUTING
  201. Or you can provide a user-based chain.
  202. The default value is 'INPUT'.
  203. EOS
  204. defaultto "INPUT"
  205. newvalue(/^[a-zA-Z0-9\-_]+$/)
  206. end
  207. newproperty(:table, :required_features => :iptables) do
  208. desc <<-EOS
  209. Table to use. Can be one of:
  210. * nat
  211. * mangle
  212. * filter
  213. * raw
  214. * rawpost
  215. By default the setting is 'filter'.
  216. EOS
  217. newvalues(:nat, :mangle, :filter, :raw, :rawpost)
  218. defaultto "filter"
  219. end
  220. newproperty(:jump, :required_features => :iptables) do
  221. desc <<-EOS
  222. The value for the iptables --jump parameter. Normal values are:
  223. * QUEUE
  224. * RETURN
  225. * DNAT
  226. * SNAT
  227. * LOG
  228. * MASQUERADE
  229. * REDIRECT
  230. * MARK
  231. But any valid chain name is allowed.
  232. For the values ACCEPT, DROP and REJECT you must use the generic
  233. 'action' parameter. This is to enfore the use of generic parameters where
  234. possible for maximum cross-platform modelling.
  235. If you set both 'accept' and 'jump' parameters, you will get an error as
  236. only one of the options should be set.
  237. EOS
  238. validate do |value|
  239. unless value =~ /^[a-zA-Z0-9\-_]+$/
  240. raise ArgumentError, <<-EOS
  241. Jump destination must consist of alphanumeric characters, an
  242. underscore or a yphen.
  243. EOS
  244. end
  245. if ["accept","reject","drop"].include?(value.downcase)
  246. raise ArgumentError, <<-EOS
  247. Jump destination should not be one of ACCEPT, REJECT or DROP. Use
  248. the action property instead.
  249. EOS
  250. end
  251. end
  252. end
  253. # Interface specific matching properties
  254. newproperty(:iniface, :required_features => :interface_match) do
  255. desc <<-EOS
  256. Input interface to filter on.
  257. EOS
  258. newvalues(/^[a-zA-Z0-9\-\._\+]+$/)
  259. end
  260. newproperty(:outiface, :required_features => :interface_match) do
  261. desc <<-EOS
  262. Output interface to filter on.
  263. EOS
  264. newvalues(/^[a-zA-Z0-9\-\._\+]+$/)
  265. end
  266. # NAT specific properties
  267. newproperty(:tosource, :required_features => :snat) do
  268. desc <<-EOS
  269. When using jump => "SNAT" you can specify the new source address using
  270. this parameter.
  271. EOS
  272. end
  273. newproperty(:todest, :required_features => :dnat) do
  274. desc <<-EOS
  275. When using jump => "DNAT" you can specify the new destination address
  276. using this paramter.
  277. EOS
  278. end
  279. newproperty(:toports, :required_features => :dnat) do
  280. desc <<-EOS
  281. For DNAT this is the port that will replace the destination port.
  282. EOS
  283. end
  284. # Reject ICMP type
  285. newproperty(:reject, :required_features => :reject_type) do
  286. desc <<-EOS
  287. When combined with jump => "REJECT" you can specify a different icmp
  288. response to be sent back to the packet sender.
  289. EOS
  290. end
  291. # Logging properties
  292. newproperty(:log_level, :required_features => :log_level) do
  293. desc <<-EOS
  294. When combined with jump => "LOG" specifies the system log level to log
  295. to.
  296. EOS
  297. munge do |value|
  298. if value.kind_of?(String)
  299. value = @resource.log_level_name_to_number(value)
  300. else
  301. value
  302. end
  303. if value == nil && value != ""
  304. self.fail("Unable to determine log level")
  305. end
  306. value
  307. end
  308. end
  309. newproperty(:log_prefix, :required_features => :log_prefix) do
  310. desc <<-EOS
  311. When combined with jump => "LOG" specifies the log prefix to use when
  312. logging.
  313. EOS
  314. end
  315. # ICMP matching property
  316. newproperty(:icmp, :required_features => :icmp_match) do
  317. desc <<-EOS
  318. When matching ICMP packets, this is the type of ICMP packet to match.
  319. A value of "any" is not supported. To achieve this behaviour the
  320. parameter should simply be omitted or undefined.
  321. EOS
  322. validate do |value|
  323. if value == "any"
  324. raise ArgumentError,
  325. "Value 'any' is not valid. This behaviour should be achieved " \
  326. "by omitting or undefining the ICMP parameter."
  327. end
  328. end
  329. munge do |value|
  330. if value.kind_of?(String)
  331. # ICMP codes differ between IPv4 and IPv6.
  332. case @resource[:provider]
  333. when :iptables
  334. protocol = 'inet'
  335. when :ip6tables
  336. protocol = 'inet6'
  337. else
  338. self.fail("cannot work out protocol family")
  339. end
  340. value = @resource.icmp_name_to_number(value, protocol)
  341. else
  342. value
  343. end
  344. if value == nil && value != ""
  345. self.fail("cannot work out icmp type")
  346. end
  347. value
  348. end
  349. end
  350. newproperty(:state, :array_matching => :all, :required_features =>
  351. :state_match) do
  352. desc <<-EOS
  353. Matches a packet based on its state in the firewall stateful inspection
  354. table. Values can be:
  355. * INVALID
  356. * ESTABLISHED
  357. * NEW
  358. * RELATED
  359. EOS
  360. newvalues(:INVALID,:ESTABLISHED,:NEW,:RELATED)
  361. # States should always be sorted. This normalizes the resource states to
  362. # keep it consistent with the sorted result from iptables-save.
  363. def should=(values)
  364. @should = super(values).sort_by {|sym| sym.to_s}
  365. end
  366. def is_to_s(value)
  367. should_to_s(value)
  368. end
  369. def should_to_s(value)
  370. value = [value] unless value.is_a?(Array)
  371. value.join(',')
  372. end
  373. end
  374. # Rate limiting properties
  375. newproperty(:limit, :required_features => :rate_limiting) do
  376. desc <<-EOS
  377. Rate limiting value for matched packets. The format is:
  378. rate/[/second/|/minute|/hour|/day].
  379. Example values are: '50/sec', '40/min', '30/hour', '10/day'."
  380. EOS
  381. end
  382. newproperty(:burst, :required_features => :rate_limiting) do
  383. desc <<-EOS
  384. Rate limiting burst value (per second) before limit checks apply.
  385. EOS
  386. newvalue(/^\d+$/)
  387. end
  388. newproperty(:uid, :required_features => :owner) do
  389. desc <<-EOS
  390. UID or Username owner matching rule. Accepts a string argument
  391. only, as iptables does not accept multiple uid in a single
  392. statement.
  393. EOS
  394. end
  395. newproperty(:gid, :required_features => :owner) do
  396. desc <<-EOS
  397. GID or Group owner matching rule. Accepts a string argument
  398. only, as iptables does not accept multiple gid in a single
  399. statement.
  400. EOS
  401. end
  402. newproperty(:set_mark, :required_features => :mark) do
  403. desc <<-EOS
  404. Set the Netfilter mark value associated with the packet. Accepts either of:
  405. mark/mask or mark. These will be converted to hex if they are not already.
  406. EOS
  407. munge do |value|
  408. int_or_hex = '[a-fA-F0-9x]'
  409. match = value.to_s.match("(#{int_or_hex}+)(/)?(#{int_or_hex}+)?")
  410. mark = @resource.to_hex32(match[1])
  411. # Values that can't be converted to hex.
  412. # Or contain a trailing slash with no mask.
  413. if mark.nil? or (mark and match[2] and match[3].nil?)
  414. raise ArgumentError, "MARK value must be integer or hex between 0 and 0xffffffff"
  415. end
  416. # Old iptables does not support a mask. New iptables will expect one.
  417. iptables_version = Facter.fact('iptables_version').value
  418. mask_required = (iptables_version and Puppet::Util::Package.versioncmp(iptables_version, '1.4.1') >= 0)
  419. if mask_required
  420. if match[3].nil?
  421. value = "#{mark}/0xffffffff"
  422. else
  423. mask = @resource.to_hex32(match[3])
  424. if mask.nil?
  425. raise ArgumentError, "MARK mask must be integer or hex between 0 and 0xffffffff"
  426. end
  427. value = "#{mark}/#{mask}"
  428. end
  429. else
  430. unless match[3].nil?
  431. raise ArgumentError, "iptables version #{iptables_version} does not support masks on MARK rules"
  432. end
  433. value = mark
  434. end
  435. value
  436. end
  437. end
  438. newproperty(:pkttype, :required_features => :pkttype) do
  439. desc <<-EOS
  440. Sets the packet type to match.
  441. EOS
  442. newvalues(:unicast, :broadcast, :multicast)
  443. end
  444. newproperty(:isfragment, :required_features => :isfragment) do
  445. desc <<-EOS
  446. Set to true to match tcp fragments (requires type to be set to tcp)
  447. EOS
  448. newvalues(:true, :false)
  449. end
  450. newproperty(:socket, :required_features => :socket) do
  451. desc <<-EOS
  452. If true, matches if an open socket can be found by doing a coket lookup
  453. on the packet.
  454. EOS
  455. newvalues(:true, :false)
  456. end
  457. newparam(:line) do
  458. desc <<-EOS
  459. Read-only property for caching the rule line.
  460. EOS
  461. end
  462. autorequire(:firewallchain) do
  463. reqs = []
  464. protocol = nil
  465. case value(:provider)
  466. when :iptables
  467. protocol = "IPv4"
  468. when :ip6tables
  469. protocol = "IPv6"
  470. end
  471. unless protocol.nil?
  472. [value(:chain), value(:jump)].each do |chain|
  473. reqs << "#{chain}:#{value(:table)}:#{protocol}" unless chain.nil?
  474. end
  475. end
  476. reqs
  477. end
  478. # Classes would be a better abstraction, pending:
  479. # http://projects.puppetlabs.com/issues/19001
  480. autorequire(:package) do
  481. case value(:provider)
  482. when :iptables, :ip6tables
  483. %w{iptables iptables-persistent}
  484. else
  485. []
  486. end
  487. end
  488. validate do
  489. debug("[validate]")
  490. # TODO: this is put here to skip validation if ensure is not set. This
  491. # is because there is a revalidation stage called later where the values
  492. # are not set correctly. I tried tracing it - but have put in this
  493. # workaround instead to skip. Must get to the bottom of this.
  494. if ! value(:ensure)
  495. return
  496. end
  497. # First we make sure the chains and tables are valid combinations
  498. if value(:table).to_s == "filter" &&
  499. value(:chain) =~ /PREROUTING|POSTROUTING/
  500. self.fail "PREROUTING and POSTROUTING cannot be used in table 'filter'"
  501. end
  502. if value(:table).to_s == "nat" && value(:chain) =~ /INPUT|FORWARD/
  503. self.fail "INPUT and FORWARD cannot be used in table 'nat'"
  504. end
  505. if value(:table).to_s == "raw" &&
  506. value(:chain) =~ /INPUT|FORWARD|POSTROUTING/
  507. self.fail "INPUT, FORWARD and POSTROUTING cannot be used in table raw"
  508. end
  509. # Now we analyse the individual properties to make sure they apply to
  510. # the correct combinations.
  511. if value(:iniface)
  512. unless value(:chain).to_s =~ /INPUT|FORWARD|PREROUTING/
  513. self.fail "Parameter iniface only applies to chains " \
  514. "INPUT,FORWARD,PREROUTING"
  515. end
  516. end
  517. if value(:outiface)
  518. unless value(:chain).to_s =~ /OUTPUT|FORWARD|POSTROUTING/
  519. self.fail "Parameter outiface only applies to chains " \
  520. "OUTPUT,FORWARD,POSTROUTING"
  521. end
  522. end
  523. if value(:uid)
  524. unless value(:chain).to_s =~ /OUTPUT|POSTROUTING/
  525. self.fail "Parameter uid only applies to chains " \
  526. "OUTPUT,POSTROUTING"
  527. end
  528. end
  529. if value(:gid)
  530. unless value(:chain).to_s =~ /OUTPUT|POSTROUTING/
  531. self.fail "Parameter gid only applies to chains " \
  532. "OUTPUT,POSTROUTING"
  533. end
  534. end
  535. if value(:set_mark)
  536. unless value(:jump).to_s =~ /MARK/ &&
  537. value(:chain).to_s =~ /PREROUTING|OUTPUT/ &&
  538. value(:table).to_s =~ /mangle/
  539. self.fail "Parameter set_mark only applies to " \
  540. "the PREROUTING or OUTPUT chain of the mangle table and when jump => MARK"
  541. end
  542. end
  543. if value(:dport)
  544. unless value(:proto).to_s =~ /tcp|udp|sctp/
  545. self.fail "[%s] Parameter dport only applies to sctp, tcp and udp " \
  546. "protocols. Current protocol is [%s] and dport is [%s]" %
  547. [value(:name), should(:proto), should(:dport)]
  548. end
  549. end
  550. if value(:jump).to_s == "DNAT"
  551. unless value(:table).to_s =~ /nat/
  552. self.fail "Parameter jump => DNAT only applies to table => nat"
  553. end
  554. unless value(:todest)
  555. self.fail "Parameter jump => DNAT must have todest parameter"
  556. end
  557. end
  558. if value(:jump).to_s == "SNAT"
  559. unless value(:table).to_s =~ /nat/
  560. self.fail "Parameter jump => SNAT only applies to table => nat"
  561. end
  562. unless value(:tosource)
  563. self.fail "Parameter jump => DNAT must have tosource parameter"
  564. end
  565. end
  566. if value(:jump).to_s == "REDIRECT"
  567. unless value(:toports)
  568. self.fail "Parameter jump => REDIRECT missing mandatory toports " \
  569. "parameter"
  570. end
  571. end
  572. if value(:jump).to_s == "MASQUERADE"
  573. unless value(:table).to_s =~ /nat/
  574. self.fail "Parameter jump => MASQUERADE only applies to table => nat"
  575. end
  576. end
  577. if value(:log_prefix) || value(:log_level)
  578. unless value(:jump).to_s == "LOG"
  579. self.fail "Parameter log_prefix and log_level require jump => LOG"
  580. end
  581. end
  582. if value(:burst) && ! value(:limit)
  583. self.fail "burst makes no sense without limit"
  584. end
  585. if value(:action) && value(:jump)
  586. self.fail "Only one of the parameters 'action' and 'jump' can be set"
  587. end
  588. end
  589. end