From 2728eed07d93fbd0daa8c4d528597cd156bf2546 Mon Sep 17 00:00:00 2001 From: Micah Date: Tue, 24 May 2016 10:19:20 -0400 Subject: Squashed 'puppet/modules/openvpn/' content from commit 26d4edc git-subtree-dir: puppet/modules/openvpn git-subtree-split: 26d4edc669853a268a65d2cbbfb42c19f1333de7 --- .fixtures.yml | 6 + .gitignore | 3 + .rvmrc | 38 ++++ .travis.yml | 29 +++ Gemfile | 7 + Gemfile.lock | 36 ++++ LICENSE | 177 ++++++++++++++++ Modulefile | 11 + Rakefile | 2 + Readme.markdown | 54 +++++ Vagrantfile | 42 ++++ manifests/client.pp | 187 +++++++++++++++++ manifests/client_specific_config.pp | 79 +++++++ manifests/config.pp | 52 +++++ manifests/init.pp | 43 ++++ manifests/install.pp | 46 ++++ manifests/params.pp | 37 ++++ manifests/server.pp | 233 +++++++++++++++++++++ manifests/service.pp | 36 ++++ spec/classes/openvpn_config_spec.rb | 15 ++ spec/classes/openvpn_init_spec.rb | 9 + spec/classes/openvpn_install_spec.rb | 11 + spec/classes/openvpn_service_spec.rb | 13 ++ spec/defines/openvpn_client_spec.rb | 88 ++++++++ .../defines/openvpn_client_specific_config_spec.rb | 40 ++++ spec/defines/openvpn_server_spec.rb | 165 +++++++++++++++ spec/spec_helper.rb | 2 + templates/client.erb | 26 +++ templates/client_specific_config.erb | 10 + templates/etc-default-openvpn.erb | 20 ++ templates/server.erb | 37 ++++ templates/vars.erb | 68 ++++++ vagrant/client.pp | 5 + vagrant/server.pp | 23 ++ 34 files changed, 1650 insertions(+) create mode 100644 .fixtures.yml create mode 100644 .gitignore create mode 100644 .rvmrc create mode 100644 .travis.yml create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 LICENSE create mode 100644 Modulefile create mode 100644 Rakefile create mode 100644 Readme.markdown create mode 100644 Vagrantfile create mode 100644 manifests/client.pp create mode 100644 manifests/client_specific_config.pp create mode 100644 manifests/config.pp create mode 100644 manifests/init.pp create mode 100644 manifests/install.pp create mode 100644 manifests/params.pp create mode 100644 manifests/server.pp create mode 100644 manifests/service.pp create mode 100644 spec/classes/openvpn_config_spec.rb create mode 100644 spec/classes/openvpn_init_spec.rb create mode 100644 spec/classes/openvpn_install_spec.rb create mode 100644 spec/classes/openvpn_service_spec.rb create mode 100644 spec/defines/openvpn_client_spec.rb create mode 100644 spec/defines/openvpn_client_specific_config_spec.rb create mode 100644 spec/defines/openvpn_server_spec.rb create mode 100644 spec/spec_helper.rb create mode 100644 templates/client.erb create mode 100644 templates/client_specific_config.erb create mode 100644 templates/etc-default-openvpn.erb create mode 100644 templates/server.erb create mode 100644 templates/vars.erb create mode 100644 vagrant/client.pp create mode 100644 vagrant/server.pp diff --git a/.fixtures.yml b/.fixtures.yml new file mode 100644 index 00000000..1125ecca --- /dev/null +++ b/.fixtures.yml @@ -0,0 +1,6 @@ +fixtures: + repositories: + concat: git://github.com/ripienaar/puppet-concat.git + symlinks: + openvpn: "#{source_dir}" + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6fd248b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +pkg +spec/fixtures +.vagrant diff --git a/.rvmrc b/.rvmrc new file mode 100644 index 00000000..6fbfb7f1 --- /dev/null +++ b/.rvmrc @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# This is an RVM Project .rvmrc file, used to automatically load the ruby +# development environment upon cd'ing into the directory + +# First we specify our desired [@], the @gemset name is optional, +# Only full ruby name is supported here, for short names use: +# echo "rvm use 1.9.3" > .rvmrc +environment_id="ruby-1.9.3-p194@puppet" + +# Uncomment the following lines if you want to verify rvm version per project +# rvmrc_rvm_version="1.15.8 (stable)" # 1.10.1 seams as a safe start +# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || { +# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading." +# return 1 +# } + +# First we attempt to load the desired environment directly from the environment +# file. This is very fast and efficient compared to running through the entire +# CLI and selector. If you want feedback on which environment was used then +# insert the word 'use' after --create as this triggers verbose mode. +if [[ -d "${rvm_path:-$HOME/.rvm}/environments" + && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] +then + \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id" + [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] && + \. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true + if [[ $- == *i* ]] # check for interactive shells + then echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green + else echo "Using: $GEM_HOME" # don't use colors in non-interactive shells + fi +else + # If the environment file has not yet been created, use the RVM CLI to select. + rvm --create use "$environment_id" || { + echo "Failed to create RVM environment '${environment_id}'." + return 1 + } +fi diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..da5c389d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +language: ruby +bundler_args: --without development +script: "bundle exec rake spec SPEC_OPTS='--format documentation'" +rvm: + - 1.8.7 + - 1.9.3 + - 2.0.0 +script: + - "rake lint" + - "rake spec SPEC_OPTS='--format documentation'" +env: + - PUPPET_VERSION="~> 2.7.0" + - PUPPET_VERSION="~> 3.0.0" + - PUPPET_VERSION="~> 3.1.0" + - PUPPET_VERSION="~> 3.2.0" +matrix: + exclude: + - rvm: 1.9.3 + env: PUPPET_VERSION="~> 2.7.0" + - rvm: 2.0.0 + env: PUPPET_VERSION="~> 2.7.0" + - rvm: 2.0.0 + env: PUPPET_VERSION="~> 3.0.0" + - rvm: 2.0.0 + env: PUPPET_VERSION="~> 3.1.0" +notifications: + email: false + on_success: always + on_failure: always diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..68e10e7d --- /dev/null +++ b/Gemfile @@ -0,0 +1,7 @@ +source :rubygems + +puppetversion = ENV['PUPPET_VERSION'] +gem 'puppet', puppetversion, :require => false +gem 'puppet-lint' +gem 'rspec-puppet' +gem 'puppetlabs_spec_helper' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..9fce3f98 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,36 @@ +GEM + remote: http://rubygems.org/ + specs: + diff-lcs (1.1.3) + facter (1.6.17) + hiera (1.0.0) + metaclass (0.0.1) + mocha (0.13.1) + metaclass (~> 0.0.1) + puppet (3.0.2) + facter (~> 1.6.11) + hiera (~> 1.0.0) + puppetlabs_spec_helper (0.4.0) + mocha (>= 0.10.5) + rake + rspec (>= 2.9.0) + rspec-puppet (>= 0.1.1) + rake (10.0.3) + rspec (2.12.0) + rspec-core (~> 2.12.0) + rspec-expectations (~> 2.12.0) + rspec-mocks (~> 2.12.0) + rspec-core (2.12.2) + rspec-expectations (2.12.1) + diff-lcs (~> 1.1.3) + rspec-mocks (2.12.1) + rspec-puppet (0.1.5) + rspec + +PLATFORMS + ruby + +DEPENDENCIES + puppet + puppetlabs_spec_helper + rspec-puppet diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f433b1a5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + 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. + + END OF TERMS AND CONDITIONS diff --git a/Modulefile b/Modulefile new file mode 100644 index 00000000..679e7e64 --- /dev/null +++ b/Modulefile @@ -0,0 +1,11 @@ +name 'luxflux-openvpn' +version '2.1.0' +source 'https://github.com/luxflux/puppet-openvpn' +author 'luxflux' +license 'Apache 2.0' +summary 'OpenVPN server puppet module' +description 'Puppet module to manage OpenVPN servers' +project_page 'https://github.com/luxflux/puppet-openvpn' + +## Add dependencies, if any: +dependency 'ripienaar/concat', '0.2.0' diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..14f1c246 --- /dev/null +++ b/Rakefile @@ -0,0 +1,2 @@ +require 'rubygems' +require 'puppetlabs_spec_helper/rake_tasks' diff --git a/Readme.markdown b/Readme.markdown new file mode 100644 index 00000000..6bcf49ea --- /dev/null +++ b/Readme.markdown @@ -0,0 +1,54 @@ +# OpenVPN Puppet module + +Puppet module to manage OpenVPN servers + +## Features: + +* Client-specific rules and access policies +* Generated client configurations and SSL-Certificates +* Downloadable client configurations and SSL-Certificates for easy client configuration +* Support for multiple server instances + +Tested on Ubuntu Precise Pangolin, CentOS 6, RedHat 6. + + +## Dependencies + - [puppet-concat](https://github.com/ripienaar/puppet-concat) + + +## Example + +```puppet + # add a server instance + openvpn::server { 'winterthur': + country => 'CH', + province => 'ZH', + city => 'Winterthur', + organization => 'example.org', + email => 'root@example.org', + server => '10.200.200.0 255.255.255.0' + } + + # define clients + openvpn::client { 'client1': + server => 'winterthur' + } + openvpn::client { 'client2': + server => 'winterthur' + } + + openvpn::client_specific_config { 'client1': + server => 'winterthur', + ifconfig => '10.200.200.50 255.255.255.0' + } +``` + +Don't forget the [sysctl](https://github.com/luxflux/puppet-sysctl) directive ```net.ipv4.ip_forward```! + + +# Contributors + +These fine folks helped to get this far with this module: +* [@jlambert121](https://github.com/jlambert121) +* [@jlk](https://github.com/jlk) +* [@elisiano](https://github.com/elisiano) diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 00000000..88875ff8 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,42 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +def server_config(config) + config.vm.provision :puppet, :module_path => '..' do |puppet| + puppet.manifests_path = "vagrant" + puppet.manifest_file = "server.pp" + end +end + +def client_config(config) + config.vm.provision :puppet, :module_path => '..' do |puppet| + puppet.manifests_path = "vagrant" + puppet.manifest_file = "client.pp" + end +end + +Vagrant::Config.run do |config| + + config.vm.define :server_ubuntu do |c| + c.vm.box = 'precise64' + server_config c + c.vm.network :hostonly, '10.255.255.10' + end + + config.vm.define :server_centos do |c| + c.vm.box = 'centos63' + + c.vm.provision :shell, :inline => 'if [ ! -f rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm ]; then wget -q http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm; fi' + c.vm.provision :shell, :inline => 'yum install -y rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm || exit 0' + + server_config c + c.vm.network :hostonly, '10.255.255.11' + end + + config.vm.define :client_ubuntu do |c| + c.vm.box = 'precise64' + client_config c + c.vm.network :hostonly, '10.255.255.20' + end + +end diff --git a/manifests/client.pp b/manifests/client.pp new file mode 100644 index 00000000..92c6aa4e --- /dev/null +++ b/manifests/client.pp @@ -0,0 +1,187 @@ +# == Define: openvpn::client +# +# This define creates the client certs for a specified openvpn server as well +# as creating a tarball that can be directly imported into openvpn clients +# +# +# === Parameters +# +# [*server*] +# String. Name of the corresponding openvpn endpoint +# Required +# +# [*compression*] +# String. Which compression algorithim to use +# Default: comp-lzo +# Options: comp-lzo or '' (disable compression) +# +# [*dev*] +# String. Device method +# Default: tun +# Options: tun (routed connections), tap (bridged connections) +# +# [*mute*] +# Integer. Set log mute level +# Default: 20 +# +# [*mute_replay_warnings*] +# Boolean. Silence duplicate packet warnings (common on wireless networks) +# Default: true +# +# [*nobind*] +# Boolean. Whether or not to bind to a specific port number +# Default: true +# +# [*persist_key*] +# Boolean. Try to retain access to resources that may be unavailable +# because of privilege downgrades +# Default: true +# +# [*persist_tun*] +# Boolean. Try to retain access to resources that may be unavailable +# because of privilege downgrades +# Default: true +# +# [*port*] +# Integer. The port the openvpn server service is running on +# Default: 1194 +# +# [*proto*] +# String. What IP protocol is being used. +# Default: tcp +# Options: tcp or udp +# +# [*remote_host*] +# String. The IP or hostname of the openvpn server service +# Default: FQDN +# +# [*resolv_retry*] +# Integer/String. How many seconds should the openvpn client try to resolve +# the server's hostname +# Default: infinite +# Options: Integer or infinite +# +# [*verb*] +# Integer. Level of logging verbosity +# Default: 3 +# +# +# === Examples +# +# openvpn::client { +# 'my_user': +# server => 'contractors', +# remote_host => 'vpn.mycompany.com' +# } +# +# * Removal: +# Manual process right now, todo for the future +# +# +# === Authors +# +# * Raffael Schmid +# * John Kinsella +# * Justin Lambert +# +# === License +# +# Copyright 2013 Raffael Schmid, +# +# 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. +# +define openvpn::client( + $server, + $compression = 'comp-lzo', + $dev = 'tun', + $mute = '20', + $mute_replay_warnings = true, + $nobind = true, + $persist_key = true, + $persist_tun = true, + $port = '1194', + $proto = 'tcp', + $remote_host = $::fqdn, + $resolv_retry = 'infinite', + $verb = '3', +) { + + Openvpn::Server[$server] -> + Openvpn::Client[$name] + + exec { + "generate certificate for ${name} in context of ${server}": + command => ". ./vars && ./pkitool ${name}", + cwd => "/etc/openvpn/${server}/easy-rsa", + creates => "/etc/openvpn/${server}/easy-rsa/keys/${name}.crt", + provider => 'shell'; + } + + file { + [ "/etc/openvpn/${server}/download-configs/${name}", + "/etc/openvpn/${server}/download-configs/${name}/keys"]: + ensure => directory; + + "/etc/openvpn/${server}/download-configs/${name}/keys/${name}.crt": + ensure => link, + target => "/etc/openvpn/${server}/easy-rsa/keys/${name}.crt", + require => Exec["generate certificate for ${name} in context of ${server}"]; + + "/etc/openvpn/${server}/download-configs/${name}/keys/${name}.key": + ensure => link, + target => "/etc/openvpn/${server}/easy-rsa/keys/${name}.key", + require => Exec["generate certificate for ${name} in context of ${server}"]; + + "/etc/openvpn/${server}/download-configs/${name}/keys/ca.crt": + ensure => link, + target => "/etc/openvpn/${server}/easy-rsa/keys/ca.crt", + require => Exec["generate certificate for ${name} in context of ${server}"]; + + "/etc/openvpn/${server}/download-configs/${name}/${name}.conf": + owner => root, + group => root, + mode => '0444', + content => template('openvpn/client.erb'), + notify => Exec["tar the thing ${server} with ${name}"]; + } + + exec { + "tar the thing ${server} with ${name}": + cwd => "/etc/openvpn/${server}/download-configs/", + command => "/bin/rm ${name}.tar.gz; tar --exclude=\\*.conf.d -chzvf ${name}.tar.gz ${name}", + refreshonly => true, + require => [ File["/etc/openvpn/${server}/download-configs/${name}/${name}.conf"], + File["/etc/openvpn/${server}/download-configs/${name}/keys/ca.crt"], + File["/etc/openvpn/${server}/download-configs/${name}/keys/${name}.key"], + File["/etc/openvpn/${server}/download-configs/${name}/keys/${name}.crt"] + ], + notify => Exec["generate ${name}.ovpn in ${server}"]; + } + + exec { + "generate ${name}.ovpn in ${server}": + cwd => "/etc/openvpn/${server}/download-configs/", + command => "/bin/rm ${name}.ovpn; cat ${name}/${name}.conf|perl -lne 'if(m|^ca keys/ca.crt|){ chomp(\$ca=`cat ${name}/keys/ca.crt`); print \"\n\$ca\n\"} elsif(m|^cert keys/${name}.crt|) { chomp(\$crt=`cat ${name}/keys/${name}.crt`); print \"\n\$crt\n\"} elsif(m|^key keys/${name}.key|){ chomp(\$key=`cat ${name}/keys/${name}.key`); print \"\n\$key\n\"} else { print} ' > ${name}.ovpn", + refreshonly => true, + require => [ File["/etc/openvpn/${server}/download-configs/${name}/${name}.conf"], + File["/etc/openvpn/${server}/download-configs/${name}/keys/ca.crt"], + File["/etc/openvpn/${server}/download-configs/${name}/keys/${name}.key"], + File["/etc/openvpn/${server}/download-configs/${name}/keys/${name}.crt"], + ], + } + + file { "/etc/openvpn/${server}/download-configs/${name}.ovpn": + mode => '0400', + require => Exec["generate ${name}.ovpn in ${server}"], + } +} diff --git a/manifests/client_specific_config.pp b/manifests/client_specific_config.pp new file mode 100644 index 00000000..4287421a --- /dev/null +++ b/manifests/client_specific_config.pp @@ -0,0 +1,79 @@ +# == Define: openvpn::client_specific_config +# +# This define configures options which will be pushed by the server to a +# specific client only. This feature is explained here: +# http://openvpn.net/index.php/open-source/documentation/howto.html#policy +# +# === Parameters +# +# All the parameters are explained in the openvpn documentation: +# http://openvpn.net/index.php/open-source/documentation/howto.html#policy +# +# [*server*] +# String. Name of the corresponding openvpn endpoint +# Required +# +# [*iroute*] +# Array. Array of iroute combinations. +# Default: [] +# +# [*ifconfig*] +# String. IP configuration to push to the client. +# Default: false +# +# [*dhcp_options] +# Array. DHCP options to push to the client. +# Default: [] +# +# +# === Examples +# +# openvpn::client_specific_config { +# 'vpn_client': +# server => 'contractors', +# iroute => ['10.0.1.0 255.255.255.0'], +# ifconfig => '10.10.10.1 10.10.10.2', +# dhcp_options => ['DNS 8.8.8.8'] +# } +# +# * Removal: +# Manual process right now, todo for the future +# +# +# === Authors +# +# * Raffael Schmid +# +# === License +# +# Copyright 2013 Raffael Schmid, +# +# 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. +# +define openvpn::client_specific_config( + $server, + $iroute = [], + $ifconfig = false, + $dhcp_options = [] +) { + + Openvpn::Server[$server] -> + Openvpn::Client[$name] -> + Openvpn::Client_specific_config[$name] + + file { "/etc/openvpn/${server}/client-configs/${name}": + ensure => present, + content => template('openvpn/client_specific_config.erb') + } + +} diff --git a/manifests/config.pp b/manifests/config.pp new file mode 100644 index 00000000..32b32094 --- /dev/null +++ b/manifests/config.pp @@ -0,0 +1,52 @@ +# == Class: openvpn::config +# +# This class sets up the openvpn enviornment as well as the default config file +# +# +# === Examples +# +# This class should not be directly invoked +# +# === Authors +# +# * Raffael Schmid +# * John Kinsella +# * Justin Lambert +# +# === License +# +# Copyright 2013 Raffael Schmid, +# +# 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. +# +class openvpn::config { + + if $::osfamily == 'Debian' { + include concat::setup + + concat { + '/etc/default/openvpn': + owner => root, + group => root, + mode => 644, + warn => true; + } + + concat::fragment { + 'openvpn.default.header': + content => template('openvpn/etc-default-openvpn.erb'), + target => '/etc/default/openvpn', + order => 01; + } + } +} diff --git a/manifests/init.pp b/manifests/init.pp new file mode 100644 index 00000000..7e07f025 --- /dev/null +++ b/manifests/init.pp @@ -0,0 +1,43 @@ +# == Class: openvpn +# +# This module installs the openvpn service, configures vpn endpoints, generates +# client certificates, and generates client config files +# +# +# === Examples +# +# * Installation: +# class { 'openvpn': } +# +# +# === Authors +# +# * Raffael Schmid +# * John Kinsella +# * Justin Lambert +# +# === License +# +# Copyright 2013 Raffael Schmid, +# +# 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. +# +class openvpn { + + class {'openvpn::params': } -> + class {'openvpn::install': } -> + class {'openvpn::config': } ~> + class {'openvpn::service': } -> + Class['openvpn'] + +} diff --git a/manifests/install.pp b/manifests/install.pp new file mode 100644 index 00000000..a230373a --- /dev/null +++ b/manifests/install.pp @@ -0,0 +1,46 @@ +# == Class: openvpn +# +# This module installs the openvpn service, configures vpn endpoints, generates +# client certificates, and generates client config files +# +# +# === Examples +# +# This class should not be directly invoked +# +# +# === Authors +# +# * Raffael Schmid +# * John Kinsella +# * Justin Lambert +# +# === License +# +# Copyright 2013 Raffael Schmid, +# +# 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. +# +class openvpn::install { + + package { + 'openvpn': + ensure => installed; + } + + file { + [ '/etc/openvpn', '/etc/openvpn/keys' ]: + ensure => directory, + require => Package['openvpn']; + } +} diff --git a/manifests/params.pp b/manifests/params.pp new file mode 100644 index 00000000..33495270 --- /dev/null +++ b/manifests/params.pp @@ -0,0 +1,37 @@ +# === License +# +# Copyright 2013 Raffael Schmid, +# +# 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. +# +class openvpn::params { + + $group = $::osfamily ? { + 'RedHat' => 'nobody', + default => 'nogroup' + } + + $easyrsa_source = $::osfamily ? { + 'RedHat' => $::operatingsystemmajrelease ? { + 6 => '/usr/share/openvpn/easy-rsa/2.0', + default => '/usr/share/doc/openvpn-2.2.2/easy-rsa/2.0' + }, + default => '/usr/share/doc/openvpn/examples/easy-rsa/2.0' + } + + $link_openssl_cnf = $::osfamily ? { + /(Debian|RedHat)/ => true, + default => false + } + +} diff --git a/manifests/server.pp b/manifests/server.pp new file mode 100644 index 00000000..649048c4 --- /dev/null +++ b/manifests/server.pp @@ -0,0 +1,233 @@ +# == Define: openvpn::server +# +# This define creates the openvpn server instance and ssl certificates +# +# +# === Parameters +# +# [*country*] +# String. Country to be used for the SSL certificate +# +# [*province*] +# String. Province to be used for the SSL certificate +# +# [*city*] +# String. City to be used for the SSL certificate +# +# [*organization*] +# String. Organization to be used for the SSL certificate +# +# [*email*] +# String. Email address to be used for the SSL certificate +# +# [*compression*] +# String. Which compression algorithim to use +# Default: comp-lzo +# Options: comp-lzo or '' (disable compression) +# +# [*dev*] +# String. Device method +# Default: tun +# Options: tun (routed connections), tap (bridged connections) +# +# [*user*] +# String. Group to drop privileges to after startup +# Default: nobody +# +# [*group*] +# String. User to drop privileges to after startup +# Default: depends on your $::osfamily +# +# [*ipp*] +# Boolean. Persist ifconfig information to a file to retain client IP +# addresses between sessions +# Default: false +# +# [*local*] +# String. Interface for openvpn to bind to. +# Default: $::ipaddress_eth0 +# Options: An IP address or '' to bind to all ip addresses +# +# [*logfile*] +# String. Logfile for this openvpn server +# Default: false +# Options: false (syslog) or log file name +# +# [*port*] +# Integer. The port the openvpn server service is running on +# Default: 1194 +# +# [*proto*] +# String. What IP protocol is being used. +# Default: tcp +# Options: tcp or udp +# +# [*status_log*] +# String. Logfile for periodic dumps of the vpn service status +# Default: "${name}/openvpn-status.log" +# +# [*server*] +# String. Network to assign client addresses out of +# Default: None. Required in tun mode, not in tap mode +# +# [*push*] +# Array. Options to push out to the client. This can include routes, DNS +# servers, DNS search domains, and many other options. +# Default: [] +# +# +# === Examples +# +# openvpn::client { +# 'my_user': +# server => 'contractors', +# remote_host => 'vpn.mycompany.com' +# } +# +# * Removal: +# Manual process right now, todo for the future +# +# +# === Authors +# +# * Raffael Schmid +# * John Kinsella +# * Justin Lambert +# +# === License +# +# Copyright 2013 Raffael Schmid, +# +# 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. +# +define openvpn::server( + $country, + $province, + $city, + $organization, + $email, + $compression = 'comp-lzo', + $dev = 'tun0', + $user = 'nobody', + $group = false, + $ipp = false, + $ip_pool = [], + $local = $::ipaddress_eth0, + $logfile = false, + $port = '1194', + $proto = 'tcp', + $status_log = "${name}/openvpn-status.log", + $server = '', + $push = [] +) { + + include openvpn + Class['openvpn::install'] -> + Openvpn::Server[$name] ~> + Class['openvpn::service'] + + $tls_server = $proto ? { + /tcp/ => true, + default => false + } + + $group_to_set = $group ? { + false => $openvpn::params::group, + default => $group + } + + file { + ["/etc/openvpn/${name}", "/etc/openvpn/${name}/client-configs", "/etc/openvpn/${name}/download-configs" ]: + ensure => directory; + } + + exec { + "copy easy-rsa to openvpn config folder ${name}": + command => "/bin/cp -r ${openvpn::params::easyrsa_source} /etc/openvpn/${name}/easy-rsa", + creates => "/etc/openvpn/${name}/easy-rsa", + notify => Exec["fix_easyrsa_file_permissions_${name}"], + require => File["/etc/openvpn/${name}"]; + } + + exec { + "fix_easyrsa_file_permissions_${name}": + refreshonly => true, + command => "/bin/chmod 755 /etc/openvpn/${name}/easy-rsa/*"; + } + + file { + "/etc/openvpn/${name}/easy-rsa/vars": + ensure => present, + content => template('openvpn/vars.erb'), + require => Exec["copy easy-rsa to openvpn config folder ${name}"]; + } + + file { + "/etc/openvpn/${name}/easy-rsa/openssl.cnf": + require => Exec["copy easy-rsa to openvpn config folder ${name}"]; + } + + if $openvpn::params::link_openssl_cnf == true { + File["/etc/openvpn/${name}/easy-rsa/openssl.cnf"] { + ensure => link, + target => "/etc/openvpn/${name}/easy-rsa/openssl-1.0.0.cnf" + } + } + + exec { + "generate dh param ${name}": + command => '. ./vars && ./clean-all && ./build-dh', + cwd => "/etc/openvpn/${name}/easy-rsa", + creates => "/etc/openvpn/${name}/easy-rsa/keys/dh1024.pem", + provider => 'shell', + require => File["/etc/openvpn/${name}/easy-rsa/vars"]; + + "initca ${name}": + command => '. ./vars && ./pkitool --initca', + cwd => "/etc/openvpn/${name}/easy-rsa", + creates => "/etc/openvpn/${name}/easy-rsa/keys/ca.key", + provider => 'shell', + require => [ Exec["generate dh param ${name}"], File["/etc/openvpn/${name}/easy-rsa/openssl.cnf"] ]; + + "generate server cert ${name}": + command => '. ./vars && ./pkitool --server server', + cwd => "/etc/openvpn/${name}/easy-rsa", + creates => "/etc/openvpn/${name}/easy-rsa/keys/server.key", + provider => 'shell', + require => Exec["initca ${name}"]; + } + + file { + "/etc/openvpn/${name}/keys": + ensure => link, + target => "/etc/openvpn/${name}/easy-rsa/keys", + require => Exec["copy easy-rsa to openvpn config folder ${name}"]; + } + + if $::osfamily == 'Debian' { + concat::fragment { + "openvpn.default.autostart.${name}": + content => "AUTOSTART=\"\$AUTOSTART ${name}\"\n", + target => '/etc/default/openvpn', + order => 10; + } + } + + file { + "/etc/openvpn/${name}.conf": + owner => root, + group => root, + mode => '0444', + content => template('openvpn/server.erb'); + } +} diff --git a/manifests/service.pp b/manifests/service.pp new file mode 100644 index 00000000..54e8db7d --- /dev/null +++ b/manifests/service.pp @@ -0,0 +1,36 @@ +# == Class: openvpn::config +# +# This class maintains the openvpn service +# +# +# === Examples +# +# This class should not be directly invoked +# +# === Authors +# +# * Raffael Schmid +# * John Kinsella +# * Justin Lambert +# +# === License +# +# Copyright 2013 Raffael Schmid, +# +# 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 +# +# lied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +class openvpn::service { + service { + 'openvpn': + ensure => running, + enable => true, + hasrestart => true, + hasstatus => true; + } +} diff --git a/spec/classes/openvpn_config_spec.rb b/spec/classes/openvpn_config_spec.rb new file mode 100644 index 00000000..bbb63a77 --- /dev/null +++ b/spec/classes/openvpn_config_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe 'openvpn::config', :type => :class do + + it { should create_class('openvpn::config') } + + context "on Debian based machines" do + let (:facts) { { :osfamily => 'Debian', :concat_basedir => '/var/lib/puppet/concat' } } + + it { should contain_class('concat::setup') } + it { should contain_concat('/etc/default/openvpn') } + it { should contain_concat__fragment('openvpn.default.header') } + end + +end diff --git a/spec/classes/openvpn_init_spec.rb b/spec/classes/openvpn_init_spec.rb new file mode 100644 index 00000000..45dcc9bf --- /dev/null +++ b/spec/classes/openvpn_init_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe 'openvpn', :type => :class do + + let (:facts) { { :concat_basedir => '/var/lib/puppet/concat' } } + + it { should create_class('openvpn') } + +end diff --git a/spec/classes/openvpn_install_spec.rb b/spec/classes/openvpn_install_spec.rb new file mode 100644 index 00000000..cdb31358 --- /dev/null +++ b/spec/classes/openvpn_install_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe 'openvpn::install', :type => :class do + + it { should create_class('openvpn::install') } + it { should contain_package('openvpn') } + + it { should contain_file('/etc/openvpn').with('ensure' => 'directory') } + it { should contain_file('/etc/openvpn/keys').with('ensure' => 'directory') } + +end diff --git a/spec/classes/openvpn_service_spec.rb b/spec/classes/openvpn_service_spec.rb new file mode 100644 index 00000000..f427e7f1 --- /dev/null +++ b/spec/classes/openvpn_service_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe 'openvpn::service', :type => :class do + + let (:facts) { { :concat_basedir => '/var/lib/puppet/concat' } } + + it { should create_class('openvpn::service') } + it { should contain_service('openvpn').with( + 'ensure' => 'running', + 'enable' => true + ) } + +end diff --git a/spec/defines/openvpn_client_spec.rb b/spec/defines/openvpn_client_spec.rb new file mode 100644 index 00000000..a4b580e8 --- /dev/null +++ b/spec/defines/openvpn_client_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe 'openvpn::client', :type => :define do + let(:title) { 'test_client' } + let(:params) { { 'server' => 'test_server' } } + let(:facts) { { :fqdn => 'somehost', :concat_basedir => '/var/lib/puppet/concat' } } + let(:pre_condition) do + 'openvpn::server { "test_server": + country => "CO", + province => "ST", + city => "Some City", + organization => "example.org", + email => "testemail@example.org" + }' + end + + it { should contain_exec('generate certificate for test_client in context of test_server') } + + [ 'test_client', 'test_client/keys'].each do |directory| + it { should contain_file("/etc/openvpn/test_server/download-configs/#{directory}") } + end + + [ 'test_client.crt', 'test_client.key', 'ca.crt' ].each do |file| + it { should contain_file("/etc/openvpn/test_server/download-configs/test_client/keys/#{file}").with( + 'ensure' => 'link', + 'target' => "/etc/openvpn/test_server/easy-rsa/keys/#{file}" + )} + end + + it { should contain_exec('tar the thing test_server with test_client').with( + 'cwd' => '/etc/openvpn/test_server/download-configs/', + 'command' => '/bin/rm test_client.tar.gz; tar --exclude=\*.conf.d -chzvf test_client.tar.gz test_client' + ) } + + context "setting the minimum parameters" do + let(:params) { { 'server' => 'test_server' } } + let(:facts) { { :fqdn => 'somehost', :concat_basedir => '/var/lib/puppet/concat' } } + + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^client$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^ca\s+keys\/ca\.crt$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^cert\s+keys\/test_client.crt$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^key\s+keys\/test_client\.key$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^dev\s+tun$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^proto\s+tcp$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^remote\s+somehost\s+1194$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^comp-lzo$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^resolv-retry\s+infinite$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^nobind$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^persist-key$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^persist-tun$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^mute-replay-warnings$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^ns\-cert\-type\s+server$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^verb\s+3$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^mute\s+20$/)} + end + + context "setting all of the parameters" do + let(:params) { { + 'server' => 'test_server', + 'compression' => 'comp-something', + 'dev' => 'tap', + 'mute' => 10, + 'mute_replay_warnings' => false, + 'nobind' => false, + 'persist_key' => false, + 'persist_tun' => false, + 'port' => '123', + 'proto' => 'udp', + 'remote_host' => 'somewhere', + 'resolv_retry' => '2m', + 'verb' => '1' + } } + let(:facts) { { :fqdn => 'somehost', :concat_basedir => '/var/lib/puppet/concat' } } + + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^client$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^ca\s+keys\/ca\.crt$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^cert\s+keys\/test_client.crt$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^key\s+keys\/test_client\.key$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^dev\s+tap$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^proto\s+udp$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^remote\s+somewhere\s+123$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^comp-something$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^resolv-retry\s+2m$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^verb\s+1$/)} + it { should contain_file('/etc/openvpn/test_server/download-configs/test_client/test_client.conf').with_content(/^mute\s+10$/)} + end + +end diff --git a/spec/defines/openvpn_client_specific_config_spec.rb b/spec/defines/openvpn_client_specific_config_spec.rb new file mode 100644 index 00000000..cfdab389 --- /dev/null +++ b/spec/defines/openvpn_client_specific_config_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe 'openvpn::client_specific_config', :type => :define do + let(:title) { 'test_client' } + let(:params) { { 'server' => 'test_server' } } + let(:facts) { { :fqdn => 'somehost', :concat_basedir => '/var/lib/puppet/concat' } } + 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 + + it { should contain_file('/etc/openvpn/test_server/client-configs/test_client') } + + describe "setting no paramter at all" do + it { should contain_file('/etc/openvpn/test_server/client-configs/test_client').with_content(/\A\n\z/) } + end + + describe "setting all parameters" do + let(:params) do + {:server => 'test_server', + :iroute => ['10.0.1.0 255.255.255.0'], + :ifconfig => '10.10.10.2 255.255.255.0', + :dhcp_options => ['DNS 8.8.8.8']} + end + + it { should contain_file('/etc/openvpn/test_server/client-configs/test_client').with_content(/^iroute 10.0.1.0 255.255.255.0$/) } + it { should contain_file('/etc/openvpn/test_server/client-configs/test_client').with_content(/^ifconfig-push 10.10.10.2 255.255.255.0$/) } + it { should contain_file('/etc/openvpn/test_server/client-configs/test_client').with_content(/^push dhcp-option DNS 8.8.8.8$/) } + end +end diff --git a/spec/defines/openvpn_server_spec.rb b/spec/defines/openvpn_server_spec.rb new file mode 100644 index 00000000..467be6aa --- /dev/null +++ b/spec/defines/openvpn_server_spec.rb @@ -0,0 +1,165 @@ +require 'spec_helper' + +describe 'openvpn::server', :type => :define do + + let(:title) { 'test_server' } + + context "creating a server with the minimum parameters" do + let(:params) { { + 'country' => 'CO', + 'province' => 'ST', + 'city' => 'Some City', + 'organization' => 'example.org', + 'email' => 'testemail@example.org' + } } + + let (:facts) { { + :ipaddress_eth0 => '1.2.3.4', + :network_eth0 => '1.2.3.0', + :netmask_eth0 => '255.255.255.0', + :concat_basedir => '/var/lib/puppet/concat', + :osfamily => 'anything_else' + } } + + # Files associated with a server config + it { should contain_file('/etc/openvpn/test_server').with('ensure' => 'directory')} + it { should contain_file('/etc/openvpn/test_server/client-configs').with('ensure' => 'directory')} + it { should contain_file('/etc/openvpn/test_server/download-configs').with('ensure' => 'directory')} + it { should contain_file('/etc/openvpn/test_server/easy-rsa/vars')} + it { should contain_file('/etc/openvpn/test_server/easy-rsa/openssl.cnf')} + it { should contain_file('/etc/openvpn/test_server/keys').with( + 'ensure' => 'link', + 'target' => '/etc/openvpn/test_server/easy-rsa/keys' + )} + + # Execs to working with certificates + it { should contain_exec('copy easy-rsa to openvpn config folder test_server').with( + 'command' => '/bin/cp -r /usr/share/doc/openvpn/examples/easy-rsa/2.0 /etc/openvpn/test_server/easy-rsa' + )} + it { should contain_exec('generate dh param test_server') } + it { should contain_exec('initca test_server') } + it { should contain_exec('generate server cert test_server') } + + # VPN server config file itself + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^mode\s+server$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^client\-config\-dir\s+\/etc\/openvpn\/test_server\/client\-configs$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^ca\s+\/etc\/openvpn\/test_server\/keys\/ca.crt$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^cert\s+\/etc\/openvpn\/test_server\/keys\/server.crt$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^key\s+\/etc\/openvpn\/test_server\/keys\/server.key$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^dh\s+\/etc\/openvpn\/test_server\/keys\/dh1024.pem$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^proto\s+tcp-server$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^tls-server$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^port\s+1194$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^comp-lzo$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^group\s+nogroup$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^user\s+nobody$/) } + it { should_not contain_file('/etc/openvpn/test_server.conf').with_content(/^log\-append\s+test_server\/openvpn\.log$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^status\s+test_server\/openvpn\-status\.log$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^dev\s+tun0$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^local\s+1\.2\.3\.4$/) } + it { should_not contain_file('/etc/openvpn/test_server.conf').with_content(/^ifconfig-pool-persist/) } + end + + context "creating a server setting all parameters" do + let(:params) { { + 'country' => 'CO', + 'province' => 'ST', + 'city' => 'Some City', + 'organization' => 'example.org', + 'email' => 'testemail@example.org', + 'compression' => 'fake_compression', + 'port' => '123', + 'proto' => 'udp', + 'group' => 'someone', + 'user' => 'someone', + 'logfile' => '/var/log/openvpn/test_server.log', + 'status_log' => '/var/log/openvpn/test_server_status.log', + 'dev' => 'tun1', + 'local' => '2.3.4.5', + 'ipp' => true, + 'server' => '2.3.4.0 255.255.0.0', + 'push' => [ 'dhcp-option DNS 172.31.0.30', 'route 172.31.0.0 255.255.0.0' ] + } } + + let (:facts) { { + :ipaddress_eth0 => '1.2.3.4', + :network_eth0 => '1.2.3.0', + :netmask_eth0 => '255.255.255.0', + :concat_basedir => '/var/lib/puppet/concat' + } } + + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^mode\s+server$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^client\-config\-dir\s+\/etc\/openvpn\/test_server\/client\-configs$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^ca\s+\/etc\/openvpn\/test_server\/keys\/ca.crt$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^cert\s+\/etc\/openvpn\/test_server\/keys\/server.crt$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^key\s+\/etc\/openvpn\/test_server\/keys\/server.key$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^dh\s+\/etc\/openvpn\/test_server\/keys\/dh1024.pem$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^proto\s+udp$/) } + it { should_not contain_file('/etc/openvpn/test_server.conf').with_content(/^proto\s+tls-server$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^port\s+123$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^fake_compression$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^group\s+someone$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^user\s+someone$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^log\-append\s+\/var\/log\/openvpn\/test_server\.log$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^status\s+\/var\/log\/openvpn\/test_server_status\.log$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^dev\s+tun1$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^local\s+2\.3\.4\.5$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^server\s+2\.3\.4\.0\s+255\.255\.0\.0$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^push\s+dhcp-option\s+DNS\s+172\.31\.0\.30$/) } + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^push\s+route\s+172\.31\.0\.0\s+255\.255\.0\.0$/) } + end + + context "when RedHat based machine" do + let(:params) { { + 'country' => 'CO', + 'province' => 'ST', + 'city' => 'Some City', + 'organization' => 'example.org', + 'email' => 'testemail@example.org' + } } + + let(:facts) { { :osfamily => 'RedHat', :concat_basedir => '/var/lib/puppet/concat' } } + + it { should contain_file('/etc/openvpn/test_server/easy-rsa/openssl.cnf').with( + 'ensure' => 'link', + 'target' => '/etc/openvpn/test_server/easy-rsa/openssl-1.0.0.cnf' + )} + + it { should contain_exec('copy easy-rsa to openvpn config folder test_server').with( + 'command' => '/bin/cp -r /usr/share/doc/openvpn-2.2.2/easy-rsa/2.0 /etc/openvpn/test_server/easy-rsa' + )} + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^group\s+nobody$/) } + + end + + context "when Debian based machine" do + let(:params) { { + 'country' => 'CO', + 'province' => 'ST', + 'city' => 'Some City', + 'organization' => 'example.org', + 'email' => 'testemail@example.org' + } } + + let(:facts) { { :osfamily => 'Debian', :concat_basedir => '/var/lib/puppet/concat' } } + + it { should contain_file('/etc/openvpn/test_server/easy-rsa/openssl.cnf').with( + 'ensure' => 'link', + 'target' => '/etc/openvpn/test_server/easy-rsa/openssl-1.0.0.cnf' + )} + + it { should contain_exec('copy easy-rsa to openvpn config folder test_server').with( + 'command' => '/bin/cp -r /usr/share/doc/openvpn/examples/easy-rsa/2.0 /etc/openvpn/test_server/easy-rsa' + )} + + # Configure to start vpn session + it { should contain_concat__fragment('openvpn.default.autostart.test_server').with( + 'content' => "AUTOSTART=\"$AUTOSTART test_server\"\n", + 'target' => '/etc/default/openvpn' + )} + + it { should contain_file('/etc/openvpn/test_server.conf').with_content(/^group\s+nogroup$/) } + + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 00000000..dc7e9f4a --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,2 @@ +require 'rubygems' +require 'puppetlabs_spec_helper/module_spec_helper' diff --git a/templates/client.erb b/templates/client.erb new file mode 100644 index 00000000..021ed617 --- /dev/null +++ b/templates/client.erb @@ -0,0 +1,26 @@ +client +ca keys/ca.crt +cert keys/<%= scope.lookupvar('name') %>.crt +key keys/<%= scope.lookupvar('name') %>.key +dev <%= scope.lookupvar('dev') %> +proto <%= scope.lookupvar('proto') %> +remote <%= scope.lookupvar('remote_host') %> <%= scope.lookupvar('port') %> +<% if scope.lookupvar('compression') != '' -%> +<%= scope.lookupvar('compression') %> +<% end -%> +resolv-retry <%= scope.lookupvar('resolv_retry') %> +<% if scope.lookupvar('nobind') -%> +nobind +<% end -%> +<% if scope.lookupvar('persist_key') -%> +persist-key +<% end -%> +<% if scope.lookupvar('persist_tun') -%> +persist-tun +<% end -%> +<% if scope.lookupvar('mute_replay_warnings') -%> +mute-replay-warnings +<% end -%> +ns-cert-type server +verb <%= scope.lookupvar('verb') %> +mute <%= scope.lookupvar('mute') %> diff --git a/templates/client_specific_config.erb b/templates/client_specific_config.erb new file mode 100644 index 00000000..62cc0e7a --- /dev/null +++ b/templates/client_specific_config.erb @@ -0,0 +1,10 @@ +<% scope.lookupvar('iroute').each do |route| -%> +iroute <%= route %> +<% end -%> +<% if ifconfig = scope.lookupvar('ifconfig') -%> +ifconfig-push <%= ifconfig %> +<% end -%> +<% scope.lookupvar('dhcp_options').each do |option| -%> +push dhcp-option <%= option %> +<% end -%> + diff --git a/templates/etc-default-openvpn.erb b/templates/etc-default-openvpn.erb new file mode 100644 index 00000000..310e462e --- /dev/null +++ b/templates/etc-default-openvpn.erb @@ -0,0 +1,20 @@ +# This is the configuration file for /etc/init.d/openvpn + +# +# Start only these VPNs automatically via init script. +# Allowed values are "all", "none" or space separated list of +# names of the VPNs. If empty, "all" is assumed. +# +#AUTOSTART="all" +#AUTOSTART="none" +#AUTOSTART="home office" +# +# Refresh interval (in seconds) of default status files +# located in /var/run/openvpn.$NAME.status +# Defaults to 10, 0 disables status file generation +# +#STATUSREFRESH=10 +#STATUSREFRESH=0 +# Optional arguments to openvpn's command line +OPTARGS="" +AUTOSTART="" diff --git a/templates/server.erb b/templates/server.erb new file mode 100644 index 00000000..6ef13263 --- /dev/null +++ b/templates/server.erb @@ -0,0 +1,37 @@ +mode server +client-config-dir /etc/openvpn/<%= scope.lookupvar('name') %>/client-configs +ca /etc/openvpn/<%= scope.lookupvar('name') %>/keys/ca.crt +cert /etc/openvpn/<%= scope.lookupvar('name') %>/keys/server.crt +key /etc/openvpn/<%= scope.lookupvar('name') %>/keys/server.key +dh /etc/openvpn/<%= scope.lookupvar('name') %>/keys/dh1024.pem +<% if scope.lookupvar('proto') == 'tcp' -%> +proto <%= scope.lookupvar('proto') %>-server +<% else -%> +proto <%= scope.lookupvar('proto') %> +<% end -%> +port <%= scope.lookupvar('port') %> +<% if scope.lookupvar('tls_server') -%> +tls-server +<% end -%> +<% if scope.lookupvar('compression') != '' -%> +<%= scope.lookupvar('compression') %> +<% end -%> +group <%= scope.lookupvar('group_to_set') %> +user <%= scope.lookupvar('user') %> +<% if scope.lookupvar('logfile') -%> +log-append <%= scope.lookupvar('logfile') %> +<% end -%> +status <%= scope.lookupvar('status_log') %> +dev <%= scope.lookupvar('dev') %> +<% if scope.lookupvar('local') != '' -%> +local <%= scope.lookupvar('local') %> +<% end -%> +<% if scope.lookupvar('ipp') -%> +ifconfig-pool-persist <%= scope.lookupvar('name') %>/vpn-ipp.txt +<% end -%> +<% if scope.lookupvar('server') != '' -%> +server <%= scope.lookupvar('server') %> +<% end -%> +<% scope.lookupvar('push').each do |item| -%> +push <%= item %> +<% end -%> diff --git a/templates/vars.erb b/templates/vars.erb new file mode 100644 index 00000000..20448b8b --- /dev/null +++ b/templates/vars.erb @@ -0,0 +1,68 @@ +# easy-rsa parameter settings + +# NOTE: If you installed from an RPM, +# don't edit this file in place in +# /usr/share/openvpn/easy-rsa -- +# instead, you should copy the whole +# easy-rsa directory to another location +# (such as /etc/openvpn) so that your +# edits will not be wiped out by a future +# OpenVPN package upgrade. + +# This variable should point to +# the top level of the easy-rsa +# tree. +export EASY_RSA="/etc/openvpn/<%= @name %>/easy-rsa" + +# +# This variable should point to +# the requested executables +# +export OPENSSL="openssl" +export PKCS11TOOL="pkcs11-tool" +export GREP="grep" + + +# This variable should point to +# the openssl.cnf file included +# with easy-rsa. +export KEY_CONFIG=`$EASY_RSA/whichopensslcnf $EASY_RSA` + +# Edit this variable to point to +# your soon-to-be-created key +# directory. +# +# WARNING: clean-all will do +# a rm -rf on this directory +# so make sure you define +# it correctly! +export KEY_DIR="$EASY_RSA/keys" + +# Issue rm -rf warning +echo NOTE: If you run ./clean-all, I will be doing a rm -rf on $KEY_DIR + +# PKCS11 fixes +export PKCS11_MODULE_PATH="dummy" +export PKCS11_PIN="dummy" + +# Increase this to 2048 if you +# are paranoid. This will slow +# down TLS negotiation performance +# as well as the one-time DH parms +# generation process. +export KEY_SIZE=1024 + +# In how many days should the root CA key expire? +export CA_EXPIRE=3650 + +# In how many days should certificates expire? +export KEY_EXPIRE=3650 + +# These are the default values for fields +# which will be placed in the certificate. +# Don't leave any of these fields blank. +export KEY_COUNTRY="<%= @country %>" +export KEY_PROVINCE="<%= @province %>" +export KEY_CITY="<%= @city %>" +export KEY_ORG="<%= @organization %>" +export KEY_EMAIL="<%= @email %>" diff --git a/vagrant/client.pp b/vagrant/client.pp new file mode 100644 index 00000000..7ebeb1d7 --- /dev/null +++ b/vagrant/client.pp @@ -0,0 +1,5 @@ +node default { + + package { 'openvpn': ensure => installed; } + +} diff --git a/vagrant/server.pp b/vagrant/server.pp new file mode 100644 index 00000000..a95def06 --- /dev/null +++ b/vagrant/server.pp @@ -0,0 +1,23 @@ +node default { + openvpn::server { 'winterthur': + country => 'CH', + province => 'ZH', + city => 'Winterthur', + organization => 'example.org', + email => 'root@example.org', + server => '10.200.200.0 255.255.255.0' + } + + openvpn::client { 'client1': + server => 'winterthur'; + } + + openvpn::client_specific_config { 'client1': + server => 'winterthur', + ifconfig => '10.200.200.100 255.255.255.0' + } + + openvpn::client { 'client2': + server => 'winterthur'; + } +} -- cgit v1.2.3