mirror of
https://github.com/inspec/inspec
synced 2024-11-26 14:40:26 +00:00
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:
parent
e2b528db20
commit
19ab22f5e2
4 changed files with 382 additions and 15 deletions
122
docs/resources/nginx_conf.md.erb
Normal file
122
docs/resources/nginx_conf.md.erb
Normal 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
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue