diff --git a/.rubocop.yml b/.rubocop.yml index bdae9aa..a511077 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -8,3 +8,5 @@ Metrics/LineLength: Max: 88 # Any offenses that should be fixed, e.g. collected via. `rubocop --auto-gen-config` +Metrics/BlockLength: + Max: 36 diff --git a/.travis.yml b/.travis.yml index 4de667c..0891683 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,17 +21,14 @@ stages: - name: release if: branch = master AND type != pull_request jobs: - allow_failures: - - env: Lint_rubocop - fast_finish: true include: ## Define the test stage that runs the linters (and testing matrix, if applicable) - # Run all of the linters in a single job (except `rubocop`) + # Run all of the linters in a single job - language: node_js node_js: lts/* env: Lint - name: 'Lint: salt-lint, yamllint & commitlint' + name: 'Lint: salt-lint, yamllint, rubocop & commitlint' before_install: skip script: # Install and run `salt-lint` @@ -42,21 +39,13 @@ jobs: # Need at least `v1.17.0` for the `yaml-files` setting - pip install --user yamllint>=1.17.0 - yamllint -s . + # Install and run `rubocop` + - gem install rubocop + - rubocop -d # Install and run `commitlint` - npm i -D @commitlint/config-conventional @commitlint/travis-cli - commitlint-travis - # Run the `rubocop` linter in a separate job that is allowed to fail - # Once these lint errors are fixed, this can be merged into a single job - - language: node_js - node_js: lts/* - env: Lint_rubocop - name: 'Lint: rubocop' - before_install: skip - script: - # Install and run `rubocop` - - gem install rubocop - - rubocop -d ## Define the rest of the matrix based on Kitchen testing # Make sure the instances listed below match up with diff --git a/.yamllint b/.yamllint index 740beca..53a159e 100644 --- a/.yamllint +++ b/.yamllint @@ -12,6 +12,7 @@ ignore: | node_modules/ test/**/states/**/*.sls .kitchen/ + test/salt/pillar/default.sls yaml-files: # Default settings diff --git a/Gemfile b/Gemfile index 5a232b6..b609bb1 100644 --- a/Gemfile +++ b/Gemfile @@ -2,6 +2,12 @@ source 'https://rubygems.org' +gem 'inspec' gem 'kitchen-docker', '>= 2.9' gem 'kitchen-inspec', '>= 1.1' gem 'kitchen-salt', '>= 0.6.0' +gem 'rspec-retry' + +group :vagrant do + gem 'kitchen-vagrant' +end diff --git a/docs/README.rst b/docs/README.rst index 0b06ba4..eb0d55f 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -131,3 +131,65 @@ Runs all of the stages above in one go: i.e. ``destroy`` + ``converge`` + ``veri ^^^^^^^^^^^^^^^^^^^^^ Gives you SSH access to the instance for manual testing. + +Testing with Vagrant +-------------------- + +Windows testing is done with ``kitchen-salt``. + +Requirements +^^^^^^^^^^^^ + +* Ruby +* Virtualbox +* Vagrant + +Setup +^^^^^ + +.. code-block:: bash + + $ gem install bundler + $ bundle install --with=vagrant + $ bin/kitchen test [platform] + +Where ``[platform]`` is the platform name defined in ``kitchen.yml``, +e.g. ``windows-81-2019-2-py3``. + +Note +^^^^ + +When testing using Vagrant you must set the environment variable ``KITCHEN_LOCAL_YAML`` to ``kitchen.vagrant.yml``. For example: + +.. code-block:: bash + + $ KITCHEN_LOCAL_YAML=kitchen.vagrant.yml bin/kitchen test # Alternatively, + $ export KITCHEN_LOCAL_YAML=kitchen.vagrant.yml + $ bin/kitchen test + +Then run the following commands as needed. + +``bin/kitchen converge`` +^^^^^^^^^^^^^^^^^^^^^^^^ + +Creates the Vagrant instance and runs the ``openvpn`` main state, ready for testing. + +``bin/kitchen verify`` +^^^^^^^^^^^^^^^^^^^^^^ + +Runs the ``inspec`` tests on the actual instance. + +``bin/kitchen destroy`` +^^^^^^^^^^^^^^^^^^^^^^^ + +Removes the Vagrant instance. + +``bin/kitchen test`` +^^^^^^^^^^^^^^^^^^^^ + +Runs all of the stages above in one go: i.e. ``destroy`` + ``converge`` + ``verify`` + ``destroy``. + +``bin/kitchen login`` +^^^^^^^^^^^^^^^^^^^^^ + +Gives you RDP access to the instance for manual testing. diff --git a/kitchen.vagrant.yml b/kitchen.vagrant.yml new file mode 100644 index 0000000..7e3c897 --- /dev/null +++ b/kitchen.vagrant.yml @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# vim: ft=yaml +--- +driver: + name: vagrant + +platforms: + - name: windows-81-2019-2-py3 + driver: + box: techneg/win81x64-pro-salt + gui: false + linked_clone: true + provisioner: + init_environment: > + salt-call --local state.single file.managed + C:\Users\vagrant\AppData\Local\Temp\kitchen\srv\salt\win\repo-ng\openvpn.sls + source=https://github.com/saltstack/salt-winrepo-ng/raw/master/openvpn.sls + skip_verify=True makedirs=True diff --git a/openvpn/defaults.yaml b/openvpn/defaults.yaml index df65f95..5f48ab9 100644 --- a/openvpn/defaults.yaml +++ b/openvpn/defaults.yaml @@ -2,6 +2,7 @@ # vim: ft=yaml --- openvpn: + bin_dir: ~ conf_dir: /etc/openvpn conf_ext: conf dh_files: ['2048', '4096'] diff --git a/openvpn/dhparams.sls b/openvpn/dhparams.sls index d45cd2d..e82379d 100644 --- a/openvpn/dhparams.sls +++ b/openvpn/dhparams.sls @@ -8,7 +8,7 @@ {%- set dh_file = config_dir ~ "/dh" ~ dh ~ ".pem" %} openvpn_create_dh_{{ dh }}: cmd.run: - - name: openssl dhparam {% if map.dsaparam %}-dsaparam {% endif %}-out {{ dh_file }} {{ dh }} + - name: '"{{ map.bin_dir | default('', true) }}openssl" dhparam {% if map.dsaparam %}-dsaparam {% endif %}-out "{{ dh_file }}" {{ dh }}' - creates: {{ dh_file }} - require: - pkg: openvpn_pkgs diff --git a/openvpn/init.sls b/openvpn/init.sls index da918e3..889026e 100644 --- a/openvpn/init.sls +++ b/openvpn/init.sls @@ -3,5 +3,6 @@ include: - openvpn.repo - openvpn.install + - openvpn.adapters - openvpn.dhparams - openvpn.service diff --git a/openvpn/osfamilymap.yaml b/openvpn/osfamilymap.yaml index 4806291..03acdec 100644 --- a/openvpn/osfamilymap.yaml +++ b/openvpn/osfamilymap.yaml @@ -26,6 +26,7 @@ FreeBSD: manage_user: false manage_group: false Windows: + bin_dir: C:\Program Files\OpenVPN\bin\ conf_dir: C:\Program Files\OpenVPN\config conf_ext: ovpn service: OpenVPNServiceInteractive diff --git a/test/integration/default/controls/config_spec.rb b/test/integration/default/controls/config_spec.rb index dd1bf23..c091963 100644 --- a/test/integration/default/controls/config_spec.rb +++ b/test/integration/default/controls/config_spec.rb @@ -1,55 +1,45 @@ -# Overide by OS +# frozen_string_literal: true + +if os[:family] == 'windows' + conf_dir = 'C:\\Program Files\\OpenVPN\\config' + conf_ext = 'ovpn' +else + conf_dir = '/etc/openvpn' + conf_ext = 'conf' +end + user = 'root' group = 'openvpn' -control 'OpenVPN server configuration' do - title 'should match desired lines' - +%w[server client].each do |role| cfgfile = case os[:name] - when 'debian' - '/etc/openvpn/server/myserver1.conf' - when 'fedora' - '/etc/openvpn/server/myserver1.conf' - when 'ubuntu' - '/etc/openvpn/server/myserver1.conf' + when 'debian', 'fedora', 'ubuntu' + "#{conf_dir}/#{role}/my#{role}1.#{conf_ext}" else - '/etc/openvpn/myserver1.conf' + "#{conf_dir}/my#{role}1.#{conf_ext}" end - describe file(cfgfile) do - it { should be_file } - it { should be_owned_by user } - it { should be_grouped_into group } - its('mode') { should cmp '0640' } - its('content') { should include '# OpenVPN server configuration' } - its('content') { should include '# Managed by Salt' } - its('content') { should include 'user' } + control "OpenVPN #{role} configuration" do + title 'should match desired lines' + + describe file(cfgfile) do + it { should be_file } + its('content') { should include "# OpenVPN #{role} configuration" } + its('content') { should include '# Managed by Salt' } + its('content') { should include 'user' } + end end -end -control 'OpenVPN client configuration' do - title 'should match desired lines' + control "OpenVPN #{role} configuration file permissions" do + title 'should be correct' - cfgfile = - case os[:name] - when 'debian' - '/etc/openvpn/client/myclient1.conf' - when 'fedora' - '/etc/openvpn/client/myclient1.conf' - when 'ubuntu' - '/etc/openvpn/client/myclient1.conf' - else - '/etc/openvpn/myclient1.conf' - end + only_if('Skip on Windows') { os[:family] != 'windows' } - describe file(cfgfile) do - it { should be_file } - it { should be_owned_by user } - it { should be_grouped_into group } - its('mode') { should cmp '0640' } - its('content') { should include '# OpenVPN client configuration' } - its('content') { should include '# Managed by Salt' } - its('content') { should include 'user' } + describe file(cfgfile) do + it { should be_owned_by user } + it { should be_grouped_into group } + its('mode') { should cmp '0640' } + end end end diff --git a/test/integration/default/controls/packages_spec.rb b/test/integration/default/controls/packages_spec.rb index 580f3de..e275c9a 100644 --- a/test/integration/default/controls/packages_spec.rb +++ b/test/integration/default/controls/packages_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + control 'OpenVPN package' do title 'should be installed' diff --git a/test/integration/default/controls/services_spec.rb b/test/integration/default/controls/services_spec.rb index 604de6d..bb2a36d 100644 --- a/test/integration/default/controls/services_spec.rb +++ b/test/integration/default/controls/services_spec.rb @@ -1,43 +1,45 @@ +# frozen_string_literal: true + control 'OpenVPN service' do impact 0.5 title 'should be running and enabled' - # single service - if os[:name] == 'centos' && os[:release].start_with?('6') - describe service("openvpn") do - it { should be_enabled } - it { should be_running } - end + require 'rspec/retry' - # multiple services - else - %w(server client).each do |role| + log_dir = '/var/log/openvpn/' + if os[:name] == 'centos' && os[:release].start_with?('6') + services = ['openvpn'] + elsif os[:family] == 'windows' + log_dir = 'C:\\Program Files\\OpenVPN\\log\\' + services = ['OpenVPNService'] + else + services = [] + %w[server client].each do |role| prefix = case os[:name] - when 'debian' - "openvpn-#{role}" - when 'fedora' - "openvpn-#{role}" - when 'ubuntu' + when 'debian', 'fedora', 'ubuntu' "openvpn-#{role}" else 'openvpn' end - - describe service("#{prefix}@my#{role}1.service") do - it { should be_enabled } - it { should be_running } - end + services << "#{prefix}@my#{role}1.service" end end - %w(server client).each do |role| - logfile = "/var/log/openvpn/my#{role}1.log" + services.each do |service| + describe service(service) do + it { should be_enabled } + it { should be_running } + end + end - describe command("sh -c 'for i in $(seq 1 60); do if grep \"Initialization Sequence Completed\" #{logfile}; then exit 0; fi; echo -n '.'; sleep 1; done; cat #{logfile}; exit 1'") do - its('exit_status') { should be 0 } - its('stdout') { should include "Initialization Sequence Completed" } + %w[server client].each do |role| + logfile = "#{log_dir}my#{role}1.log" + describe 'Initialization' do + it 'should be completed', retry: 60, retry_wait: 1 do + expect(file(logfile).content).to include 'Initialization Sequence Completed' + end end end end diff --git a/test/integration/default/inspec.yml b/test/integration/default/inspec.yml index 8f0c767..063edad 100644 --- a/test/integration/default/inspec.yml +++ b/test/integration/default/inspec.yml @@ -16,3 +16,4 @@ supports: - platform-name: freebsd - platform-name: amazon - platform-name: arch + - platform: windows diff --git a/test/salt/pillar/default.sls b/test/salt/pillar/default.sls index 7b93519..ce83bad 100644 --- a/test/salt/pillar/default.sls +++ b/test/salt/pillar/default.sls @@ -1,13 +1,24 @@ # -*- coding: utf-8 -*- # vim: ft=yaml --- +{%- if grains['os_family'] == 'Windows' %} +{%- set log_dir = 'C:\\Program Files\\OpenVPN\log\\' %} +{%- set conf_dir = 'C:\\ProgramData\\OpenVPN\config\\' %} +{%- else %} +{%- set log_dir = '/var/log/openvpn/' %} +{%- set conf_dir = '/etc/openvpn/' %} +{%- endif %} openvpn: lookup: +{%- if not grains['os_family'] == 'Windows' %} user: openvpn group: openvpn manage_user: true manage_group: true external_repo_enabled: true +{%- else %} + service: OpenVPNService +{%- endif %} dh_files: ['512'] server: myserver1: @@ -16,13 +27,16 @@ openvpn: proto: udp topology: p2p dev: tun +{%- if grains['os_family'] == 'Windows' %} + dev_node: myserver1 +{%- endif %} comp_lzo: "yes" ifconfig: 169.254.0.1 169.254.0.2 - log_append: /var/log/openvpn/myserver1.log - status: /var/log/openvpn/myserver1-status.log - secret: /etc/openvpn/myserver1_secret.key + log_append: '''{{ log_dir }}myserver1.log''' + status: '''{{ log_dir }}myserver1-status.log''' + secret: '''{{ conf_dir }}myserver1_secret.key''' # /usr/sbin/openvpn --genkey --secret /dev/stdout - secret_content: | + secret_content: &secret_key | # # 2048 bit OpenVPN static key # @@ -52,34 +66,16 @@ openvpn: proto: udp topology: p2p dev: tun +{%- if grains['os_family'] == 'Windows' %} + dev_node: myclient1 +{%- endif %} comp_lzo: "yes" pull: false tls_client: false nobind: false ifconfig: 169.254.0.2 169.254.0.1 - status: /var/log/openvpn/myclient1-status.log - log_append: /var/log/openvpn/myclient1.log - secret: /etc/openvpn/myclient1_secret.key + status: '''{{ log_dir }}myclient1-status.log''' + log_append: '''{{ log_dir }}myclient1.log''' + secret: '''{{ conf_dir }}myclient1_secret.key''' # /usr/sbin/openvpn --genkey --secret /dev/stdout - secret_content: | - # - # 2048 bit OpenVPN static key - # - -----BEGIN OpenVPN Static key V1----- - 6b3e7b098232e9c885f8deed5c069b02 - 47a966595178cc30ebcd4e1042e019ef - fdfbed752e26ef7b0877e0e0a6e4e38b - ffed3fd9da205ff6cd39825d0f8a99ec - 324848682062676868b57e4474791042 - 4dc4ad7f3ff7ba8815e31f950c7443c8 - b52441384936cbf50d2f4d051d0c889a - f118dec5c749398cdce859fced60a4eb - 4e78abb9939f8dbe1cbdbbcaa914b539 - 6258235dce1a8ef044a29f8ce018f183 - 4b83f17a42b788c583cf006cccb5050f - a1c53b22688d98a2092fcd23b160b01a - 064d84f1355c605287b30b140c3c5fa7 - b5e2a0a8def6eb46b3ab4a11b5cb4c96 - 4c099bf8e74b8bf4e6509de69b7a79ad - 7391b6cf3f4ae296ecf8b552144a2947 - -----END OpenVPN Static key V1----- + secret_content: *secret_key