cfn-vpn 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4e3b6334f24037b79137a09d3d93325a2513162f4e151b1e565875e74352aac7
4
+ data.tar.gz: d0e7e304876a4bcccab2b082fc8c298f8fc74b0769b4eb9338fdc98b5d5842a1
5
+ SHA512:
6
+ metadata.gz: c75ae0f97eb239c6db309b98d09d5b08a688bde621aa5f23b091face28fab3737369d67d6d40c9f13e7615ba8f58c4ee3553fe24a55715ac0282f889b72946fd
7
+ data.tar.gz: d543e66f8ce62534e64de754d09be061c4a92b50bff6d8ddfbd0f8df03a48f830217c501024986e8810e4f2a300fa503a9631efb7ad0cb4a22c6c47019456df6
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ cfn-vpn-*.gem
data/.travis.yml ADDED
@@ -0,0 +1,17 @@
1
+ sudo: required
2
+ dist: trusty
3
+ language: ruby
4
+ rvm:
5
+ - 2.5
6
+ script:
7
+ - bundle install
8
+ - gem build cfn-vpn.gemspec
9
+ - gem install cfn-vpn-*.gem
10
+ - cfn-vpn help
11
+ deploy:
12
+ provider: rubygems
13
+ api_key: "${RUBYGEMS_API_KEY}"
14
+ gem: cfn-vpn
15
+ on:
16
+ all_branches: true
17
+ condition: $TRAVIS_BRANCH =~ ^develop|master && $TRAVIS_EVENT_TYPE =~ ^push|api$ && $TRAVIS_REPO_SLUG == "base2services/aws-client-vpn"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cfn-vpn.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,75 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cfn-vpn (0.1.0)
5
+ aws-sdk-acm (~> 1, < 2)
6
+ aws-sdk-cloudformation (~> 1, < 2)
7
+ aws-sdk-ec2 (~> 1.95, < 2)
8
+ aws-sdk-ssm (~> 1, < 2)
9
+ cfhighlander (~> 0.9, < 1)
10
+ cfndsl (~> 0.17, < 1)
11
+ thor (~> 0.20)
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ aws-eventstream (1.0.3)
17
+ aws-partitions (1.176.0)
18
+ aws-sdk-acm (1.23.0)
19
+ aws-sdk-core (~> 3, >= 3.56.0)
20
+ aws-sigv4 (~> 1.1)
21
+ aws-sdk-cloudformation (1.23.0)
22
+ aws-sdk-core (~> 3, >= 3.56.0)
23
+ aws-sigv4 (~> 1.1)
24
+ aws-sdk-core (3.56.0)
25
+ aws-eventstream (~> 1.0, >= 1.0.2)
26
+ aws-partitions (~> 1.0)
27
+ aws-sigv4 (~> 1.1)
28
+ jmespath (~> 1.0)
29
+ aws-sdk-ec2 (1.95.0)
30
+ aws-sdk-core (~> 3, >= 3.56.0)
31
+ aws-sigv4 (~> 1.1)
32
+ aws-sdk-kms (1.22.0)
33
+ aws-sdk-core (~> 3, >= 3.56.0)
34
+ aws-sigv4 (~> 1.1)
35
+ aws-sdk-s3 (1.43.0)
36
+ aws-sdk-core (~> 3, >= 3.56.0)
37
+ aws-sdk-kms (~> 1)
38
+ aws-sigv4 (~> 1.1)
39
+ aws-sdk-ssm (1.50.0)
40
+ aws-sdk-core (~> 3, >= 3.56.0)
41
+ aws-sigv4 (~> 1.1)
42
+ aws-sigv4 (1.1.0)
43
+ aws-eventstream (~> 1.0, >= 1.0.2)
44
+ cfhighlander (0.9.0)
45
+ aws-sdk-cloudformation (~> 1, < 2)
46
+ aws-sdk-core (~> 3, < 4)
47
+ aws-sdk-ec2 (~> 1, < 2)
48
+ aws-sdk-s3 (~> 1, < 2)
49
+ cfndsl (~> 0.16, < 1)
50
+ duplicate (~> 1.1)
51
+ git (~> 1.4, < 2)
52
+ highline (>= 1.7.10, < 1.8)
53
+ netaddr (~> 1.5, >= 1.5.1)
54
+ rubyzip (>= 1.2.1, < 2)
55
+ thor (~> 0.20, < 1)
56
+ cfndsl (0.17.0)
57
+ duplicate (1.1.1)
58
+ git (1.5.0)
59
+ highline (1.7.10)
60
+ jmespath (1.4.0)
61
+ netaddr (1.5.1)
62
+ rake (10.5.0)
63
+ rubyzip (1.2.3)
64
+ thor (0.20.3)
65
+
66
+ PLATFORMS
67
+ ruby
68
+
69
+ DEPENDENCIES
70
+ bundler (~> 2.0)
71
+ cfn-vpn!
72
+ rake (~> 10.0)
73
+
74
+ BUNDLED WITH
75
+ 2.0.1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Guslington
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # CfnVpn
2
+
3
+ Manages the resources required to create a client vpn in AWS.
4
+ Uses cloudformation to manage the state of the vpn resources.
5
+
6
+ ## Installation
7
+
8
+ Install `cfn-vpn` gem
9
+
10
+ ```bash
11
+ gem install cfn-vpn
12
+ ```
13
+
14
+ Install [docker](https://docs.docker.com/install/)
15
+
16
+ Docker is required to generate the certificates required for the client vpn.
17
+ The gem uses [openvpn/easy-rsa](https://github.com/OpenVPN/easy-rsa) project in [base2/aws-client-vpn](https://hub.docker.com/r/base2/aws-client-vpn) dokcer image.
18
+
19
+ ## Usage
20
+
21
+ ### help
22
+
23
+ Displays all possible commands
24
+
25
+ ```bash
26
+ Commands:
27
+ cfn-vpn --version, -v # print the version
28
+ cfn-vpn help [COMMAND] # Describe available commands or one specific command
29
+ cfn-vpn init [name] --server-cn=SERVER_CN --subnet-id=SUBNET_ID # Ciinabox configuration initialization
30
+ ```
31
+
32
+ ### init
33
+
34
+ Initialises a new client vpn and creates all required resources to get it running.
35
+
36
+ ```bash
37
+ Usage:
38
+ cfn-vpn init [name] --server-cn=SERVER_CN --subnet-id=SUBNET_ID
39
+
40
+ Options:
41
+ p, [--profile=PROFILE] # AWS Profile
42
+ r, [--region=REGION] # AWS Region
43
+ --server-cn=SERVER_CN # server certificate common name
44
+ [--client-cn=CLIENT_CN] # client certificate common name
45
+ --subnet-id=SUBNET_ID # subnet id to associate your vpn with
46
+ [--cidr=CIDR] # cidr from which to assign client IP addresses
47
+ # Default: 10.250.0.0/16
48
+
49
+ Ciinabox configuration initialization
50
+ ```
51
+
52
+ ### config
53
+
54
+ Downloads the opvn config file for the client vpn
55
+
56
+ ```bash
57
+ Usage:
58
+ cfn-vpn config [name]
59
+
60
+ Options:
61
+ [--profile=PROFILE] # AWS Profile
62
+ [--region=REGION] # AWS Region
63
+ # Default: ap-southeast-2
64
+
65
+ Ciinabox configuration initialization
66
+ ```
67
+
68
+ ## Contributing
69
+
70
+ Bug reports and pull requests are welcome on GitHub at https://github.com/base2services/aws-client-vpn.
71
+
72
+ ## License
73
+
74
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/cfn-vpn.gemspec ADDED
@@ -0,0 +1,48 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "cfnvpn/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cfn-vpn"
8
+ spec.version = CfnVpn::VERSION
9
+ spec.authors = ["Guslington"]
10
+ spec.email = ["guslington@gmail.com"]
11
+
12
+ spec.summary = %q{creates and manages resources for the aws client vpn}
13
+ spec.description = %q{creates and manages resources for the aws client vpn}
14
+ spec.homepage = "https://github.com/base2services/aws-client-vpn"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata["allowed_push_host"] = 'https://rubygems.org'
21
+
22
+ spec.metadata["homepage_uri"] = spec.homepage
23
+ spec.metadata["source_code_uri"] = "https://github.com/base2services/aws-client-vpn"
24
+ else
25
+ raise "RubyGems 2.0 or newer is required to protect against " \
26
+ "public gem pushes."
27
+ end
28
+
29
+ # Specify which files should be added to the gem when it is released.
30
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
32
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
33
+ end
34
+ spec.bindir = "exe"
35
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
+ spec.require_paths = ["lib"]
37
+
38
+ spec.add_dependency "thor", "~> 0.20"
39
+ spec.add_dependency 'cfhighlander', '~> 0.9', '<1'
40
+ spec.add_dependency 'cfndsl', '~> 0.17', '<1'
41
+ spec.add_runtime_dependency 'aws-sdk-ec2', '~> 1.95', '<2'
42
+ spec.add_runtime_dependency 'aws-sdk-acm', '~> 1', '<2'
43
+ spec.add_runtime_dependency 'aws-sdk-ssm', '~> 1', '<2'
44
+ spec.add_runtime_dependency 'aws-sdk-cloudformation', '~> 1', '<2'
45
+
46
+ spec.add_development_dependency "bundler", "~> 2.0"
47
+ spec.add_development_dependency "rake", "~> 10.0"
48
+ end
data/exe/cfn-vpn ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require "cfnvpn"
3
+
4
+ CfnVpn::Cli.start( ARGV )
data/lib/cfnvpn.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'thor'
2
+ require 'cfnvpn/version'
3
+ require 'cfnvpn/init'
4
+ require 'cfnvpn/config'
5
+
6
+ module CfnVpn
7
+ class Cli < Thor
8
+
9
+ map %w[--version -v] => :__print_version
10
+ desc "--version, -v", "print the version"
11
+ def __print_version
12
+ puts CfnVpn::VERSION
13
+ end
14
+
15
+ # Initializes ciinabox configuration
16
+ register CfnVpn::Init, 'init', 'init [name]', 'Ciinabox configuration initialization'
17
+ tasks["init"].options = CfnVpn::Init.class_options
18
+
19
+ register CfnVpn::Config, 'config', 'config [name]', 'Ciinabox configuration initialization'
20
+ tasks["config"].options = CfnVpn::Config.class_options
21
+
22
+ end
23
+
24
+ # Aws.config[:retry_limit] = if ENV.key? 'CFNVPN_AWS_RETRY_LIMIT' then (ENV['CFNVPN_AWS_RETRY_LIMIT'].to_i) else 10 end
25
+
26
+ end
data/lib/cfnvpn/acm.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'aws-sdk-acm'
2
+ require 'fileutils'
3
+
4
+ module CfnVpn
5
+ class Acm
6
+
7
+ def initialize(region,cert_dir)
8
+ @client = Aws::ACM::Client.new(region: region)
9
+ @cert_dir = cert_dir
10
+ end
11
+
12
+ def import_certificate(cert,key,ca)
13
+ cert_body = load_certificate(cert)
14
+ key_body = load_certificate(key)
15
+ ca_body = load_certificate(ca)
16
+
17
+ resp = @client.import_certificate({
18
+ certificate: cert_body,
19
+ private_key: key_body,
20
+ certificate_chain: ca_body
21
+ })
22
+ return resp.certificate_arn
23
+ end
24
+
25
+ def tag_certificate(arn,name,type,cfnvpn_name)
26
+ @client.add_tags_to_certificate({
27
+ certificate_arn: arn,
28
+ tags: [
29
+ { key: "Name", value: name },
30
+ { key: "cfnvpn:name", value: cfnvpn_name },
31
+ { key: "cfnvpn:certificate:type", value: type }
32
+ ]
33
+ })
34
+ end
35
+
36
+ def load_certificate(cert)
37
+ File.read("#{@cert_dir}/#{cert}")
38
+ end
39
+
40
+ def certificate_exists?(name)
41
+ return true
42
+ end
43
+
44
+ def get_certificate(name)
45
+ return 'arn'
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,55 @@
1
+ require 'fileutils'
2
+ require 'cfnvpn/acm'
3
+ require 'cfnvpn/ssm'
4
+ require 'cfnvpn/log'
5
+
6
+ module CfnVpn
7
+ class Certificates
8
+ include CfnVpn::Log
9
+
10
+ def initialize(build_dir,cfnvpn_name)
11
+ @cfnvpn_name = cfnvpn_name
12
+ @config_dir = "#{build_dir}/config"
13
+ @cert_dir = "#{build_dir}/certificates"
14
+ FileUtils.mkdir_p(@cert_dir)
15
+ end
16
+
17
+ def generate(server_cn,client_cn)
18
+ cmd = ["docker", "run", "-it", "--rm"]
19
+ cmd << "-e EASYRSA_REQ_CN=#{server_cn}"
20
+ cmd << "-e EASYRSA_CLIENT_CN=#{client_cn}"
21
+ cmd << "-v #{@cert_dir}:/easy-rsa/output"
22
+ cmd << "base2/aws-client-vpn:3.0.5"
23
+ return `#{cmd.join(' ')}`
24
+ end
25
+
26
+ def upload_certificates(region,cert,type,cn=nil)
27
+ cn = cn.nil? ? cert : cn
28
+ acm = CfnVpn::Acm.new(region, @cert_dir)
29
+ arn = acm.import_certificate("#{cert}.crt", "#{cert}.key", "ca.crt")
30
+ Log.logger.debug "Uploaded #{type} certificate to ACM #{arn}"
31
+ acm.tag_certificate(arn,cn,type,@cfnvpn_name)
32
+ return arn
33
+ end
34
+
35
+ def store_certificate(region,cert)
36
+ ssm = CfnVpn::SSM.new(@cfnvpn_name, region, @cert_dir)
37
+ ssm.put_parameter(cert)
38
+ end
39
+
40
+ def write_certificate(cert_body,name,force)
41
+ file = "#{@config_dir}/#{name}"
42
+ if File.file?(file)
43
+ if force
44
+ Log.logger.warn "overriding existing #{name}"
45
+ File.write(file, cert_body)
46
+ else
47
+ Log.logger.info "#{name} already exists"
48
+ end
49
+ else
50
+ File.write(file, cert_body)
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,49 @@
1
+ require 'cfhighlander.publisher'
2
+ require 'cfhighlander.factory'
3
+ require 'cfhighlander.validator'
4
+
5
+ require 'cfnvpn/version'
6
+
7
+ module CfnVpn
8
+ class CfHiglander
9
+
10
+ def initialize(region, name, config, output_dir)
11
+ @component_name = name
12
+ @region = region
13
+ @config = config
14
+ @cfn_output_format = 'yaml'
15
+ ENV['CFHIGHLANDER_WORKDIR'] = output_dir
16
+ end
17
+
18
+ def render()
19
+ component = load_component(@component_name)
20
+ compiled = compile_component(component)
21
+ validate_component(component,compiled.cfn_template_paths)
22
+ cfn_template_paths = compiled.cfn_template_paths
23
+ return cfn_template_paths.select { |path| path.match(@component_name) }.first
24
+ end
25
+
26
+ private
27
+
28
+ def load_component(component_name)
29
+ factory = Cfhighlander::Factory::ComponentFactory.new
30
+ component = factory.loadComponentFromTemplate(component_name)
31
+ component.config = @config
32
+ component.version = CfnVpn::VERSION
33
+ component.load()
34
+ return component
35
+ end
36
+
37
+ def compile_component(component)
38
+ component_compiler = Cfhighlander::Compiler::ComponentCompiler.new(component)
39
+ component_compiler.compileCloudFormation(@cfn_output_format)
40
+ return component_compiler
41
+ end
42
+
43
+ def validate_component(component,template_paths)
44
+ component_validator = Cfhighlander::Cloudformation::Validator.new(component)
45
+ component_validator.validate(template_paths, @cfn_output_format)
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ require 'aws-sdk-ec2'
2
+ require 'cfnvpn/log'
3
+
4
+ module CfnVpn
5
+ class ClientVpn
6
+ include CfnVpn::Log
7
+
8
+ def initialize(name,region)
9
+ @client = Aws::EC2::Client.new(region: region)
10
+ @name = name
11
+ end
12
+
13
+ def get_endpoint()
14
+ resp = @client.describe_client_vpn_endpoints({
15
+ filters: [{ name: "tag:cfnvpn:name", values: [@name] }]
16
+ })
17
+ if resp.client_vpn_endpoints.empty?
18
+ Log.logger.error "unable to find endpoint with tag Key: cfnvpn:name with Value: #{@name}"
19
+ raise "Unable to find client vpn"
20
+ end
21
+ resp.client_vpn_endpoints.first
22
+ end
23
+
24
+ def get_endpoint_id()
25
+ return get_endpoint().client_vpn_endpoint_id
26
+ end
27
+
28
+ def get_config(endpoint_id)
29
+ resp = @client.export_client_vpn_client_configuration({
30
+ client_vpn_endpoint_id: endpoint_id
31
+ })
32
+ return resp.client_configuration
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,75 @@
1
+ require 'aws-sdk-cloudformation'
2
+ require 'fileutils'
3
+ require 'cfnvpn/version'
4
+ require 'cfnvpn/log'
5
+
6
+ module CfnVpn
7
+ class Cloudformation
8
+ include CfnVpn::Log
9
+
10
+ def initialize(region,name)
11
+ @name = name
12
+ @stack_name = "#{@name}-cfnvpn"
13
+ @client = Aws::CloudFormation::Client.new(region: region)
14
+ end
15
+
16
+ # TODO: check for REVIEW_IN_PROGRESS
17
+ def does_cf_stack_exist()
18
+ begin
19
+ resp = @client.describe_stacks({
20
+ stack_name: @stack_name,
21
+ })
22
+ rescue Aws::CloudFormation::Errors::ValidationError
23
+ return false
24
+ end
25
+ return resp.size > 0
26
+ end
27
+
28
+ def get_change_set_type()
29
+ return does_cf_stack_exist() ? 'UPDATE' : 'CREATE'
30
+ end
31
+
32
+ def create_change_set(template_path)
33
+ change_set_name = "#{@stack_name}-#{CfnVpn::CHANGE_SET_VERSION}"
34
+ change_set_type = get_change_set_type()
35
+ template_body = File.read(template_path)
36
+ Log.logger.debug "Creating changeset"
37
+ change_set = @client.create_change_set({
38
+ stack_name: @stack_name,
39
+ template_body: template_body,
40
+ tags: [
41
+ {
42
+ key: "cfnvpn:version",
43
+ value: CfnVpn::VERSION,
44
+ },
45
+ {
46
+ key: "cfnvpn:name",
47
+ value: @name,
48
+ }
49
+ ],
50
+ change_set_name: change_set_name,
51
+ change_set_type: change_set_type
52
+ })
53
+ return change_set, change_set_type
54
+ end
55
+
56
+ def wait_for_changeset(change_set_id)
57
+ Log.logger.debug "Waiting for changeset to be created"
58
+ @client.wait_until :change_set_create_complete, change_set_name: change_set_id
59
+ end
60
+
61
+ def execute_change_set(change_set_id)
62
+ Log.logger.debug "Executing the changeset"
63
+ stack = @client.execute_change_set({
64
+ change_set_name: change_set_id
65
+ })
66
+ end
67
+
68
+ def wait_for_execute(change_set_type)
69
+ waiter = change_set_type == 'CREATE' ? :stack_create_complete : :stack_update_complete
70
+ Log.logger.info "Waiting for changeset to #{change_set_type}"
71
+ resp = @client.wait_until waiter, stack_name: @stack_name
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,77 @@
1
+ require 'cfnvpn/clientvpn'
2
+ require 'cfnvpn/ssm'
3
+ require 'cfnvpn/log'
4
+
5
+ module CfnVpn
6
+ class Config < Thor::Group
7
+ include Thor::Actions
8
+ include CfnVpn::Log
9
+
10
+ argument :name
11
+
12
+ class_option :profile, desc: 'AWS Profile'
13
+ class_option :region, default: ENV['AWS_REGION'], desc: 'AWS Region'
14
+ class_option :force, default: false, type: :boolean, desc: 'AWS Region'
15
+ class_option :verbose, desc: 'set log level to debug', type: :boolean
16
+
17
+ def self.source_root
18
+ File.dirname(__FILE__)
19
+ end
20
+
21
+ def set_loglevel
22
+ Log.logger.level = Logger::DEBUG if @options['verbose']
23
+ end
24
+
25
+ def create_config_directory
26
+ @home_dir = "#{ENV['HOME']}/.cfnvpn/#{@name}"
27
+ @config_dir = "#{@home_dir}/config"
28
+ Log.logger.debug("Creating config directory #{@config_dir}")
29
+ FileUtils.mkdir_p(@config_dir)
30
+ end
31
+
32
+ def download_config
33
+ vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
34
+ @endpoint_id = vpn.get_endpoint_id()
35
+ Log.logger.info "downloading client config for #{@endpoint_id}"
36
+ @config = vpn.get_config(@endpoint_id)
37
+ end
38
+
39
+ def download_certificate
40
+ ssm = CfnVpn::SSM.new(@name,@options['region'],@home_dir)
41
+ cert_body = ssm.get_parameter("#{@name}.crt")
42
+ if cert_body
43
+ cert = CfnVpn::Certificates.new(@home_dir,@name)
44
+ cert.write_certificate(cert_body,"#{@name}.crt",@options['force'])
45
+ Log.logger.info "downloaded client certificate #{@name}.crt"
46
+ else
47
+ Log.logger.error "unable to find client certificate #{@name}.crt"
48
+ end
49
+ end
50
+
51
+ def download_key
52
+ ssm = CfnVpn::SSM.new(@name,@options['region'],@home_dir)
53
+ cert_body = ssm.get_parameter("#{@name}.key")
54
+ if cert_body
55
+ cert = CfnVpn::Certificates.new(@home_dir,@name)
56
+ cert.write_certificate(cert_body,"#{@name}.key",@options['force'])
57
+ Log.logger.info "downloaded client key #{@name}.key"
58
+ else
59
+ Log.logger.error "unable to find client certificate #{@name}.crt"
60
+ end
61
+ end
62
+
63
+ def alter_config
64
+ string = (0...8).map { (65 + rand(26)).chr.downcase }.join
65
+ @config.sub!(@endpoint_id, "#{string}.#{@endpoint_id}")
66
+ @config.concat("\n\ncert #{@config_dir}/#{@name}.crt")
67
+ @config.concat("\nkey #{@config_dir}/#{@name}.key\n")
68
+ end
69
+
70
+ def write_config
71
+ config_file = "#{@config_dir}/#{@name}.ovpn"
72
+ File.write(config_file, @config)
73
+ Log.logger.info "downloaded client config #{config_file}"
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,93 @@
1
+ require 'thor'
2
+ require 'fileutils'
3
+ require 'cfnvpn/cloudformation'
4
+ require 'cfnvpn/certificates'
5
+ require 'cfnvpn/cfhighlander'
6
+ require 'cfnvpn/cloudformation'
7
+ require 'cfnvpn/log'
8
+ require 'cfnvpn/clientvpn'
9
+
10
+ module CfnVpn
11
+ class Init < Thor::Group
12
+ include Thor::Actions
13
+ include CfnVpn::Log
14
+
15
+ argument :name
16
+
17
+ class_option :profile, aliases: :p, desc: 'AWS Profile'
18
+ class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
19
+ class_option :verbose, desc: 'set log level to debug', type: :boolean
20
+
21
+ class_option :server_cn, required: true, desc: 'server certificate common name'
22
+ class_option :client_cn, desc: 'client certificate common name'
23
+
24
+ class_option :subnet_id, required: true, desc: 'subnet id to associate your vpn with'
25
+ class_option :cidr, default: '10.250.0.0/16', desc: 'cidr from which to assign client IP addresses'
26
+
27
+ def self.source_root
28
+ File.dirname(__FILE__)
29
+ end
30
+
31
+ def set_loglevel
32
+ Log.logger.level = Logger::DEBUG if @options['verbose']
33
+ end
34
+
35
+ def create_build_directory
36
+ @build_dir = "#{ENV['HOME']}/.cfnvpn/#{@name}"
37
+ Log.logger.debug "creating directory #{@build_dir}"
38
+ FileUtils.mkdir_p(@build_dir)
39
+ end
40
+
41
+ def initialize_config
42
+ @config = {}
43
+ @config['subnet_id'] = @options['subnet_id']
44
+ @config['cidr'] = @options['cidr']
45
+ end
46
+
47
+ def stack_exist
48
+ @cfn = CfnVpn::Cloudformation.new(@options['region'],@name)
49
+ if @cfn.does_cf_stack_exist()
50
+ Log.logger.error "#{@name}-cfnvpn stack already exists in this account in region #{@options['region']}"
51
+ exit 1
52
+ end
53
+ end
54
+
55
+ # create certificates
56
+ def generate_server_certificates
57
+ Log.logger.info "Generating certificates using openvpn easy-rsa"
58
+ cert = CfnVpn::Certificates.new(@build_dir,@name)
59
+ @client_cn = @options['client_cn'] ? @options['client_cn'] : "#{@name}.#{@options['server_cn']}"
60
+ Log.logger.debug cert.generate(@options['server_cn'],@client_cn)
61
+ end
62
+
63
+ def upload_certificates
64
+ cert = CfnVpn::Certificates.new(@build_dir,@name)
65
+ @config['server_cert_arn'] = cert.upload_certificates(@options['region'],'server','server',@options['server_cn'])
66
+ @config['client_cert_arn'] = cert.upload_certificates(@options['region'],@client_cn,'client')
67
+ cert.store_certificate(@options['region'],"#{@client_cn}.crt")
68
+ cert.store_certificate(@options['region'],"#{@client_cn}.key")
69
+ end
70
+
71
+ def deploy_vpn
72
+ template('templates/cfnvpn.cfhighlander.rb.tt', "#{@build_dir}/#{@name}.cfhighlander.rb", @config)
73
+ Log.logger.debug "Generating cloudformation from #{@build_dir}/#{@name}.cfhighlander.rb"
74
+ cfhl = CfnVpn::CfHiglander.new(@options['region'],@name,@config,@build_dir)
75
+ template_path = cfhl.render()
76
+ Log.logger.debug "Cloudformation template #{template_path} generated and validated"
77
+ Log.logger.info "Launching cloudformation stack #{@name}-cfnvpn in #{@options['region']}"
78
+ cfn = CfnVpn::Cloudformation.new(@options['region'],@name)
79
+ change_set, change_set_type = cfn.create_change_set(template_path)
80
+ cfn.wait_for_changeset(change_set.id)
81
+ cfn.execute_change_set(change_set.id)
82
+ cfn.wait_for_execute(change_set_type)
83
+ Log.logger.debug "Changeset #{change_set_type} complete"
84
+ end
85
+
86
+ def finish
87
+ vpn = CfnVpn::ClientVpn.new(@name,@options['region'])
88
+ @endpoint_id = vpn.get_endpoint_id()
89
+ Log.logger.info "Client VPN #{@endpoint_id} created. Run `cfn-vpn config #{@name}` to setup the client config"
90
+ end
91
+
92
+ end
93
+ end
data/lib/cfnvpn/log.rb ADDED
@@ -0,0 +1,38 @@
1
+ require 'logger'
2
+
3
+ module CfnVpn
4
+ module Log
5
+
6
+ def self.colors
7
+ @colors ||= {
8
+ ERROR: 31, # red
9
+ WARN: 33, # yellow
10
+ INFO: 0,
11
+ DEBUG: 32 # grenn
12
+ }
13
+ end
14
+
15
+ def self.logger
16
+ if @logger.nil?
17
+ @logger = Logger.new(STDOUT)
18
+ @logger.level = Logger::INFO
19
+ @logger.formatter = proc do |severity, datetime, progname, msg|
20
+ "\e[#{colors[severity.to_sym]}m#{severity}: - #{msg}\e[0m\n"
21
+ end
22
+ end
23
+ @logger
24
+ end
25
+
26
+ def self.logger=(logger)
27
+ @logger = logger
28
+ end
29
+
30
+ levels = %w(debug info warn error fatal)
31
+ levels.each do |level|
32
+ define_method("#{level.to_sym}") do |msg|
33
+ self.logger.send(level, msg)
34
+ end
35
+ end
36
+
37
+ end
38
+ end
data/lib/cfnvpn/ssm.rb ADDED
@@ -0,0 +1,50 @@
1
+ require 'aws-sdk-ssm'
2
+ require 'fileutils'
3
+ require 'cfnvpn/log'
4
+
5
+ module CfnVpn
6
+ class SSM
7
+ include CfnVpn::Log
8
+
9
+ def initialize(name,region,cert_dir)
10
+ @name = name
11
+ @cert_dir = cert_dir
12
+ @path_prefix = "/cfnvpn/#{@name}"
13
+ @client = Aws::SSM::Client.new(region: region)
14
+ end
15
+
16
+ def get_parameter(cert)
17
+ begin
18
+ resp = @client.get_parameter({
19
+ name: "#{@path_prefix}/#{cert}",
20
+ with_decryption: true
21
+ })
22
+ rescue Aws::SSM::Errors::ParameterNotFound
23
+ Log.logger.debug("Parameter #{@path_prefix}/#{cert} not found")
24
+ return false
25
+ end
26
+ Log.logger.debug("found parameter #{@path_prefix}/#{cert}")
27
+ return resp.parameter.value
28
+ end
29
+
30
+ def put_parameter(cert)
31
+ certificate = File.read("#{@cert_dir}/#{cert}")
32
+ Log.logger.debug("Reading certificate #{@cert_dir}/#{cert}")
33
+ ext = cert.split('.').last
34
+ @client.put_parameter({
35
+ name: "#{@path_prefix}/#{@name}.#{ext}",
36
+ description: "cfn-vpn #{@name} #{cert}",
37
+ value: certificate,
38
+ type: "SecureString",
39
+ overwrite: false,
40
+ tags: [
41
+ { key: "cfnvpn:name", value: @name },
42
+ { key: "cfnvpn:certificate", value: cert }
43
+ ],
44
+ tier: "Advanced",
45
+ })
46
+ Log.logger.info("Stored #{cert} in ssm parameter #{@path_prefix}/#{@name}.#{ext}")
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,14 @@
1
+ CfhighlanderTemplate do
2
+
3
+ Parameters do
4
+ ComponentParam 'EnvironmentName', '<%= @name %>'
5
+ end
6
+
7
+ Component template: 'client-vpn', name: 'vpn', render: Inline do
8
+ parameter name: 'AssociationSubnetId', value: '<%= @config['subnet_id'] %>'
9
+ parameter name: 'ClientCidrBlock', value: '<%= @config['cidr'] %>'
10
+ parameter name: 'ClientCertificateArn', value: '<%= @config['client_cert_arn'] %>'
11
+ parameter name: 'ServerCertificateArn', value: '<%= @config['server_cert_arn'] %>'
12
+ end
13
+
14
+ end
@@ -0,0 +1,4 @@
1
+ module CfnVpn
2
+ VERSION = "0.1.0".freeze
3
+ CHANGE_SET_VERSION = VERSION.gsub('.', '-').freeze
4
+ end
metadata ADDED
@@ -0,0 +1,231 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cfn-vpn
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Guslington
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-06-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.20'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.20'
27
+ - !ruby/object:Gem::Dependency
28
+ name: cfhighlander
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.9'
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '1'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '0.9'
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '1'
47
+ - !ruby/object:Gem::Dependency
48
+ name: cfndsl
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.17'
54
+ - - "<"
55
+ - !ruby/object:Gem::Version
56
+ version: '1'
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '0.17'
64
+ - - "<"
65
+ - !ruby/object:Gem::Version
66
+ version: '1'
67
+ - !ruby/object:Gem::Dependency
68
+ name: aws-sdk-ec2
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '1.95'
74
+ - - "<"
75
+ - !ruby/object:Gem::Version
76
+ version: '2'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.95'
84
+ - - "<"
85
+ - !ruby/object:Gem::Version
86
+ version: '2'
87
+ - !ruby/object:Gem::Dependency
88
+ name: aws-sdk-acm
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '1'
94
+ - - "<"
95
+ - !ruby/object:Gem::Version
96
+ version: '2'
97
+ type: :runtime
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1'
104
+ - - "<"
105
+ - !ruby/object:Gem::Version
106
+ version: '2'
107
+ - !ruby/object:Gem::Dependency
108
+ name: aws-sdk-ssm
109
+ requirement: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - "~>"
112
+ - !ruby/object:Gem::Version
113
+ version: '1'
114
+ - - "<"
115
+ - !ruby/object:Gem::Version
116
+ version: '2'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '1'
124
+ - - "<"
125
+ - !ruby/object:Gem::Version
126
+ version: '2'
127
+ - !ruby/object:Gem::Dependency
128
+ name: aws-sdk-cloudformation
129
+ requirement: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - "~>"
132
+ - !ruby/object:Gem::Version
133
+ version: '1'
134
+ - - "<"
135
+ - !ruby/object:Gem::Version
136
+ version: '2'
137
+ type: :runtime
138
+ prerelease: false
139
+ version_requirements: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: '1'
144
+ - - "<"
145
+ - !ruby/object:Gem::Version
146
+ version: '2'
147
+ - !ruby/object:Gem::Dependency
148
+ name: bundler
149
+ requirement: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - "~>"
152
+ - !ruby/object:Gem::Version
153
+ version: '2.0'
154
+ type: :development
155
+ prerelease: false
156
+ version_requirements: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - "~>"
159
+ - !ruby/object:Gem::Version
160
+ version: '2.0'
161
+ - !ruby/object:Gem::Dependency
162
+ name: rake
163
+ requirement: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - "~>"
166
+ - !ruby/object:Gem::Version
167
+ version: '10.0'
168
+ type: :development
169
+ prerelease: false
170
+ version_requirements: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - "~>"
173
+ - !ruby/object:Gem::Version
174
+ version: '10.0'
175
+ description: creates and manages resources for the aws client vpn
176
+ email:
177
+ - guslington@gmail.com
178
+ executables:
179
+ - cfn-vpn
180
+ extensions: []
181
+ extra_rdoc_files: []
182
+ files:
183
+ - ".gitignore"
184
+ - ".travis.yml"
185
+ - Gemfile
186
+ - Gemfile.lock
187
+ - LICENSE.txt
188
+ - README.md
189
+ - Rakefile
190
+ - cfn-vpn.gemspec
191
+ - exe/cfn-vpn
192
+ - lib/cfnvpn.rb
193
+ - lib/cfnvpn/acm.rb
194
+ - lib/cfnvpn/certificates.rb
195
+ - lib/cfnvpn/cfhighlander.rb
196
+ - lib/cfnvpn/clientvpn.rb
197
+ - lib/cfnvpn/cloudformation.rb
198
+ - lib/cfnvpn/config.rb
199
+ - lib/cfnvpn/init.rb
200
+ - lib/cfnvpn/log.rb
201
+ - lib/cfnvpn/ssm.rb
202
+ - lib/cfnvpn/templates/cfnvpn.cfhighlander.rb.tt
203
+ - lib/cfnvpn/version.rb
204
+ homepage: https://github.com/base2services/aws-client-vpn
205
+ licenses:
206
+ - MIT
207
+ metadata:
208
+ allowed_push_host: https://rubygems.org
209
+ homepage_uri: https://github.com/base2services/aws-client-vpn
210
+ source_code_uri: https://github.com/base2services/aws-client-vpn
211
+ post_install_message:
212
+ rdoc_options: []
213
+ require_paths:
214
+ - lib
215
+ required_ruby_version: !ruby/object:Gem::Requirement
216
+ requirements:
217
+ - - ">="
218
+ - !ruby/object:Gem::Version
219
+ version: '0'
220
+ required_rubygems_version: !ruby/object:Gem::Requirement
221
+ requirements:
222
+ - - ">="
223
+ - !ruby/object:Gem::Version
224
+ version: '0'
225
+ requirements: []
226
+ rubyforge_project:
227
+ rubygems_version: 2.7.6
228
+ signing_key:
229
+ specification_version: 4
230
+ summary: creates and manages resources for the aws client vpn
231
+ test_files: []