add nginx_conf accessors for http, servers, and locations (#2119)

* wip: extend nginx_conf for http+servers+locations

... well `http` entries really, but we couldnt just call it `https`.

the goal is to `nginx_conf.http` / `nginx_conf.servers` / `nginx_conf.locations` and then also have these calls cascaded down to simplify the access to these fields. the current pattern is rather tedious since we need to check for nil everywhere.

* add test for new nginx accessors

Signed-off-by: Dominik Richter <dominik.richter@gmail.com>

* add docs for nginx-conf

Signed-off-by: Dominik Richter <dominik.richter@gmail.com>

* fix all incorrect NGINX spellings in docs

* prevent edge-cases where nginx params are nil

for location, http, and servers

Signed-off-by: Dominik Richter <dominik.richter@gmail.com>

* more descriptive to_s for nginx servers

as suggested by @adamleff, thank you!

Signed-off-by: Dominik Richter <dominik.richter@gmail.com>

* add more descriptive to_s for nginx location

Signed-off-by: Dominik Richter <dominik.richter@gmail.com>
This commit is contained in:
Dominik Richter 2017-09-06 14:19:04 +02:00 committed by Adam Leff
parent e2b528db20
commit 19ab22f5e2
4 changed files with 382 additions and 15 deletions

View file

@ -0,0 +1,122 @@
---
title: About the nginx_conf Resource
---
# nginx_conf
Use the `nginx_conf` InSpec resource to test configuration data for the NGINX server located at `/etc/nginx/nginx.conf` on Linux and Unix platforms.
**Stability: Experimental**
## Syntax
An `nginx_conf` resource block declares the client NGINX configuration data to be tested:
describe nginx_conf.params['pid'] do
it { should cmp 'logs/nginx.pid' }
end
where
* `nginx_conf` is the resource to reference your NGINX configuration
* `params` accesses all its parameters
* `params['pid']` selects the `pid` entry from the global NGINX configuration
* `{ should cmp 'logs/nginx.pid' }` tests if the PID is set to `logs/nginx.pid` (via `cmp` matcher)
## Matchers
This InSpec audit resource has the following matchers:
### http
Retrieves all `http` entries in the configuration file.
nginx_conf.http
=> nginx_conf /etc/nginx/nginx.conf, http entries
It provides further access to all individual entries, servers, and locations.
nginx_conf.http.entries
=> [nginx_conf /etc/nginx/nginx.conf, http entry ...]
nginx_conf.http.servers
=> [nginx_conf /etc/nginx/nginx.conf, server entry ...]
nginx_conf.http.locations
=> [nginx_conf /etc/nginx/nginx.conf, location entry ...]
You can access each of these from the array and inspect it further (see below).
### servers
Retrieve all `servers` entries in the configuration:
# all servers across all configs aggregated:
nginx_conf.servers
=> [nginx_conf /etc/nginx/nginx.conf, server entry ...]
# servers that belong to a specific http entry:
nginx_conf.http.entries[0].servers
=> [nginx_conf /etc/nginx/nginx.conf, server entry ...]
Servers provide access to all their locations, parent http entry, and raw parameters:
server = nginx_conf.servers[0]
server.locations
=> [nginx_conf /etc/nginx/nginx.conf, location entry ...]
server.parent
=> nginx_conf /etc/nginx/nginx.conf, http entry
server.params
=> {"listen"=>[["85"]],
"server_name"=>[["domain1.com", "www.domain1.com"]],
"root"=>[["html"]],
"location"=>[{"_"=>["~", "\\.php$"], "fastcgi_pass"=>[["127.0.0.1:1025"]]}]}
### locations
Retrieve all `location` entries in the configuration:
# all locations across all configs aggregated:
nginx_conf.locations
=> [nginx_conf /etc/nginx/nginx.conf, location entry ...]
# locations of a http entry aggregated:
nginx_conf.http.entries[0].locations
=> [nginx_conf /etc/nginx/nginx.conf, location entry ...]
# locations of a specific server:
nginx_conf.servers[0].locations
=> [nginx_conf /etc/nginx/nginx.conf, location entry ...]
Locations provide access to their parent server entry and raw parameters:
location = nginx_conf.locations[0]
location.parent
=> nginx_conf /etc/nginx/nginx.conf, server entry
location.params
=> {"_"=>["~", "\\.php$"], "fastcgi_pass"=>[["127.0.0.1:1025"]]}
## Examples
The following examples show how to use this InSpec audit resource.
### Find a specific server
servers = nginx_conf.servers
domain2 = servers.find { |s| s.params['server_name'].flatten.include? 'domain2.com' }
describe 'No server serves domain2' do
subject { domain2 }
it { should be_nil }
end
### Test a raw parameter
describe nginx_conf.params['worker_processes'].flatten do
it { should cmp 5 }
end

View file

@ -67,7 +67,7 @@ The `version` matcher tests if the named package version is on the system:
The following examples show how to use this InSpec audit resource.
### Test if nginx version 1.9.5 is installed
### Test if NGINX version 1.9.5 is installed
describe package('nginx') do
it { should be_installed }

View file

@ -3,6 +3,7 @@
# author: Christoph Hartmann
require 'utils/nginx_parser'
require 'forwardable'
# STABILITY: Experimental
# This resouce needs a proper interace to the underlying data, which is currently missing.
@ -22,6 +23,8 @@ module Inspec::Resources
describe nginx_conf('/path/to/my/nginx.conf').params ...
"
extend Forwardable
attr_reader :contents
def initialize(conf_path = nil)
@ -37,6 +40,12 @@ module Inspec::Resources
@params = {}
end
def http
NginxConfHttp.new(params['http'], self)
end
def_delegators :http, :servers, :locations
def to_s
"nginx_conf #{@conf_path}"
end
@ -92,4 +101,101 @@ module Inspec::Resources
Hash[data.map { |k, v| [k, resolve_references(v, rel_path)] }]
end
end
class NginxConfHttp
attr_reader :entries
def initialize(params, parent)
@parent = parent
@entries = (params || []).map { |x| NginxConfHttpEntry.new(x, parent) }
end
def servers
@entries.map(&:servers).flatten
end
def locations
servers.map(&:locations).flatten
end
def to_s
@parent.to_s + ', http entries'
end
alias inspect to_s
end
class NginxConfHttpEntry
attr_reader :params, :parent
def initialize(params, parent)
@params = params || {}
@parent = parent
end
filter = FilterTable.create
filter.add_accessor(:where)
.add(:servers, field: 'server')
.connect(self, :server_table)
def locations
servers.map(&:locations).flatten
end
def to_s
@parent.to_s + ', http entry'
end
alias inspect to_s
private
def server_table
@server_table ||= (params['server'] || []).map { |x| { 'server' => NginxConfServer.new(x, self) } }
end
end
class NginxConfServer
attr_reader :params, :parent
def initialize(params, parent)
@parent = parent
@params = params || {}
end
filter = FilterTable.create
filter.add_accessor(:where)
.add(:locations, field: 'location')
.connect(self, :location_table)
def to_s
server = ''
name = Array(params['server_name']).flatten.first
unless name.nil?
server += name
listen = Array(params['listen']).flatten.first
server += ":#{listen}" unless listen.nil?
end
# go two levels up: 1. to the http entry and 2. to the root nginx conf
@parent.parent.to_s + ", server #{server}"
end
alias inspect to_s
private
def location_table
@location_table ||= (params['location'] || []).map { |x| { 'location' => NginxConfLocation.new(x, self) } }
end
end
class NginxConfLocation
attr_reader :params, :parent
def initialize(params, parent)
@parent = parent
@params = params || {}
end
def to_s
location = Array(params['_']).join(' ')
# go three levels up: 1. to the server entry, 2. http entry and 3. to the root nginx conf
@parent.parent.parent.to_s + ", location #{location.inspect}"
end
alias inspect to_s
end
end

View file

@ -10,30 +10,40 @@ describe 'Inspec::Resources::NginxConf' do
# nginx_conf toplevel comment.
next if Gem.win_platform?
it 'reads the nginx_conf with all referenced include calls' do
resource = MockLoader.new(:ubuntu1404).load_resource('nginx_conf')
let(:nginx_conf) { MockLoader.new(:ubuntu1404).load_resource('nginx_conf') }
_(resource.params).must_be_kind_of Hash
_(resource.contents).must_be_kind_of Hash
_(resource.contents.keys).must_equal ["/etc/nginx/nginx.conf", "/etc/nginx/conf/mime.types", "/etc/nginx/proxy.conf"]
it 'doesnt fail with a missing file' do
nginx_conf = MockLoader.new(:ubuntu1404).load_resource('nginx_conf', '/....missing_file')
_(nginx_conf.params).must_equal({})
end
it 'doesnt fail with an incorrect file' do
nginx_conf = MockLoader.new(:ubuntu1404).load_resource('nginx_conf', '/etc/passwd')
_(nginx_conf.params).must_equal({})
end
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)
# global entries
_(resource.params['user']).must_equal [["www", "www"]] # multiple
_(resource.params['error_log']).must_equal [["logs/error.log"]] # with /
_(nginx_conf.params['user']).must_equal [["www", "www"]] # multiple
_(nginx_conf.params['error_log']).must_equal [["logs/error.log"]] # with /
# verify http, events, and servers
_(resource.params['events']).must_equal [{"worker_connections"=>[["4096"]]}]
_(resource.params['http'].length).must_equal 1
_(resource.params['http'][0]['server'].length).must_equal 2
_(resource.params['http'][0]['default_type']).must_equal [['application/octet-stream']]
_(nginx_conf.params['events']).must_equal [{"worker_connections"=>[["4096"]]}]
_(nginx_conf.params['http'].length).must_equal 1
_(nginx_conf.params['http'][0]['server'].length).must_equal 2
_(nginx_conf.params['http'][0]['default_type']).must_equal [['application/octet-stream']]
# verify relative include
_(resource.params['http'][0]['types']).must_equal [{'text/html'=>[['html', 'htm', 'shtml']]}]
_(nginx_conf.params['http'][0]['types']).must_equal [{'text/html'=>[['html', 'htm', 'shtml']]}]
# verify absolute include
_(resource.params['http'][0]['proxy_redirect']).must_equal [['off']]
_(nginx_conf.params['http'][0]['proxy_redirect']).must_equal [['off']]
# verify multiline
_(resource.params['http'][0]['log_format']).must_equal [['main', 'multi', 'line']]
_(nginx_conf.params['http'][0]['log_format']).must_equal [['main', 'multi', 'line']]
end
it 'skips the resource if it cannot parse the config' do
@ -41,4 +51,133 @@ describe 'Inspec::Resources::NginxConf' do
_(resource.params).must_equal({})
_(resource.instance_variable_get(:@resource_skipped)).must_equal "Cannot parse NginX config in /etc/nginx/failed.conf."
end
describe '#http' do
let(:http) { nginx_conf.http }
it 'provides an accessor for all http entries' do
_(http).must_be_kind_of Inspec::Resources::NginxConfHttp
end
it 'pretty-prints in CLI' do
_(http.inspect).must_equal 'nginx_conf /etc/nginx/nginx.conf, http entries'
end
it 'provides accessors to individual http entries' do
_(http.entries).must_be_kind_of Array
_(http.entries.length).must_equal 1
_(http.entries[0]).must_be_kind_of Inspec::Resources::NginxConfHttpEntry
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
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
end
it 'doesnt fail on params == nil' do
entry = Inspec::Resources::NginxConfHttp.new(nil, nil)
_(entry.entries).must_equal([])
_(entry.servers).must_equal([])
_(entry.locations).must_equal([])
end
end
describe 'NginxConfHttpEntry' do
let(:entry) { nginx_conf.http.entries[0] }
it 'pretty-prints in CLI' do
_(entry.inspect).must_equal 'nginx_conf /etc/nginx/nginx.conf, http entry'
end
it 'provides aggregated access to all servers' do
_(entry.servers).must_be_kind_of Array
_(entry.servers.length).must_equal 2
_(entry.servers[0]).must_be_kind_of Inspec::Resources::NginxConfServer
end
it 'provides aggregated access to all locations' do
_(entry.locations).must_be_kind_of Array
_(entry.locations.length).must_equal 3
_(entry.locations[0]).must_be_kind_of Inspec::Resources::NginxConfLocation
end
it 'doesnt fail on params == nil' do
entry = Inspec::Resources::NginxConfHttpEntry.new(nil, nil)
_(entry.params).must_equal({})
_(entry.servers).must_equal([])
_(entry.locations).must_equal([])
end
end
describe '#servers' do
let(:servers) { nginx_conf.servers }
it 'forwards access to #http.servers' do
_(servers.map(&:params)).must_equal nginx_conf.http.servers.map(&:params)
end
end
describe '#locations' do
let(:locations) { nginx_conf.locations }
it 'forwards access to #http.locations' do
_(locations.map(&:params)).must_equal nginx_conf.http.locations.map(&:params)
end
end
describe 'NginxConfServer' 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'
end
it 'provides access to all its parameters' do
_(entry.params).must_equal nginx_conf.params['http'][0]['server'][0]
end
it 'provides access to its parent' do
_(entry.parent.params).must_equal nginx_conf.http.entries[0].params
end
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
end
it 'doesnt fail on params == nil' do
entry = Inspec::Resources::NginxConfServer.new(nil, nil)
_(entry.params).must_equal({})
_(entry.locations).must_equal([])
end
end
describe 'NginxConfLocation' do
let(:entry) { nginx_conf.locations[0] }
it 'pretty-prints in CLI' do
_(entry.inspect).must_equal 'nginx_conf /etc/nginx/nginx.conf, location "~ \\\\.php$"'
end
it 'provides access to all its parameters' do
_(entry.params).must_equal nginx_conf.params['http'][0]['server'][0]['location'][0]
end
it 'provides access to its parent' do
_(entry.parent.params).must_equal nginx_conf.servers[0].params
end
it 'doesnt fail on params == nil' do
entry = Inspec::Resources::NginxConfLocation.new(nil, nil)
_(entry.params).must_equal({})
end
end
end