From 8a69984a21a7c7e05aa3a0b86d4cec2b2686554c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCnch?= Date: Tue, 12 Jul 2016 15:10:19 +0200 Subject: [PATCH 1/4] update travis file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrick Münch --- .travis.yml | 11 ++- controls/postgres_spec.rb | 35 ++++++++ default/puppet/Modulefile | 11 --- default/puppet/Puppetfile | 7 -- default/puppet/manifests/site.pp | 9 -- default/roles/postgresql.json | 21 ----- default/serverspec/postgresql_spec.rb | 115 -------------------------- default/serverspec/spec_helper.rb | 63 -------------- inspec.yml | 10 +++ 9 files changed, 53 insertions(+), 229 deletions(-) create mode 100644 controls/postgres_spec.rb delete mode 100644 default/puppet/Modulefile delete mode 100644 default/puppet/Puppetfile delete mode 100644 default/puppet/manifests/site.pp delete mode 100644 default/roles/postgresql.json delete mode 100644 default/serverspec/postgresql_spec.rb delete mode 100644 default/serverspec/spec_helper.rb create mode 100644 inspec.yml diff --git a/.travis.yml b/.travis.yml index 2c6aced..1da179c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,10 @@ +--- +language: ruby +cache: bundler rvm: - 1.9.3 - - 2.0.0 -language: ruby -script: bundle exec rake run_all_linters \ No newline at end of file + - 2.0 + - 2.2 + +bundler_args: --without integration +script: bundle exec rake diff --git a/controls/postgres_spec.rb b/controls/postgres_spec.rb new file mode 100644 index 0000000..b4c5482 --- /dev/null +++ b/controls/postgres_spec.rb @@ -0,0 +1,35 @@ +# encoding: utf-8 +# +# Copyright 2016, Patrick Muench +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# author: Christoph Hartmann +# author: Dominik Richter +# author: Patrick Muench + +title 'Postgres server config' + +only_if do + command(postgres.service).exist? +end + +control 'apache-01' do + impact 1.0 + title 'Apache should be running' + desc 'Apache should be running.' + describe service(postgres.service) do + it { should be_installed } + it { should be_running } + end +end diff --git a/default/puppet/Modulefile b/default/puppet/Modulefile deleted file mode 100644 index 3251fbb..0000000 --- a/default/puppet/Modulefile +++ /dev/null @@ -1,11 +0,0 @@ -name 'hardening/postgres_hardening' -version '0.1.0' -source 'https://github.com/TelekomLabs/puppet-postgres-hardening' -author 'Edmund Haselwanter' -license 'Apache License, Version 2.0' -summary 'Configures Postgres for security hardening' -description 'Configures Postgres for security hardening' -project_page 'https://github.com/TelekomLabs/puppet-postgres-hardening' - -dependency 'hardening/hardening_stdlib', '>=0.0.0 <1.0.0' -dependency 'puppetlabs/postgresql' \ No newline at end of file diff --git a/default/puppet/Puppetfile b/default/puppet/Puppetfile deleted file mode 100644 index fbf1b9c..0000000 --- a/default/puppet/Puppetfile +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env ruby -#^syntax detection - -forge "http://forge.puppetlabs.com" - -# use dependencies defined in Modulefile -modulefile \ No newline at end of file diff --git a/default/puppet/manifests/site.pp b/default/puppet/manifests/site.pp deleted file mode 100644 index e9814d2..0000000 --- a/default/puppet/manifests/site.pp +++ /dev/null @@ -1,9 +0,0 @@ -# Configure Postgresql Server as you normally would: - -class { '::postgresql::server': - postgres_password => 'iloverandompasswordsbutthiswilldo', -} - -class { '::postgres_hardening': - provider => 'puppetlabs/postgresql' -} diff --git a/default/roles/postgresql.json b/default/roles/postgresql.json deleted file mode 100644 index 5ee0e3d..0000000 --- a/default/roles/postgresql.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "postgresql", - "default_attributes": { }, - "override_attributes": { }, - "json_class": "Chef::Role", - "description": "Postgresql Hardened Server Test Role", - "chef_type": "role", - "default_attributes" : { - "postgresql": { - "password": { - "postgres" : "iloverandompasswordsbutthiswilldo" - } - } - }, - "run_list": [ - "recipe[chef-solo-search]", - "recipe[apt]", - "recipe[postgresql::server]", - "recipe[postgres-hardening]" - ] -} diff --git a/default/serverspec/postgresql_spec.rb b/default/serverspec/postgresql_spec.rb deleted file mode 100644 index 86c6cfb..0000000 --- a/default/serverspec/postgresql_spec.rb +++ /dev/null @@ -1,115 +0,0 @@ -# encoding: utf-8 - -require 'spec_helper' - -ENV['PGPASSWORD'] = 'iloverandompasswordsbutthiswilldo' - -RSpec::Matchers.define :match_key_value do |key, value| - match do |actual| - actual =~ /^\s*?#{key}\s*?=\s*?#{value}/ - end -end - -# set OS-dependent filenames and paths -case os[:family] -when 'ubuntu', 'debian' - postgres_home = '/var/lib/postgresql' - service_name = 'postgresql' - task_name = 'postgresql.conf' - user_name = 'postgres' - postgres_version = command('ls /etc/postgresql/').stdout.chomp - config_path = "/etc/postgresql/#{postgres_version}/main" - -else - postgres_home = '/var/lib/postgresql' - service_name = 'postgresql' - task_name = 'postmaster' - user_name = 'postgres' - config_path = '/var/lib/pgsql/data' -end - -describe service("#{service_name}") do - it { should be_enabled } - it { should be_running } -end - -hba_config_file = "#{config_path}/pg_hba.conf" -postgres_config_file = "#{config_path}/postgresql.conf" -psql_command = "sudo -u postgres -i PGPASSWORD='#{ENV['PGPASSWORD']}' psql" - -describe command('sudo -i psql -V') do - its(:stdout) { should_not match(/RC/) } - its(:stdout) { should_not match(/DEVEL/) } - its(:stdout) { should_not match(/BETA/) } -end - -describe command("ps aux | grep #{task_name} | grep -v grep | wc -l") do - its(:stdout) { should match(/^1/) } -end - -describe 'Checking Postgres-databases for risky entries' do - - describe command("#{psql_command} -d postgres -c \"SELECT count (*) FROM pg_language WHERE lanpltrusted = 'f' AND lanname!='internal' AND lanname!='c';\" | tail -n3 | head -n1 | tr -d ' '") do - its(:stdout) { should match(/^0/) } - end - - describe command("#{psql_command} -d postgres -c \"SELECT * FROM pg_shadow WHERE passwd IS NULL;\" | tail -n2 | head -n1 | cut -d '(' -f2 | cut -d ' ' -f1") do - its(:stdout) { should match(/^0/) } - end - - describe command("#{psql_command} -d psql -d postgres -c \"SELECT passwd FROM pg_shadow;\" | tail -n+3 | head -n-2 | grep -v \"md5\" -c") do - its(:stdout) { should match(/^0/) } - end - - describe command("#{psql_command} -d postgres -c \"SELECT rolname,rolsuper,rolcreaterole,rolcreatedb FROM pg_roles WHERE rolsuper IS TRUE OR rolcreaterole IS TRUE or rolcreatedb IS TRUE;\" | tail -n+3 | head -n-2 | wc -l") do - its(:stdout) { should match(/^1/) } - end - - describe command("#{psql_command} -d postgres -c \"\\dp pg_catalog.pg_authid\" | grep pg_catalog | wc -l") do - its(:stdout) { should match(/^1/) } - end -end - -describe 'Postgres FS-permissions' do - describe command("sudo find #{postgres_home} -user #{user_name} -group #{user_name} -perm /go=rwx | wc -l") do - its(:stdout) { should match(/^0/) } - end -end - -describe 'Parsing configfiles' do - - describe file(postgres_config_file) do - its(:content) { should match_key_value('ssl', 'off') } - its(:content) { should match_key_value('ssl_ciphers', "'ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH'") } - its(:content) { should match_key_value('password_encryption', 'on') } - end - - describe 'require MD5 for ALL users, peers in pg_hba.conf' do - describe file(hba_config_file) do - its(:content) { should match(/local\s.*?all\s.*?all\s.*?md5/) } - its(:content) { should match(%r{/host\s.*?all\s.*?all\s.*?127.0.0.1\/32\s.*?md5/}) } - its(:content) { should match(%r{/host\s.*?all\s.*?all\s.*?::1\/128\s.*?md5/}) } - end - - # We accept one peer and one ident for now (chef automation) - describe command("sudo -i cat #{hba_config_file} | egrep 'peer|ident' | wc -l") do - its(:stdout) { should match(/^[2|1]/) } - end - - describe command("sudo -i cat #{hba_config_file} | egrep 'trust|password|crypt' | wc -l") do - its(:stdout) { should match(/^0/) } - end - end - - describe 'System Monitoring' do - describe file(postgres_config_file) do - its(:content) { should match_key_value('logging_collector', 'on') } - its(:content) { should match(/log_directory\s.*?pg_log/) } # match pg_log and 'pg_log' - its(:content) { should match_key_value('log_connections', 'on') } - its(:content) { should match_key_value('log_disconnections', 'on') } - its(:content) { should match_key_value('log_duration', 'on') } - its(:content) { should match_key_value('log_hostname', 'on') } - its(:content) { should match_key_value('log_line_prefix', "'%t %u %d %h'") } - end - end -end diff --git a/default/serverspec/spec_helper.rb b/default/serverspec/spec_helper.rb deleted file mode 100644 index 78e701b..0000000 --- a/default/serverspec/spec_helper.rb +++ /dev/null @@ -1,63 +0,0 @@ -# encoding: utf-8 -# -# Copyright 2014, Deutsche Telekom AG -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -if ENV['STANDALONE_SPEC'] - - require 'serverspec' - require 'pathname' - require 'net/ssh' - require 'highline/import' - - set :backend, :ssh - - RSpec.configure do |c| - - if ENV['ASK_SUDO_PASSWORD'] - c.sudo_password = ask('Enter sudo password: ') { |q| q.echo = false } - else - c.sudo_password = ENV['SUDO_PASSWORD'] - end - - options = {} - - if ENV['ASK_LOGIN_PASSWORD'] - options[:password] = ask("\nEnter login password: ") { |q| q.echo = false } - else - options[:password] = ENV['LOGIN_PASSWORD'] - end - - if ENV['ASK_LOGIN_USERNAME'] - options[:user] = ask("\nEnter login username: ") { |q| q.echo = false } - else - options[:user] = ENV['LOGIN_USERNAME'] || ENV['user'] || Etc.getlogin - end - - if options[:user].nil? - puts 'specify login user env LOGIN_USERNAME= or user=' - exit 1 - end - - c.host = ENV['TARGET_HOST'] - c.ssh_options = options.merge(Net::SSH::Config.for(c.host)) - - end - -else - require 'serverspec' - - set :backend, :exec -end diff --git a/inspec.yml b/inspec.yml new file mode 100644 index 0000000..565fb0c --- /dev/null +++ b/inspec.yml @@ -0,0 +1,10 @@ +name: postgres-hardening +title: Hardening Framework Apache Hardening Test Suite +maintainer: Hardening Framework Team +copyright: Hardening Framework Team +copyright_email: hello@dev-sec.io +license: Apache 2 license +summary: Test-suite for best-practice postgres hardening +version: 0.1.0 +supports: +- os-family: unix From 0c6c56f88e299f6ed16ec18a4fc3d0d7d7161ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCnch?= Date: Tue, 12 Jul 2016 15:11:03 +0200 Subject: [PATCH 2/4] update gemfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrick Münch --- Gemfile | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 0d8654c..587005b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,18 @@ # encoding: utf-8 + source 'https://rubygems.org' +# pin dependency for Ruby 1.9.3 since bundler is not +# detecting that net-ssh 3 does not work with 1.9.3 +if Gem::Version.new(RUBY_VERSION) <= Gem::Version.new('1.9.3') + gem 'net-ssh', '~> 2.9' +end + gem 'rake' -gem 'serverspec', '~> 2.3.0' -gem 'rubocop', '~> 0.27' +gem 'inspec', '~> 0' +gem 'rubocop', '~> 0.36.0' gem 'highline', '~> 1.6.0' + +group :tools do + gem 'github_changelog_generator', '~> 1.12.0' +end From e6d17f20978b39171fbe2a2dba97ede3b1790636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCnch?= Date: Tue, 12 Jul 2016 15:11:20 +0200 Subject: [PATCH 3/4] update Rakefile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrick Münch --- Rakefile | 51 ++++++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/Rakefile b/Rakefile index 3bc2908..1a77717 100644 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,7 @@ +#!/usr/bin/env rake # encoding: utf-8 -require 'rake' -require 'rspec/core/rake_task' +require 'rake/testtask' require 'rubocop/rake_task' # Rubocop @@ -10,37 +10,30 @@ task :rubocop do RuboCop::RakeTask.new end -# Lint the cookbook -desc 'Run linters' -task :run_all_linters => [:rubocop] # rubocop:disable Style/HashSyntax -task :default => :run_all_linters # rubocop:disable Style/HashSyntax +# lint the project +desc 'Run robocop linter' +task lint: [:rubocop] -# Serverspec tests -suites = Dir.glob('*').select { |entry| File.directory?(entry) } +# run tests +task default: [:lint, 'test:check'] -class ServerspecTask < RSpec::Core::RakeTask - attr_accessor :target - - def spec_command - if target.nil? - puts 'specify either env TARGET_HOST or target_host=' - exit 1 - end - - cmd = super - "env TARGET_HOST=#{target} STANDALONE_SPEC=true #{cmd} --format documentation --no-profile" +namespace :test do + # run inspec check to verify that the profile is properly configured + task :check do + dir = File.join(File.dirname(__FILE__)) + sh("bundle exec inspec check #{dir}") end end -namespace :serverspec do - suites.each do |suite| - desc "Run serverspec suite #{suite}" - ServerspecTask.new(suite.to_sym) do |t| - t.rspec_opts = '--no-color --format html --out report.html' if ENV['format'] == 'html' - t.rspec_opts = '--no-color --format json --out report.json' if ENV['format'] == 'json' - t.target = ENV['TARGET_HOST'] || ENV['target_host'] - t.ruby_opts = "-I #{suite}/serverspec" - t.pattern = "#{suite}/serverspec/*_spec.rb" - end +# Automatically generate a changelog for this project. Only loaded if +# the necessary gem is installed. +# use `rake changelog to=1.2.0` +begin + v = ENV['to'] + require 'github_changelog_generator/task' + GitHubChangelogGenerator::RakeTask.new :changelog do |config| + config.future_release = v end +rescue LoadError + puts '>>>>> GitHub Changelog Generator not loaded, omitting tasks' end From df01edb8fcf283d5678b3a35f38d90f72943c670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20M=C3=BCnch?= Date: Tue, 12 Jul 2016 15:11:51 +0200 Subject: [PATCH 4/4] migrate from serverspec to inspec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrick Münch add postgres test 1 to 9 Signed-off-by: Patrick Münch add inspec attributes POSTGRES_CONF_DIR and POSTGRES_CONF_PATH, add test 10 to 12 Signed-off-by: Patrick Münch add test 12 Signed-off-by: Patrick Münch added new postgres test Signed-off-by: Patrick Münch add license Signed-off-by: Patrick Münch update gemfile, travis, rakefile Signed-off-by: Patrick Münch change Metrics/BlockLength in rubocop Signed-off-by: Patrick Münch change name and version bump Signed-off-by: Patrick Münch rename inspec attribute db_user to user and db_password to password Signed-off-by: Patrick Münch --- .rubocop.yml | 7 +- .travis.yml | 2 +- Gemfile | 13 +-- LICENSE | 175 +++++++++++++++++++++++++++ Rakefile | 9 +- controls/postgres_spec.rb | 240 +++++++++++++++++++++++++++++++++++++- inspec.yml | 6 +- 7 files changed, 427 insertions(+), 25 deletions(-) create mode 100644 LICENSE diff --git a/.rubocop.yml b/.rubocop.yml index 18a5eec..0b061d0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,12 +4,11 @@ AllCops: - vendor/**/* - "*/puppet/Puppetfile" - "*/puppet/.tmp/**/*" + TargetRubyVersion: 1.9 Documentation: Enabled: false AlignParameters: Enabled: true -Encoding: - Enabled: true HashSyntax: Enabled: true LineLength: @@ -20,9 +19,11 @@ MethodLength: Max: 40 NumericLiterals: MinDigits: 10 +Metrics/BlockLength: + Max: 42 Metrics/CyclomaticComplexity: Max: 10 Metrics/PerceivedComplexity: Max: 10 Metrics/AbcSize: - Max: 29 + Max: 30 diff --git a/.travis.yml b/.travis.yml index 1da179c..08dc2bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,9 @@ language: ruby cache: bundler rvm: - - 1.9.3 - 2.0 - 2.2 + - 2.3.1 bundler_args: --without integration script: bundle exec rake diff --git a/Gemfile b/Gemfile index 587005b..fc82503 100644 --- a/Gemfile +++ b/Gemfile @@ -1,16 +1,9 @@ -# encoding: utf-8 - source 'https://rubygems.org' -# pin dependency for Ruby 1.9.3 since bundler is not -# detecting that net-ssh 3 does not work with 1.9.3 -if Gem::Version.new(RUBY_VERSION) <= Gem::Version.new('1.9.3') - gem 'net-ssh', '~> 2.9' -end - gem 'rake' -gem 'inspec', '~> 0' -gem 'rubocop', '~> 0.36.0' +gem 'rack', '1.6.4' +gem 'inspec', '~> 1' +gem 'rubocop', '~> 0.44.0' gem 'highline', '~> 1.6.0' group :tools do diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..67db858 --- /dev/null +++ b/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/Rakefile b/Rakefile index 1a77717..ebf65df 100644 --- a/Rakefile +++ b/Rakefile @@ -26,10 +26,13 @@ namespace :test do end # Automatically generate a changelog for this project. Only loaded if -# the necessary gem is installed. -# use `rake changelog to=1.2.0` +# the necessary gem is installed. By default its picking up the version from +# inspec.yml. You can override that behavior with `rake changelog to=1.2.0` begin - v = ENV['to'] + require 'yaml' + metadata = YAML.load_file('inspec.yml') + v = ENV['to'] || metadata['version'] + puts "Generate changelog for version #{v}" require 'github_changelog_generator/task' GitHubChangelogGenerator::RakeTask.new :changelog do |config| config.future_release = v diff --git a/controls/postgres_spec.rb b/controls/postgres_spec.rb index b4c5482..f1f65d2 100644 --- a/controls/postgres_spec.rb +++ b/controls/postgres_spec.rb @@ -18,18 +18,248 @@ # author: Dominik Richter # author: Patrick Muench -title 'Postgres server config' +title 'PostgreSQL Server Configuration' + +# attributes + +USER = attribute( + 'user', + description: 'define the postgresql user to access the database', + default: 'postgres' +) + +PASSWORD = attribute( + 'password', + description: 'define the postgresql password to access the database', + default: 'iloverandompasswordsbutthiswilldo' +) + +POSTGRES_DATA = attribute( + 'postgres_data', + description: 'define the postgresql data directory', + default: postgres.data_dir +) + +POSTGRES_CONF_DIR = attribute( + 'postgres_conf_dir', + description: 'define the postgresql configuration directory', + default: postgres.conf_dir +) + +POSTGRES_CONF_PATH = attribute( + 'postgres_conf_path', + description: 'define path for the postgresql configuration file', + default: postgres.conf_path +) + +POSTGRES_HBA_CONF_FILE = attribute( + 'postgres_hba_conf_file', + description: 'define path for the postgresql configuration file', + default: postgres.conf_dir + '/pg_hba.conf' +) only_if do - command(postgres.service).exist? + command('psql').exist? end -control 'apache-01' do +control 'postgres-01' do impact 1.0 - title 'Apache should be running' - desc 'Apache should be running.' + title 'Postgresql should be running' + desc 'Postgresql should be running.' describe service(postgres.service) do it { should be_installed } it { should be_running } + it { should be_enabled } + end +end + +control 'postgres-02' do + impact 1.0 + title 'Use stable postgresql version' + desc 'Use only community or commercially supported version of the PostgreSQL software. Do not use RC, DEVEL oder BETA versions in a production environment.' + describe command('sudo -i psql -V') do + its('stdout') { should match(/9.[2-5]/) } + end + describe command('sudo -i psql -V') do + its('stdout') { should_not match(/RC/) } + its('stdout') { should_not match(/DEVEL/) } + its('stdout') { should_not match(/BETA/) } + end +end + +control 'postgres-03' do + impact 1.0 + title 'Run one postgresql instance per operating system' + desc 'Only one postgresql database instance must be running on an operating system instance (both physical HW or virtualized).' + describe command('ps aux | grep \'postgres -D\' | grep -v grep | wc -l') do + its('stdout') { should match(/^1/) } + end +end + +control 'postgres-04' do + impact 1.0 + title 'Only "c" and "internal" should be used as non-trusted procedural languages' + desc 'If additional programming languages (e.g. plperl) are installed with non-trust mode, then it is possible to gain OS-level access permissions.' + describe postgres_session(USER, PASSWORD).query('SELECT count (*) FROM pg_language WHERE lanpltrusted = \'f\' AND lanname!=\'internal\' AND lanname!=\'c\';') do + its('output') { should eq '0' } + end +end + +control 'postgres-05' do + impact 1.0 + title 'Set a password for each user' + desc 'It tests for usernames which does not set a password.' + describe postgres_session(USER, PASSWORD).query('SELECT count(*) FROM pg_shadow WHERE passwd IS NULL;') do + its('output') { should eq '0' } + end +end + +control 'postgres-06' do + impact 1.0 + title 'Use salted MD5 to store postgresql passwords' + desc 'Store postgresql passwords in salted hash format (e.g. salted MD5).' + describe postgres_session(USER, PASSWORD).query('SELECT passwd FROM pg_shadow;') do + its('output') { should match(/^md5\S*$/) } + end + describe postgres_conf(POSTGRES_CONF_PATH) do + its('password_encryption') { should eq 'on' } + end +end + +control 'postgres-07' do + impact 1.0 + title 'Only the postgresql database administrator should have SUPERUSER, CREATEDB or CREATEROLE privileges.' + desc 'Granting extensive privileges to ordinary users can cause various security problems, such as: intentional/ unintentional access, modification or destroying data' + describe postgres_session(USER, PASSWORD).query('SELECT count(*) FROM pg_roles WHERE rolsuper IS TRUE OR rolcreaterole IS TRUE or rolcreatedb IS TRUE;') do + its('output') { should eq '1' } + end +end + +control 'postgres-08' do + impact 1.0 + title 'Only the DBA should have privileges on pg_catalog.pg_authid table.' + desc 'In pg_catalog.pg_authid table there are stored credentials such as username and password. If hacker has access to the table, then he can extract these credentials.' + describe postgres_session(USER, PASSWORD).query('\dp pg_catalog.pg_authid') do + its('output') { should eq 'pg_catalog | pg_authid | table | postgres=arwdDxt/postgres |' } + end +end + +control 'postgres-09' do + impact 1.0 + title 'The PostgreSQL "data_directory" should be assigned exclusively to the database account (such as "postgres").' + desc 'If file permissions on data are not property defined, other users may read, modify or delete those files.' + find_command = 'find ' + POSTGRES_DATA + ' -user ' + USER + ' -group ' + USER + ' -perm /go=rwx' + describe command(find_command) do + its('stdout') { should eq '' } + end +end + +control 'postgres-10' do + impact 1.0 + title 'The PostgreSQL config directory and file should be assigned exclusively to the database account (such as "postgres").' + desc 'If file permissions on config files are not property defined, other users may read, modify or delete those files.' + describe file(POSTGRES_CONF_DIR) do + it { should be_directory } + it { should be_owned_by USER } + it { should be_readable.by('owner') } + it { should be_readable.by('group') } + it { should be_readable.by('other') } + it { should be_writable.by('owner') } + it { should_not be_writable.by('group') } + it { should_not be_writable.by('other') } + it { should be_executable.by('owner') } + it { should be_executable.by('group') } + it { should be_executable.by('other') } + end + describe file(POSTGRES_CONF_PATH) do + it { should be_file } + it { should be_owned_by USER } + it { should be_readable.by('owner') } + it { should_not be_readable.by('group') } + it { should_not be_readable.by('other') } + it { should be_writable.by('owner') } + it { should_not be_writable.by('group') } + it { should_not be_writable.by('other') } + it { should_not be_executable.by('owner') } + it { should_not be_executable.by('group') } + it { should_not be_executable.by('other') } + end + describe file(POSTGRES_HBA_CONF_FILE) do + it { should be_file } + it { should be_owned_by USER } + it { should be_readable.by('owner') } + it { should_not be_readable.by('group') } + it { should_not be_readable.by('other') } + it { should be_writable.by('owner') } + it { should_not be_writable.by('group') } + it { should_not be_writable.by('other') } + it { should_not be_executable.by('owner') } + it { should_not be_executable.by('group') } + it { should_not be_executable.by('other') } + end +end + +control 'postgres-11' do + impact 1.0 + title 'SSL is deactivated just for testing the chef-hardening-cookbook. It is recommended to activate ssl communication.' + desc 'The chef-hardening-cookbook will delete the links from #var/lib/postgresql/#{node[\'postgresql\'][\'version\']}/main/server.crt to etc/ssl/certs/ssl-cert-snakeoil.pem and #var/lib/postgresql/#{node[\'postgresql\'][\'version\']}/main/server.key to etc/ssl/private/ssl-cert-snakeoil.key on Debian systems. This certificates are self-signed (see http://en.wikipedia.org/wiki/Snake_oil_%28cryptography%29) and therefore not trusted. You have to #provide our own trusted certificates for SSL.' + describe postgres_conf(POSTGRES_CONF_PATH) do + its('ssl') { should eq 'off' } + end +end + +control 'postgres-12' do + impact 1.0 + title 'Use strong chiphers for ssl communication' + desc 'The following categories of SSL Ciphers must not be used: ADH, LOW, EXP and MD5. A very good description for secure postgres installation / configuration can be found at: https://bettercrypto.org' + describe postgres_conf(POSTGRES_CONF_PATH) do + its('ssl_ciphers') { should eq 'ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH' } + end +end + +control 'postgres-13' do + impact 1.0 + title 'Require MD5 for ALL users, peers in pg_hba.conf' + desc 'Require MD5 for ALL users, peers in pg_hba.conf and do not allow untrusted authentication methods.' + describe file(POSTGRES_HBA_CONF_FILE) do + its('content') { should match(/local\s.*?all\s.*?all\s.*?md5/) } + its('content') { should match(%r{host\s.*?all\s.*?all\s.*?127.0.0.1\/32\s.*?md5}) } + its('content') { should match(%r{host\s.*?all\s.*?all\s.*?::1\/128\s.*?md5}) } + its('content') { should_not match(/.*password/) } + its('content') { should_not match(/.*trust/) } + its('content') { should_not match(/.*crypt/) } + end +end + +control 'postgres-14' do + impact 1.0 + title 'We accept one peer and one ident for now (chef automation)' + desc 'We accept one peer and one ident for now (chef automation)' + describe command('sudo -i cat ' + POSTGRES_HBA_CONF_FILE + ' | egrep \'peer|ident\' | wc -l') do + its('stdout') { should match(/^[2|1]/) } + end +end + +control 'postgres-14' do + impact 1.0 + title 'We accept one peer and one ident for now (chef automation)' + desc 'We accept one peer and one ident for now (chef automation)' + describe command('sudo -i cat ' + POSTGRES_HBA_CONF_FILE + ' | egrep \'peer|ident\' | wc -l') do + its('stdout') { should match(/^[2|1]/) } + end +end + +control 'postgres-15' do + impact 1.0 + title 'Enable logging functions' + desc 'Logging functions must be turned on and properly configured according / compliant to local law.' + describe postgres_conf(POSTGRES_CONF_PATH) do + its('logging_collector') { should eq 'on' } + its('log_connections') { should eq 'on' } + its('log_disconnections') { should eq 'on' } + its('log_duration') { should eq 'on' } + its('log_hostname') { should eq 'on' } + its('log_directory') { should eq 'pg_log' } + its('log_line_prefix') { should eq '%t %u %d %h' } end end diff --git a/inspec.yml b/inspec.yml index 565fb0c..9902a94 100644 --- a/inspec.yml +++ b/inspec.yml @@ -1,10 +1,10 @@ -name: postgres-hardening -title: Hardening Framework Apache Hardening Test Suite +name: postgres-baseline +title: Hardening Framework Postgres Hardening Test Suite maintainer: Hardening Framework Team copyright: Hardening Framework Team copyright_email: hello@dev-sec.io license: Apache 2 license summary: Test-suite for best-practice postgres hardening -version: 0.1.0 +version: 2.0.0 supports: - os-family: unix