From 89cd5b41d1610af0c64df9cfb6018bd285a5dc34 Mon Sep 17 00:00:00 2001 From: Tobias Knipping Date: Fri, 21 Apr 2017 17:12:35 +0200 Subject: [PATCH] add openvpn::deploy::(export/client) fix linting, add credit, add tests fixes #231 --- lib/facter/openvpn.rb | 62 +++++++++++++ manifests/deploy/client.pp | 58 ++++++++++++ manifests/deploy/export.pp | 96 +++++++++++++++++++ manifests/deploy/install.pp | 26 ++++++ manifests/deploy/prepare.pp | 29 ++++++ manifests/deploy/service.pp | 31 +++++++ spec/defines/openvpn_deploy_client_spec.rb | 33 +++++++ spec/defines/openvpn_deploy_export_spec.rb | 57 ++++++++++++ spec/spec_helper.rb | 3 + spec/unit/openvpn_module_spec.rb | 103 +++++++++++++++++++++ 10 files changed, 498 insertions(+) create mode 100644 lib/facter/openvpn.rb create mode 100644 manifests/deploy/client.pp create mode 100644 manifests/deploy/export.pp create mode 100644 manifests/deploy/install.pp create mode 100644 manifests/deploy/prepare.pp create mode 100644 manifests/deploy/service.pp create mode 100644 spec/defines/openvpn_deploy_client_spec.rb create mode 100644 spec/defines/openvpn_deploy_export_spec.rb create mode 100644 spec/unit/openvpn_module_spec.rb diff --git a/lib/facter/openvpn.rb b/lib/facter/openvpn.rb new file mode 100644 index 00000000..b9b1bde2 --- /dev/null +++ b/lib/facter/openvpn.rb @@ -0,0 +1,62 @@ +require 'facter' + +module Openvpn + def self.etc_path + case Facter.value(:osfamily) + when 'FreeBSD' + '/usr/local/etc/openvpn' + when 'RedHat' + '/etc/openvpn' + when 'Debian' + '/etc/openvpn' + when 'Archlinux' + '/etc/openvpn' + when 'Linux' + '/etc/openvpn' + else + '' + end + end + + def self.client_certs + path = etc_path + clients = {} + if File.directory?(path) + Dir.entries(path).each do |server| + next unless File.directory?("#{path}/#{server}/download-configs") + clients[server.to_s] = {} + + Dir.entries("#{path}/#{server}/download-configs").each do |client| + next unless File.directory?("#{path}/#{server}/download-configs/#{client}") && client !~ %r{^\.\.?$} && client !~ %r{\.tblk$} + + clients[server.to_s][client.to_s] = {} + clients[server.to_s][client.to_s]['conf'] = File.open("#{path}/#{server}/download-configs/#{client}/#{client}.conf", 'r').read + clients[server.to_s][client.to_s]['ca'] = File.open("#{path}/#{server}/download-configs/#{client}/keys/#{client}/ca.crt", 'r').read + clients[server.to_s][client.to_s]['crt'] = File.open("#{path}/#{server}/download-configs/#{client}/keys/#{client}/#{client}.crt", 'r').read + clients[server.to_s][client.to_s]['key'] = File.open("#{path}/#{server}/download-configs/#{client}/keys/#{client}/#{client}.key", 'r').read + if File.exist?("#{path}/#{server}/download-configs/#{client}/keys/#{client}/ta.key") + clients[server.to_s][client.to_s]['ta'] = File.open("#{path}/#{server}/download-configs/#{client}/keys/#{client}/ta.key", 'r').read + end + end + end + end + clients + end + + # Method to call the Facter DSL and dynamically add facts at runtime. + # + # This method is necessary to add reasonable RSpec coverage for the custom + # fact + # + # @return [NilClass] + def self.add_facts + certs = client_certs + Facter.add('openvpn::client_configs') do + setcode do + certs + end + end + end +end + +Openvpn.add_facts diff --git a/manifests/deploy/client.pp b/manifests/deploy/client.pp new file mode 100644 index 00000000..55802ac5 --- /dev/null +++ b/manifests/deploy/client.pp @@ -0,0 +1,58 @@ +# == Define: openvpn::deploy::client +# +# Collect the exported configs for an Host and ensure a running Openvpn Service +# +# === Parameters +# +# $server which Openvpn::Server[$server] does the config belong to? +# String +# +# $manage_etc should the /etc/openvpn directory be managed? (warning, all unmanaged files will be purged!) +# +# === Variables +# +# None +# +# === Examples +# +# openvpn::deploy::client { 'test-client': +# server => 'test_server', +# } +# +# === Authors +# +# Phil Bayfield https://bitbucket.org/Philio/ +# + +define openvpn::deploy::client ( + String $server, + Boolean $manage_etc = true, +) { + + include openvpn::deploy::prepare + + Class['openvpn::deploy::install'] + -> Openvpn::Deploy::Client[$name] + ~> Class['openvpn::deploy::service'] + + + if $manage_etc { + file { [ + "${::openvpn::params::etc_directory}/openvpn", + "${::openvpn::params::etc_directory}/openvpn/keys", + "${::openvpn::params::etc_directory}/openvpn/keys/${name}", + ]: + ensure => directory, + require => Package['openvpn']; + } + } else { + file { "${::openvpn::params::etc_directory}/openvpn/keys/${name}": + ensure => directory, + require => Package['openvpn']; + } + } + + File <<| tag == "${server}-${name}" |>> + ~> Class['openvpn::deploy::service'] + +} diff --git a/manifests/deploy/export.pp b/manifests/deploy/export.pp new file mode 100644 index 00000000..5d2cc3e5 --- /dev/null +++ b/manifests/deploy/export.pp @@ -0,0 +1,96 @@ +# == Define: openvpn::deploy::export +# +# Prepare all Openvpn-Client-Configs to be exported +# +# === Parameters +# +# $server which Openvpn::Server[$server] does the config belong to? +# String +# +# $tls_auth should the ta* files be exported too? +# +# === Variables +# +# None +# +# === Examples +# +# openvpn::deploy::export { 'test-client': +# server => 'test_server', +# } +# +# === Authors +# +# Tobias Knipping https://github.com/to-kn +# Phil Bayfield https://bitbucket.org/Philio/ +# + +define openvpn::deploy::export ( + String $server, + Boolean $tls_auth = false, +) { + + Openvpn::Server[$server] + -> Openvpn::Client[$name] + -> Openvpn::Deploy::Export[$name] + + if $::openvpn::client_configs { + if $::openvpn::client_configs[$server][$name] { + $data = $::openvpn::client_configs[$server][$name] + + @@file { "exported-${server}-${name}-config": + ensure => file, + path => "${::openvpn::params::etc_directory}/openvpn/${name}.conf", + owner => 'root', + group => 'root', + mode => '0600', + content => $data['conf'], + tag => "${server}-${name}", + } + + @@file { "exported-${server}-${name}-ca": + ensure => file, + path => "${::openvpn::params::etc_directory}/openvpn/keys/${name}/ca.crt", + owner => 'root', + group => 'root', + mode => '0600', + content => $data['ca'], + tag => "${server}-${name}", + } + + @@file { "exported-${server}-${name}-crt": + ensure => file, + path => "${::openvpn::params::etc_directory}/openvpn/keys/${name}/${name}.crt", + owner => 'root', + group => 'root', + mode => '0600', + content => $data['crt'], + tag => "${server}-${name}", + } + + @@file { "exported-${server}-${name}-key": + ensure => file, + path => "${::openvpn::params::etc_directory}/openvpn/keys/${name}/${name}.key", + owner => 'root', + group => 'root', + mode => '0600', + content => $data['key'], + tag => "${server}-${name}", + } + + if $tls_auth { + @@file { "exported-${server}-${name}-ta": + ensure => file, + path => "${::openvpn::params::etc_directory}/openvpn/keys/${name}/ta.key", + owner => 'root', + group => 'root', + mode => '0600', + content => $data['ta'], + tag => "${server}-${name}", + } + } + } + } else { + fail('openvpn::client_configs not defined, is pluginsync enabled?') + } +} diff --git a/manifests/deploy/install.pp b/manifests/deploy/install.pp new file mode 100644 index 00000000..5ed35d9e --- /dev/null +++ b/manifests/deploy/install.pp @@ -0,0 +1,26 @@ +# == Class: openvpn::deploy::install +# +# Installs the Openvpn profile +# +# === Parameters +# +# None +# +# === Variables +# +# None +# +# === Examples +# +# include openvpn::deploy::install +# +# === Authors +# +# Phil Bayfield https://bitbucket.org/Philio/ +# + +class openvpn::deploy::install { + + ensure_packages(['openvpn']) + +} diff --git a/manifests/deploy/prepare.pp b/manifests/deploy/prepare.pp new file mode 100644 index 00000000..089c4631 --- /dev/null +++ b/manifests/deploy/prepare.pp @@ -0,0 +1,29 @@ +# == Class: openvpn::deploy::prepare +# +# Base profile +# +# === Parameters +# +# None +# +# === Variables +# +# None +# +# === Examples +# +# include openvpn::deploy::prepare +# +# === Authors +# +# Phil Bayfield https://bitbucket.org/Philio/ +# + +class openvpn::deploy::prepare { + + class { 'openvpn::params': } + + class { 'openvpn::deploy::install': } + ~> class { 'openvpn::deploy::service': } + +} diff --git a/manifests/deploy/service.pp b/manifests/deploy/service.pp new file mode 100644 index 00000000..84078f4b --- /dev/null +++ b/manifests/deploy/service.pp @@ -0,0 +1,31 @@ +# == Class: openvpn::deploy::service +# +# Base profile +# +# === Parameters +# +# None +# +# === Variables +# +# None +# +# === Examples +# +# include openvpn::deploy::service +# +# === Authors +# +# Phil Bayfield https://bitbucket.org/Philio/ +# + +class openvpn::deploy::service { + + service { 'openvpn': + ensure => running, + enable => true, + hasrestart => true, + hasstatus => true; + } + +} diff --git a/spec/defines/openvpn_deploy_client_spec.rb b/spec/defines/openvpn_deploy_client_spec.rb new file mode 100644 index 00000000..a724fec9 --- /dev/null +++ b/spec/defines/openvpn_deploy_client_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe 'openvpn::deploy::client', type: :define do + let(:title) { 'test_client' } + let(:params) { { 'server' => 'test_server' } } + let(:facts) do + { + fqdn: 'somehost', + concat_basedir: '/var/lib/puppet/concat', + osfamily: 'Debian', + operatingsystem: 'Ubuntu', + operatingsystemrelease: '12.04' + } + end + + it { is_expected.to contain_file('/etc/openvpn/keys/test_client') } + + it { is_expected.to contain_package('openvpn') } + it { + is_expected.to contain_service('openvpn').with( + ensure: 'running', + enable: true + ) + } + + context 'with manage_etc' do + let(:params) { { 'server' => 'test_server', 'manage_etc' => true } } + + it { is_expected.to contain_file('/etc/openvpn') } + it { is_expected.to contain_file('/etc/openvpn/keys') } + it { is_expected.to contain_file('/etc/openvpn/keys/test_client') } + end +end diff --git a/spec/defines/openvpn_deploy_export_spec.rb b/spec/defines/openvpn_deploy_export_spec.rb new file mode 100644 index 00000000..368fc85f --- /dev/null +++ b/spec/defines/openvpn_deploy_export_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe 'openvpn::deploy::export', type: :define do + let(:title) { 'test_client' } + let(:params) { { 'server' => 'test_server' } } + let(:facts) do + { + fqdn: 'somehost', + concat_basedir: '/var/lib/puppet/concat', + osfamily: 'Debian', + operatingsystem: 'Ubuntu', + operatingsystemrelease: '12.04', + openvpn: { + client_configs: { + test_server: { + test_client: { + conf: 'config', + crt: 'crt', + ca: 'ca', + key: 'key', + ta: 'ta' + } + } + } + } + } + end + let(:pre_condition) do + [ + 'openvpn::server { "test_server": + country => "CO", + province => "ST", + city => "Some City", + organization => "example.org", + email => "testemail@example.org" + }', + 'openvpn::client { "test_client": + server => "test_server" + }' + ].join + end + + context 'exported resources' do + subject { exported_resources } + + it { is_expected.to contain_file('exported-test_server-test_client-config').with_content('config') } + it { is_expected.to contain_file('exported-test_server-test_client-ca').with_content('ca') } + it { is_expected.to contain_file('exported-test_server-test_client-crt').with_content('crt') } + it { is_expected.to contain_file('exported-test_server-test_client-key').with_content('key') } + + context 'with tls_auth' do + let(:params) { { 'server' => 'test_server', 'tls_auth' => true } } + + it { is_expected.to contain_file('exported-test_server-test_client-ta').with_content('ta') } + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7277b72a..7b42e49a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,8 @@ require 'rspec-puppet-facts' include RspecPuppetFacts +$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../')) + if Dir.exist?(File.expand_path('../../lib', __FILE__)) require 'coveralls' require 'simplecov' @@ -27,6 +29,7 @@ default_facts.merge!(YAML.load(File.read(File.expand_path('../default_module_facts.yml', __FILE__)))) if File.exist?(File.expand_path('../default_module_facts.yml', __FILE__)) c.default_facts = default_facts c.hiera_config = 'spec/fixtures/hiera/hiera.yaml' + c.mock_with :rspec end # vim: syntax=ruby diff --git a/spec/unit/openvpn_module_spec.rb b/spec/unit/openvpn_module_spec.rb new file mode 100644 index 00000000..8d4147c6 --- /dev/null +++ b/spec/unit/openvpn_module_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' +require 'lib/facter/openvpn' + +describe Openvpn do + describe '.etc_path' do + subject(:path) { described_class.etc_path } + + before do + Facter.fact(:osfamily).stubs(:value).returns(osfamily) + end + + after { Facter.clear } + + context 'on FreeBSD' do + let(:osfamily) { 'FreeBSD' } + + it { is_expected.to eq('/usr/local/etc/openvpn') } + end + context 'on Debian' do + let(:osfamily) { 'Debian' } + + it { is_expected.to eq('/etc/openvpn') } + end + context 'on RedHat' do + let(:osfamily) { 'RedHat' } + + it { is_expected.to eq('/etc/openvpn') } + end + context 'on Archlinux' do + let(:osfamily) { 'Archlinux' } + + it { is_expected.to eq('/etc/openvpn') } + end + context 'on Linux' do + let(:osfamily) { 'Linux' } + + it { is_expected.to eq('/etc/openvpn') } + end + context 'on Other' do + let(:osfamily) { 'Other' } + + it { is_expected.to eq('') } + end + end + + describe '.client_certs' do + subject(:path) { described_class.client_certs } + + before do + Facter.fact(:osfamily).stubs(:value).returns(osfamily) + end + + after { Facter.clear } + + context 'with Openvpn installed' do + let(:osfamily) { 'Linux' } + + before do + allow(Dir).to receive(:entries).and_call_original + allow(Dir).to receive(:entries).with('/etc/openvpn').and_return(%w[. .. test-server]) + allow(Dir).to receive(:entries).with('/etc/openvpn/test-server').and_return(%w[. .. download-configs]) + allow(Dir).to receive(:entries).with('/etc/openvpn/test-server/download-configs').and_return(%w[. .. test2 client3 other4]) + allow(File).to receive(:directory?).and_call_original + allow(File).to receive(:directory?).with('/etc/openvpn').and_return(true) + allow(File).to receive(:directory?).with('/etc/openvpn/test-server').and_return(true) + allow(File).to receive(:directory?).with('/etc/openvpn/test-server/download-configs').and_return(true) + allow(File).to receive(:directory?).with('/etc/openvpn/test-server/download-configs/test2').and_return(true) + allow(File).to receive(:open).with('/etc/openvpn/test-server/download-configs/test2/test2.conf', 'r').and_return(StringIO.new('conf')) + allow(File).to receive(:open).with('/etc/openvpn/test-server/download-configs/test2/keys/test2/ca.crt', 'r').and_return(StringIO.new('ca')) + allow(File).to receive(:open).with('/etc/openvpn/test-server/download-configs/test2/keys/test2/test2.crt', 'r').and_return(StringIO.new('crt')) + allow(File).to receive(:open).with('/etc/openvpn/test-server/download-configs/test2/keys/test2/test2.key', 'r').and_return(StringIO.new('key')) + end + it { is_expected.to eq('test-server' => { 'test2' => { 'conf' => 'conf', 'ca' => 'ca', 'crt' => 'crt', 'key' => 'key' } }) } + + context 'with tsl_auth enabled' do + before do + allow(File).to receive(:exist?).with('/etc/openvpn/test-server/download-configs/test2/keys/test2/ta.key').and_return(true) + allow(File).to receive(:open).with('/etc/openvpn/test-server/download-configs/test2/keys/test2/ta.key', 'r').and_return(StringIO.new('ta')) + end + + it { is_expected.to eq('test-server' => { 'test2' => { 'conf' => 'conf', 'ca' => 'ca', 'crt' => 'crt', 'key' => 'key', 'ta' => 'ta' } }) } + end + end + end + + describe 'openvpn::client_configs fact' do + subject(:fact) { Facter.fact('openvpn::client_configs').value } + + before do + # Ensure we're populating Facter's internal collection with our Fact + described_class.add_facts + end + + # A regular ol' RSpec example + it { is_expected.to eq({}) } + + after do + # Make sure we're clearing out Facter every time + Facter.clear + Facter.clear_messages + end + end +end