Add wildcard/multiple server support to nginx_conf resource (#2141)

* Add wildcard/multiple server support to nginx_conf

Signed-off-by: Jerry Aldrich <jerryaldrichiii@gmail.com>

* separate the merge function for maps in nginx_conf

Signed-off-by: Dominik Richter <dominik.richter@gmail.com>
This commit is contained in:
Jerry Aldrich III 2017-09-15 15:37:57 -05:00 committed by Adam Leff
parent 48b0e6a667
commit 9773e1cd94
10 changed files with 146 additions and 22 deletions

View file

@ -3,6 +3,7 @@
# author: Christoph Hartmann
require 'utils/nginx_parser'
require 'utils/find_files'
require 'forwardable'
# STABILITY: Experimental
@ -25,6 +26,8 @@ module Inspec::Resources
extend Forwardable
include FindFiles
attr_reader :contents
def initialize(conf_path = nil)
@ -93,13 +96,37 @@ module Inspec::Resources
if data.key?('include')
data.delete('include').flatten
.map { |x| File.expand_path(x, rel_path) }
.map { |x| find_files(x) }.flatten
.map { |path| parse_nginx(path) }
.map { |e| data.merge!(e) }
.each { |conf| merge_config!(data, conf) }
end
# Walk through the remaining hash fields to find more references
Hash[data.map { |k, v| [k, resolve_references(v, rel_path)] }]
end
# Deep merge fields from NginxConfig.parse.
# A regular merge would overwrite values so a deep merge is needed.
# @param data [Hash] data structure from NginxConfig.parse
# @param conf [Hash] data structure to be deep merged into data
# @return [Hash] data structure with conf and data deep merged
def merge_config!(data, conf)
# Catch edge-cases
return if data.nil? || conf.nil?
# Step through all conf items and create combined return value
data.merge!(conf) do |_, v1, v2|
if v1.is_a?(Array) && v2.is_a?(Array)
# If both the data field and the conf field are arrays, then combine them
v1 + v2
elsif v1.is_a?(Hash) && v2.is_a?(Hash)
# If both the data field and the conf field are maps, then deep merge them
merge_config!(v1, v2)
else
# All other cases, just use the new value (regular merge behavior)
v2
end
end
end
end
class NginxConfHttp

View file

@ -139,6 +139,8 @@ class MockLoader
'/etc/nginx/nginx.conf' => mockfile.call('nginx.conf'),
'/etc/nginx/proxy.conf' => mockfile.call('nginx_proxy.conf'),
'/etc/nginx/conf/mime.types' => mockfile.call('nginx_mime.types'),
'/etc/nginx/conf.d/foobar.conf' => mockfile.call('nginx_confd_foobar.conf'),
'/etc/nginx/conf.d/multiple.conf' => mockfile.call('nginx_confd_multiple.conf'),
'/etc/xinetd.conf' => mockfile.call('xinetd.conf'),
'/etc/xinetd.d' => mockfile.call('xinetd.d'),
'/etc/xinetd.d/chargen-stream' => mockfile.call('xinetd.d_chargen-stream'),
@ -297,6 +299,10 @@ class MockLoader
'find /etc/httpd/conf-enabled/*.conf -type l -maxdepth 1' => cmd.call('find-httpd-conf-enabled-link'),
'find /etc/apache2/conf-enabled/*.conf -type f -maxdepth 1' => cmd.call('find-apache2-conf-enabled'),
'find /etc/apache2/conf-enabled/*.conf -type l -maxdepth 1' => cmd.call('find-apache2-conf-enabled-link'),
'find /etc/nginx/nginx.conf' => cmd.call('find-nginx-conf'),
'find /etc/nginx/conf/mime.types' => cmd.call('find-nginx-mime-types'),
'find /etc/nginx/proxy.conf' => cmd.call('find-nginx-proxy-conf'),
'find /etc/nginx/conf.d/*.conf' => cmd.call('find-nginx-confd-multiple-conf'),
# mount
"mount | grep -- ' on /'" => cmd.call("mount"),
"mount | grep -- ' on /mnt/iso-disk'" => cmd.call("mount-multiple"),

View file

@ -0,0 +1 @@
/etc/nginx/nginx.conf

View file

@ -0,0 +1,2 @@
/etc/nginx/conf.d/foobar.conf
/etc/nginx/conf.d/multiple.conf

View file

@ -0,0 +1 @@
/etc/nginx/conf/mime.types

View file

@ -0,0 +1 @@
/etc/nginx/proxy.conf

View file

@ -8,6 +8,7 @@ events { # events test
http {
include conf/mime.types; # relative path
include /etc/nginx/proxy.conf; # absolute path
include /etc/nginx/conf.d/*.conf; # wildcard path
index index.html index.htm index.php;
default_type application/octet-stream; # parameter with '/'
@ -18,7 +19,7 @@ http {
# multi server tests
server {
listen 85;
listen 80;
server_name domain1.com www.domain1.com;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:1025;
@ -26,7 +27,7 @@ http {
}
server {
listen 80;
listen 443;
server_name domain2.com www.domain2.com;
# multiple locations test
location ~ ^/(images|javascript|js|css|flash|media|static)/ {

View file

@ -0,0 +1,7 @@
server {
listen 8081;
server_name foobar.com www.foobar.com;
location ~ ^/flash/ {
root /var/www/virtual/www.foobar.com/htdocs;
}
}

View file

@ -0,0 +1,15 @@
server {
listen 8083;
server_name example1.com www.example1.com;
location ~ ^/static/ {
root /var/www/virtual/www.example1.com/htdocs;
}
}
server {
listen 8084;
server_name example2.com www.example2.com;
location ~ ^/media/ {
root /var/www/virtual/www.example2.com/htdocs;
}
}

View file

@ -25,25 +25,73 @@ describe 'Inspec::Resources::NginxConf' do
it 'reads the nginx_conf with all referenced include calls' do
_(nginx_conf.params).must_be_kind_of Hash
_(nginx_conf.contents).must_be_kind_of Hash
_(nginx_conf.contents.keys).must_equal %w(/etc/nginx/nginx.conf /etc/nginx/conf/mime.types /etc/nginx/proxy.conf)
_(nginx_conf.contents.keys).must_equal %w(
/etc/nginx/nginx.conf
/etc/nginx/conf/mime.types
/etc/nginx/proxy.conf
/etc/nginx/conf.d/foobar.conf
/etc/nginx/conf.d/multiple.conf
)
# global entries
_(nginx_conf.params['user']).must_equal [["www", "www"]] # multiple
_(nginx_conf.params['error_log']).must_equal [["logs/error.log"]] # with /
# verify user
_(nginx_conf.params['user']).must_equal [['www', 'www']] # multiple
# verify http, events, and servers
_(nginx_conf.params['events']).must_equal [{"worker_connections"=>[["4096"]]}]
# verify error_log
_(nginx_conf.params['error_log']).must_equal [['logs/error.log']] # with /
# verify events
_(nginx_conf.params['events']).must_equal [{'worker_connections'=>[['4096']]}]
# verify http
_(nginx_conf.params['http'].length).must_equal 1
_(nginx_conf.params['http'][0]['server'].length).must_equal 2
# verify server count
_(nginx_conf.params['http'][0]['server'].length).must_equal 5
# verify index
_(nginx_conf.params['http'][0]['index']).must_equal [['index.html', 'index.htm', 'index.php']]
# verify default_type (parameter with '/')
_(nginx_conf.params['http'][0]['default_type']).must_equal [['application/octet-stream']]
# verify relative include
# verify log_format (multi-line parameter)
_(nginx_conf.params['http'][0]['log_format']).must_equal [['main', 'multi', 'line']]
# verify types (relative include test)
_(nginx_conf.params['http'][0]['types']).must_equal [{'text/html'=>[['html', 'htm', 'shtml']]}]
# verify absolute include
# verify proxy_redirect (absolute include test)
_(nginx_conf.params['http'][0]['proxy_redirect']).must_equal [['off']]
# verify multiline
_(nginx_conf.params['http'][0]['log_format']).must_equal [['main', 'multi', 'line']]
# verify server in main nginx.conf
_(nginx_conf.params['http'][0]['server'][0]['listen']).must_equal [['80']]
_(nginx_conf.params['http'][0]['server'][0]['server_name']).must_equal [['domain1.com', 'www.domain1.com']]
_(nginx_conf.params['http'][0]['server'][0]['location'][0]['_']).must_equal ["~", "\\.php$"]
_(nginx_conf.params['http'][0]['server'][0]['location'][0]['fastcgi_pass']).must_equal [["127.0.0.1:1025"]]
# verify another server in main nginx.conf (multi-server and multi-location test)
_(nginx_conf.params['http'][0]['server'][1]['listen']).must_equal [['443']]
_(nginx_conf.params['http'][0]['server'][1]['server_name']).must_equal [['domain2.com', 'www.domain2.com']]
_(nginx_conf.params['http'][0]['server'][1]['location'][0]['_']).must_equal ['~', '^/(images|javascript|js|css|flash|media|static)/']
_(nginx_conf.params['http'][0]['server'][1]['location'][0]['root']).must_equal [['/var/www/virtual/big.server.com/htdocs']]
_(nginx_conf.params['http'][0]['server'][1]['location'][1]['_']).must_equal ['/']
_(nginx_conf.params['http'][0]['server'][1]['location'][1]['proxy_pass']).must_equal [['http://127.0.0.1:8080']]
# verify a server in conf.d (wildcard include test)
_(nginx_conf.params['http'][0]['server'][2]['listen']).must_equal [['8081']]
_(nginx_conf.params['http'][0]['server'][2]['server_name']).must_equal [['foobar.com', 'www.foobar.com']]
_(nginx_conf.params['http'][0]['server'][2]['location'][0]['_']).must_equal ['~', '^/flash/']
_(nginx_conf.params['http'][0]['server'][2]['location'][0]['root']).must_equal [['/var/www/virtual/www.foobar.com/htdocs']]
# verify servers in conf.d files (wildcard include test)
_(nginx_conf.params['http'][0]['server'][3]['listen']).must_equal [['8083']]
_(nginx_conf.params['http'][0]['server'][3]['server_name']).must_equal [['example1.com', 'www.example1.com']]
_(nginx_conf.params['http'][0]['server'][3]['location'][0]['_']).must_equal ['~', '^/static/']
_(nginx_conf.params['http'][0]['server'][3]['location'][0]['root']).must_equal [['/var/www/virtual/www.example1.com/htdocs']]
_(nginx_conf.params['http'][0]['server'][4]['listen']).must_equal [['8084']]
_(nginx_conf.params['http'][0]['server'][4]['server_name']).must_equal [['example2.com', 'www.example2.com']]
_(nginx_conf.params['http'][0]['server'][4]['location'][0]['_']).must_equal ['~', '^/media/']
_(nginx_conf.params['http'][0]['server'][4]['location'][0]['root']).must_equal [['/var/www/virtual/www.example2.com/htdocs']]
end
it 'skips the resource if it cannot parse the config' do
@ -67,18 +115,25 @@ describe 'Inspec::Resources::NginxConf' do
_(http.entries).must_be_kind_of Array
_(http.entries.length).must_equal 1
_(http.entries[0]).must_be_kind_of Inspec::Resources::NginxConfHttpEntry
http.entries.each do |entry|
_(entry).must_be_kind_of Inspec::Resources::NginxConfHttpEntry
end
end
it 'provides aggregated access to all servers' do
_(http.servers).must_be_kind_of Array
_(http.servers.length).must_equal 2
_(http.servers[0]).must_be_kind_of Inspec::Resources::NginxConfServer
_(http.servers.length).must_equal 5
http.servers.each do |server|
_(server).must_be_kind_of Inspec::Resources::NginxConfServer
end
end
it 'provides aggregated access to all locations' do
_(http.locations).must_be_kind_of Array
_(http.locations.length).must_equal 3
_(http.locations[0]).must_be_kind_of Inspec::Resources::NginxConfLocation
_(http.locations.length).must_equal 6
http.locations.each do |location|
_(location).must_be_kind_of Inspec::Resources::NginxConfLocation
end
end
it 'doesnt fail on params == nil' do
@ -98,14 +153,20 @@ describe 'Inspec::Resources::NginxConf' do
it 'provides aggregated access to all servers' do
_(entry.servers).must_be_kind_of Array
_(entry.servers.length).must_equal 2
_(entry.servers.length).must_equal 5
_(entry.servers[0]).must_be_kind_of Inspec::Resources::NginxConfServer
entry.servers.each do |server|
_(server).must_be_kind_of Inspec::Resources::NginxConfServer
end
end
it 'provides aggregated access to all locations' do
_(entry.locations).must_be_kind_of Array
_(entry.locations.length).must_equal 3
_(entry.locations.length).must_equal 6
_(entry.locations[0]).must_be_kind_of Inspec::Resources::NginxConfLocation
entry.locations.each do |location|
_(location).must_be_kind_of Inspec::Resources::NginxConfLocation
end
end
it 'doesnt fail on params == nil' do
@ -136,7 +197,7 @@ describe 'Inspec::Resources::NginxConf' do
let(:entry) { nginx_conf.servers[0] }
it 'pretty-prints in CLI' do
_(entry.inspect).must_equal 'nginx_conf /etc/nginx/nginx.conf, server domain1.com:85'
_(entry.inspect).must_equal 'nginx_conf /etc/nginx/nginx.conf, server domain1.com:80'
end
it 'provides access to all its parameters' do
@ -150,7 +211,9 @@ describe 'Inspec::Resources::NginxConf' do
it 'provides access to all its locations' do
_(entry.locations).must_be_kind_of Array
_(entry.locations.length).must_equal 1
_(entry.locations[0]).must_be_kind_of Inspec::Resources::NginxConfLocation
entry.locations.each do |location|
_(location).must_be_kind_of Inspec::Resources::NginxConfLocation
end
end
it 'doesnt fail on params == nil' do