-
Notifications
You must be signed in to change notification settings - Fork 682
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
etc_hosts_allow and etc_hosts_deny resources: test the content of the tcpwrappers configuration files #2073
etc_hosts_allow and etc_hosts_deny resources: test the content of the tcpwrappers configuration files #2073
Conversation
Signed-off-by: dromazos <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @dromazmj I'm giving this a partial review. Given the feedback I've provided on the other 2 PRs, I'm going to stop here for now and allow you to apply the review feedback from those to all your pending PRs. Please ping me once complete and I'll take another look.
## Supported Properties | ||
|
||
'daemon_list', 'client_list', 'options' | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is under my impression that cmp only works with arrays of one value. So for this resource I think we should leave the matcher operator as eq because in most cases rules specify more than one daemon, client or option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'm alright with that for the tests that will return an array of values, but let's change to cmp for number and single-element comparisons please.
# author: Matthew Dromazos | ||
|
||
require 'utils/parser' | ||
require 'pry' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this line, please.
test/unit/mock/files/hosts.allow
Outdated
sshd: ALL | ||
# Added for testing | ||
LOCAL : [fe80::]/10 : deny | ||
UNKNOWN, sshd : 127.0.1.154, [:fff:fAb0::] : deny : /etc/bin/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure about this example? UNKNOWN
is a client keyword, not a daemon keyword. I was under the impression that you could only have a single daemon per line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right that UNKNOWN is for clients but there can be more than one daemon specified for a rule. Ill change the UNKNOWN so it is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I looked more into the documentation and you can use ALL, LOCAL, UNKNOWN, etc in the daemon field. So to give more variety of tests Im going to leave this as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Firstly, can you link to that documentation? It doesn't match the docs I read yesterday.
If that's the case, I think this requires some additional treatment to duplicate the entries in the FilterTable results. Think of the user experience... a user will likely want to write a test like "make sure 1.2.3.4 can access sshd". They shouldn't have to ask "make sure 1.2.3.4 can access sshd and unknown".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://www.centos.org/docs/rhel-rg-en-3/s1-tcpwrappers-access.html
And I see what you're saying about the user experience. maybe it would be better to use the include instead something like:
describe etc_hosts_allow.where (daemon_list include 'sshd') do
its ('client_list') { should include '192.168.0.1/24' }
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's an option, but it requires the user to understand the resource's technical implementation. It doesn't really help them answer the question.
Think about what tcpwrappers is for... it controls access to daemons. A daemon is the primary key. A user shouldn't have to worry about a line protecting multiple daemons -- they want to ask a question about a daemon.
I think the right solution is, while parsing the lines, if there are multiple daemons listed on a line, add multiple entries to the hash used by the FilterTable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, that doc says that the only keyword allowed in the daemon_list is "ALL", not "UNKNOWN"... so we should not use UNKNOWN as a daemon example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So just to make sure if you have a line such as:
sshd, vsftpd : '192.168.0.1/24'
then you want
sshd : '192.168.0.1/24'
vsftpd : '192.168.0.1/24'
in the hash
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's the right thing to do, yes.
end | ||
|
||
# If there is a file and it contains content, continue | ||
inspec.file(conf_path).content.lines |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-reading file again - should probably be raw_conf.lines
Signed-off-by: dromazos <[email protected]>
Signed-off-by: dromazos <[email protected]>
Signed-off-by: dromazos <[email protected]>
Signed-off-by: dromazos <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dromazmj thanks for this PR. I've left some feedback in terms of the implementation of the line parsing. I'm concerned about the complexity and future debuggability of those methods. I provided a potential alternate solution. Let's chat more about this if you have any questions.
# etc_hosts_allow | ||
|
||
Use the `etc_hosts_allow` InSpec audit resource to test rules set to accept daemon and client traffic set in /etc/hosts.allow file. | ||
## Syntax |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newline needed between lines 7 and 8.
docs/resources/etc_hosts_deny.md.erb
Outdated
# etc_hosts_deny | ||
|
||
Use the `etc_hosts_deny` InSpec audit resource to test rules set to reject daemon and client traffic set in /etc/hosts.deny. | ||
## Syntax |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newline needed between lines 7 and 8.
end.compact | ||
end | ||
|
||
def parse_line(line) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While this method, and the parse_attributes
method, are creative ways of solving the parsing problem, I think they may be too complicated and there's definitely a high maintenance burden if we have to debug it in the future.
I think this can be a lot simpler. I whipped this method up for some local testing and I think something similar to this would do the trick:
def parse(line)
daemons, clients_and_options = line.split(/\s+:\s+/, 2)
daemon_list = daemons.split(',').map(&:strip)
clients_and_options ||= ''
clients, options = clients_and_options.split(' : ', 2)
client_list = clients.split(',').map(&:strip)
options ||= ''
options_list = options.split(': ').map(&:strip)
puts "Daemons: #{daemon_list.inspect}"
puts "Clients: #{client_list.inspect}"
puts "Options: #{options_list.inspect}"
end
I'd also like to see some specific unit tests for the method that parses these lines given how complex they can be. The tests should focus on parsing a single line that you hardcode in your tests. The tests might look something like this:
describe 'Inspec::Resources::EtcHostsAllow' do
describe '#parse_line' do
it 'parses a line with multiple clients' do
line = 'sshd : 1.2.3.4, 4.3.2.1'
_(resource.parse_line(line)['daemons']).must_equal ['sshd']
_(resource.parse_line(line)['clients']).must_equal ['1.2.3.4', '4.3.2.1']
end
it 'parses a line with multiple daemons' do
# etc
end
it 'parses a line with one option' do
end
it 'parses a line with multiple options' do
end
end
end
Let me know if you have questions about this or if you think my suggestion doesn't properly parse these lines.
I will note that hosts.allow / hosts.deny files can contain entries that span multiple lines and can even reference other files for the client list, for example. We don't have to solve that problem here, but it's something to be aware of and why I want the line parsing methods to be as simple as can be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only issue I see with this is when you have an ipv6 address in the client list, because then you cannot split by ':'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It still works because the tcpwrappers spec states that there must be a space before and after the :
between the clients and any options.
[14] pry(main)> parse('vsftpd, sshd : 127.0.1.154, [:fff:fAb0::] : deny : /etc/bin/')
Daemons: ["vsftpd", "sshd"]
Clients: ["127.0.1.154", "[:fff:fAb0::]"]
Options: ["deny", "/etc/bin/"]
end | ||
end | ||
|
||
class EtcHostsDeny < EtcHostsAllow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great job doing subclassing. Thank you for helping keep our codebase only as large as it needs to be!
Signed-off-by: dromazmj <[email protected]>
options ||= '' | ||
options_list = options.split(': ').map(&:strip) | ||
|
||
puts "Daemons: #{daemon_list.inspect}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be removed. I provided them to you in order to provide output to show how my method worked but they should not be included in your proposed pull request.
{ | ||
'daemon' => daemon, | ||
'daemon' => daemons.strip, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the strip
needed?
@@ -23,6 +23,32 @@ | |||
_(entries.client_list).must_include ['127.0.1.154', '[:fff:fAb0::]'] | |||
end | |||
end | |||
|
|||
describe '#parse_line' do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't quite what I was suggesting, though it's fairly effective.
You can test individual methods, even if they are private (and we should make sure parse_line
is private, please!). For example:
it 'parses a line with multiple clients' do
line = 'foo: client1, client2 : some_option'
_(resource.send(:parse_line, line)).must_equal [ 'client1', 'client2 ' ]
end
This allows you test lots and lots of iterations of that method accepting different values by just focusing on that method - you don't exercise FilterTable, the mocked data, etc. Your test remains laser-focused. Given the importance of this one method, we should isolate a number of tests just on that method to ensure it works properly and catch any future regressions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay sounds good, Only test you suggested that wouldnt make sense is to test a line with multiple daemons because I split up lines with mulitiple daemons before that method is ever called.
Signed-off-by: dromazmj <[email protected]>
Signed-off-by: dromazmj <[email protected]>
@dromazmj Please fix merge conflicts and then I think this will be ready for final review. |
….rb, adding proper solution that tcp wrapper uses to parse the file. Signed-off-by: dromazmj <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dromazmj I think this is ready for final review but we need to rebase on master and cleanup resource.rb
and test/helper.rb
... your current commits have changes that are not part of the focus on this PR, likely from an attempt to resolve some merge conflicts.
test/helper.rb
Outdated
@@ -158,6 +158,14 @@ def md.directory? | |||
'C:/etc/postgresql/9.5/main/pg_ident.conf' => mockfile.call('pg_ident.conf'), | |||
'/etc/postgresql/9.5/main' => mockfile.call('9.5.main'), | |||
'/var/lib/postgresql/9.5/main' => mockfile.call('var.9.5.main'), | |||
'/etc/hosts.allow' => mockfile.call('hosts.allow'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a bunch of changes in test/helper.rb
that have nothing to do with this change. I think you need to rebase onto master. Feel free to squash your commits at this point as well since we're pretty much at final review.
lib/inspec/resource.rb
Outdated
@@ -89,6 +89,8 @@ def self.validate_resource_dsl_version!(version) | |||
require 'resources/docker_image' | |||
require 'resources/docker_container' | |||
require 'resources/etc_group' | |||
require 'resources/etc_hosts_allow_deny' | |||
require 'resources/etc_hosts' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Line 93 has nothing to do with this PR. I think this is likely from a bad merge conflict resolution. Please rebase on master and ensure your changes are only related to your PR focus.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, Im having a little trouble Ill need to talk to you or Aaron tomorrow if you have time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm traveling for work today and tomorrow, but I'd be happy to pair with you on Wednesday.
Signed-off-by: dromazos <[email protected]> Modifications to new resource - etc_hosts_allow_deny Signed-off-by: dromazos <[email protected]> Modifications to new resource - etc_hosts_allow_deny Signed-off-by: dromazos <[email protected]> Modification to new resource - etc_hosts_allow_deny Signed-off-by: dromazos <[email protected]> Modification to new resource - etc_hosts_allow_deny Signed-off-by: dromazos <[email protected]> Modifications to new resource - etc_hosts_allow_deny Signed-off-by: dromazmj <[email protected]> Modifications to new resource etc_hosts_allow_deny Signed-off-by: dromazmj <[email protected]> Modification to new resource - etc_hosts_allow_deny Signed-off-by: dromazmj <[email protected]> Resolving merge conflicts with test/helper.rb and lib/inspec/resource.rb, adding proper solution that tcp wrapper uses to parse the file. Signed-off-by: dromazmj <[email protected]>
…pold/inspec into al/etc_hosts_allow_deny
…osts_allow_deny Fixing merge conflicts with inspec/resource.rb and test/helper.rb Signed-off-by: dromazmj <[email protected]>
…al/etc_hosts_allow_deny Signed-off-by: dromazmj <[email protected]>
* Added loading the etc_hosts_allow_deny in lib/resource.rb * Added loading mock files for test in test/unit/resources/helper.rb Signed-off-by: dromazmj <[email protected]>
Signed-off-by: dromazmj <[email protected]>
Signed-off-by: dromazmj <[email protected]>
@adamleff Changes are made and it should be good for you. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good, @dromazmj. You have an unused variable in your #initialize
method that we should remove, but this is approved, otherwise. Please send up a commit removing that line. Thanks!
def initialize(hosts_allow_path = nil) | ||
return skip_resource 'The `etc_hosts_allow` resource is not supported on your OS.' unless inspec.os.linux? | ||
@conf_path = hosts_allow_path || '/etc/hosts.allow' | ||
@files_contents = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable - let's remove.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dromazmj as soon as you remove the unused variable, |
Signed-off-by: dromazmj <[email protected]>
@adamleff I removed the unused variable and pushed the commit |
Looks great! Going to merge this in now and it will be part of our next stable release. Thanks! |
Signed-off-by: dromazos [email protected]