letsencrypt_webfaction 0.0.1

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
+ SHA1:
3
+ metadata.gz: 426a3c583e582e83ea8402d1b2e8d08803be339f
4
+ data.tar.gz: 58b5b5ac5d36b69847e4366681c0e7e2d68edb4c
5
+ SHA512:
6
+ metadata.gz: 73db72bcf5ed848ecb11358bdee275596fbb5368620f156cd632ff5ff8d3ddf3061646c39bae8003043d55dd3ddf6ea94325c5a4dccd6a6b06a8e5c8d0fa9ecd
7
+ data.tar.gz: ed3e7670be93c3835e1c89ca22f3a43a9aa1bb1f98d3247983335c3b2d459235dc65cbd837445461cb994fdb21483c62b8dc0a1c16e7dab25506c5e32627922d
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ # Ignore Gem builds
2
+ /*.gem
3
+ /pkg
4
+
5
+ # Ignore Gemfile locking
6
+ /Gemfile.lock
7
+
8
+ # Ignore code coverage output.
9
+ /coverage
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,37 @@
1
+ AllCops:
2
+ Exclude:
3
+ # These are autogenerated binstubs.
4
+ - 'bin/*'
5
+ # Keep rubocop from scanning installed gems.
6
+ - 'vendor/**/*'
7
+ # Line length is something that isn't dogmatic. 80 chars is a good recommendation.
8
+ Metrics/LineLength:
9
+ Enabled: false
10
+ # This requires leading comments which can tend to be redundant.
11
+ Style/Documentation:
12
+ Enabled: false
13
+
14
+
15
+ # Extra cops:
16
+
17
+ # Encourages block syntax for things like File.open
18
+ Style/AutoResourceCleanup:
19
+ Enabled: true
20
+
21
+ # Standardize multi-line params.
22
+ Style/FirstArrayElementLineBreak:
23
+ Enabled: true
24
+ Style/FirstHashElementLineBreak:
25
+ Enabled: true
26
+ Style/FirstMethodArgumentLineBreak:
27
+ Enabled: true
28
+ Style/FirstMethodParameterLineBreak:
29
+ Enabled: true
30
+
31
+ # Encourage using public_send to prove that you actually want to bypass visibility.
32
+ Style/Send:
33
+ Enabled: true
34
+
35
+ # If you interpolate a literal, just include it in the string itself.
36
+ Lint/LiteralInInterpolation:
37
+ Enabled: true
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ - 2.2
5
+ - 2.1
6
+ before_install:
7
+ - gem install bundler
8
+ cache: bundler
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in test_gem.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # LetsEncrypt Webfaction
2
+
3
+ LetsEncrypt utility client for WebFaction hosts.
4
+
5
+ This tool simplifies the manual process of using LetsEncrypt on Webfaction hosts. It can be added to cron where it will validate your domains automatically, place the generated certificates in a common folder, and then email you directions and example text to send the WebFaction support team.
6
+
7
+ [![Build Status](https://travis-ci.org/will-in-wi/letsencrypt-webfaction.svg?branch=master)](https://travis-ci.org/will-in-wi/letsencrypt-webfaction)
8
+
9
+ ## Installation
10
+
11
+ Because WebFaction has an unusual Ruby setup with the default Ruby as 1.8, I recommend that you [set up RBEnv](https://github.com/rbenv/rbenv) and [Ruby Build](https://github.com/rbenv/ruby-build#readme) on your WebFaction server to run this script.
12
+
13
+ Once you have done so, install Ruby 2.1+ (probably 2.3.0 at time of writing). Then set the local Ruby and install the Gem. Finally unset the local Ruby so that you don't run into problems.
14
+
15
+ $ rbenv install 2.3.0
16
+ $ rbenv local 2.3.0
17
+ $ gem install letsencrypt_webfaction
18
+ $ rbenv rehash
19
+ $ rm .ruby-version
20
+
21
+ ## Usage
22
+
23
+ Basic example:
24
+
25
+ $ letsencrypt_webfaction --contact me@example.com --domains example.com,www.example.com --public ~/webapps/myapp/public_html/
26
+
27
+ To quickly get a list of parameters, you can call:
28
+
29
+ $ letsencrypt_webfaction --help
30
+
31
+ ### Cron usage
32
+
33
+ Normally, you will run the script manually once to get the certificate, and then you will use Cron to automate future certificate renewal.
34
+
35
+ Your cron task could look something like:
36
+
37
+ 0 4 * */2 * cd /home/williaminwi/Projects/letsencrypt-webfaction && RBENV_ROOT=~/.rbenv RBENV_VERSION=2.3.0 ~/.rbenv/bin/rbenv exec letsencrypt_webfaction --contact you@example.com --domains example.com,www.example.com --public ~/webapps/myapp/
38
+
39
+ This [would run](http://crontab.guru/#0_4_*_*/2_*) at 4 a.m. in Jan, Mar, May, Jul, Sep, and Nov. Certificates expire three months after issuance, so modify as desired.
40
+
41
+ If you have more than one cron task running like this, you may want to set the environment variables at the top of the file, and create a config file containing the contact information.
42
+
43
+ ### Detailed examples
44
+
45
+ Default parameters can be found in [config.defaults.yml](./config.defaults.yml). All of the parameters can be overridden by passing another config file, arguments to the executable, or both. If a config file and arguments are passed, they will be interleaved with the arguments having precedence.
46
+
47
+ A config file needs to be in YAML format and have a subset of the keys in [config.defaults.yml](./config.defaults.yml). If you use a config file, you pass the `--config ./myconfig.yml` parameter.
48
+
49
+ This allows you to set up a cron task for multiple sites with the defaults for all of them (such as your email address) in a config file, and site specific directives in the command. For example:
50
+
51
+ $ letsencrypt_webfaction --config ~/le_config.yml --domains example.com,www.example.com --public ~/webapps/myapp/public_html/
52
+
53
+ This could be run automatically every two months.
54
+
55
+ ### Operation
56
+
57
+ When the code runs, it places verification files into a public directory, validates the domains with LetsEncrypt (or your ACME provider), and then dumps the signed certificate and private key into an output folder. By default, the output folder is `~/le_certs/`, inside which it will create `[domain_name]/[timestamp]/`.
58
+
59
+ After this is done, the utility will dump to stdout instructions regarding how to request that your new certs be installed by the WebFaction admins. This will include sample text and a link to https://help.webfaction.com/.
60
+
61
+ If you use Cron, you should set `MAILTO=youremail@example.com` and `MAILFROM=username@servername.webfaction.com` at the top in order to receive the instructions. The [WebFaction docs](https://docs.webfaction.com/software/general.html#scheduling-tasks-cron) have more details about how to do this.
62
+
63
+ If you see messages containing SyntaxErrors, you are most likely using an old version of Ruby. This utility requires Ruby 2.1+.
64
+
65
+ ### Public folders
66
+
67
+ For this utility to work, it is assumed that there is a folder which is directly served at `http://yourdomain/` into which the ACME verification files can be placed.
68
+
69
+ In the case of a PHP site, such as Drupal or Wordpress, look for the folder with `index.php` in it. This is usually in `/home/myuser/webapps/myapp/`.
70
+
71
+ In the case of a Rails app, look for a folder called `public/`. If you are deploying your app with Capistrano, this could show up in `/home/myuser/webapps/myapp/current/public/`.
72
+
73
+ ## Development
74
+
75
+ To run the script directly from the repository, use:
76
+
77
+ $ ruby -Ilib exe/letsencrypt_webfaction
78
+
79
+ To test certificate issuance, consider using the [LetsEncrypt staging server](https://community.letsencrypt.org/t/testing-against-the-lets-encrypt-staging-environment/6763). This doesn't have the 5 certs per domain every 7 days rate limit. You can add the `--endpoint https://acme-staging.api.letsencrypt.org/` parameter to do so.
80
+
81
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
82
+
83
+ To install this gem onto your local machine, run `bin/rake install`. To release a new version, update the version number in `lib/letsencrypt_webfaction.rb`, and then run `bin/rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). This project uses [Semantic Versioning](http://semver.org/).
84
+
85
+ ## Contributing
86
+
87
+ Bug reports and pull requests are welcome on GitHub at https://github.com/will-in-wi/letsencrypt-webfaction
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ RuboCop::RakeTask.new
7
+
8
+ namespace :test do
9
+ desc 'Run all tests'
10
+ task :all do
11
+ Rake::Task['rubocop'].invoke
12
+ Rake::Task['spec'].invoke
13
+ end
14
+ end
15
+
16
+ task default: 'test:all'
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'letsencrypt_webfaction'
5
+
6
+ require 'pry'
7
+ Pry.start
data/bin/rake ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rake' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("rake", "rake")
data/bin/rspec ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rspec' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("rspec-core", "rspec")
data/bin/rubocop ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rubocop' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("rubocop", "rubocop")
data/bin/setup ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -0,0 +1,7 @@
1
+ key_size: 4096
2
+ # We need an ACME server to talk to, see github.com/letsencrypt/boulder
3
+ endpoint: 'https://acme-v01.api.letsencrypt.org/'
4
+ domains: []
5
+ public: ''
6
+ contact: ''
7
+ output_dir: '~/le_certs/'
@@ -0,0 +1,8 @@
1
+ key_size: 4096
2
+ endpoint: 'https://acme-v01.api.letsencrypt.org/'
3
+ contact: 'me@example.com'
4
+ domains:
5
+ - 'example.com'
6
+ - 'www.example.com'
7
+ # The webroot of the application.
8
+ public: '/home/myuser/webapps/myapp/public_html'
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'letsencrypt_webfaction/application'
4
+
5
+ LetsencryptWebfaction::Application.new.run!
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'letsencrypt_webfaction'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'letsencrypt_webfaction'
8
+ spec.version = LetsencryptWebfaction::VERSION
9
+ spec.authors = ['William Johnston']
10
+ spec.email = ['william@johnstonhaus.us']
11
+
12
+ spec.summary = 'LetsEncrypt utility client for WebFaction hosts.'
13
+ spec.description = 'A tool to simplify the manual process of using ' \
14
+ 'LetsEncrypt on Webfaction hosts. It can be added to ' \
15
+ 'cron where it will validate your domains ' \
16
+ 'automatically, place the generated certs in a common ' \
17
+ 'folder, and then email you directions and example ' \
18
+ 'text to send the WebFaction support team.'
19
+ spec.homepage = 'https://github.com/will-in-wi/letsencrypt-webfaction'
20
+ spec.license = 'MIT'
21
+
22
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ spec.bindir = 'exe'
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = %w(lib)
26
+
27
+ spec.required_ruby_version = '>= 2.1.0'
28
+
29
+ spec.add_runtime_dependency 'acme-client', '~> 0.3.0'
30
+
31
+ spec.add_development_dependency 'bundler', '~> 1.11'
32
+ spec.add_development_dependency 'rake', '~> 10.0'
33
+ spec.add_development_dependency 'rspec', '~> 3.4'
34
+ spec.add_development_dependency 'rubocop', '~> 0.37'
35
+ spec.add_development_dependency 'simplecov', '~> 0.11'
36
+ spec.add_development_dependency 'pry', '~> 0.10'
37
+ end
@@ -0,0 +1,3 @@
1
+ module LetsencryptWebfaction
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,81 @@
1
+ require 'openssl'
2
+ require 'acme/client'
3
+
4
+ require 'letsencrypt_webfaction/args_parser'
5
+ require 'letsencrypt_webfaction/domain_validator'
6
+ require 'letsencrypt_webfaction/certificate_writer'
7
+ require 'letsencrypt_webfaction/instructions'
8
+
9
+ module LetsencryptWebfaction
10
+ class Application
11
+ def initialize
12
+ @options = LetsencryptWebfaction::ArgsParser.new(ARGV)
13
+ end
14
+
15
+ def run!
16
+ # Validate that the correct options were passed.
17
+ validate_options!
18
+
19
+ # Register the private key.
20
+ register_key!
21
+
22
+ # Validate the domains.
23
+ validator.validate!
24
+
25
+ # Write the obtained certificates.
26
+ certificate_writer.write!
27
+
28
+ # Dump help text.
29
+ puts instructions.message
30
+ end
31
+
32
+ private
33
+
34
+ def instructions
35
+ @instructions ||= LetsencryptWebfaction::Instructions.new certificate_writer.output_dir, @options.domains
36
+ end
37
+
38
+ def certificate_writer
39
+ @certificate_writer ||= LetsencryptWebfaction::CertificateWriter.new(@options.output_dir, @options.domains.first, certificate)
40
+ end
41
+
42
+ def certificate
43
+ # We can now request a certificate, you can pass anything that returns
44
+ # a valid DER encoded CSR when calling to_der on it, for example a
45
+ # OpenSSL::X509::Request too.
46
+ @certificate ||= client.new_certificate(csr)
47
+ end
48
+
49
+ def csr
50
+ # We're going to need a certificate signing request. If not explicitly
51
+ # specified, the first name listed becomes the common name.
52
+ @csr ||= Acme::Client::CertificateRequest.new(names: @options.domains)
53
+ end
54
+
55
+ def validator
56
+ @validator ||= LetsencryptWebfaction::DomainValidator.new @options.domains, client, @options.public
57
+ end
58
+
59
+ def client
60
+ @client ||= Acme::Client.new(private_key: private_key, endpoint: @options.endpoint)
61
+ end
62
+
63
+ def register_key!
64
+ # If the private key is not known to the server, we need to register it for the first time.
65
+ registration = client.register(contact: "mailto:#{@options.contact}")
66
+
67
+ # You'll may need to agree to the term (that's up the to the server to require it or not but boulder does by default)
68
+ registration.agree_terms
69
+ end
70
+
71
+ def validate_options!
72
+ return if @options.valid?
73
+ puts @options.errors.values.join("\n")
74
+ exit
75
+ end
76
+
77
+ def private_key
78
+ OpenSSL::PKey::RSA.new(@options.key_size)
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,101 @@
1
+ require 'optparse'
2
+ require 'yaml'
3
+
4
+ require 'letsencrypt_webfaction/args_parser/field'
5
+ require 'letsencrypt_webfaction/args_parser/string_validator'
6
+ require 'letsencrypt_webfaction/args_parser/defined_values_validator'
7
+ require 'letsencrypt_webfaction/args_parser/array_validator'
8
+
9
+ module LetsencryptWebfaction
10
+ class ArgsParser
11
+ BANNER = 'Usage: get_cert [options]'.freeze
12
+ DEFAULTS_PATH = 'config.defaults.yml'.freeze
13
+ VALID_KEY_SIZES = [2048, 4096].freeze
14
+
15
+ FIELDS = [
16
+ Field::IntegerField.new(:key_size, 'Size of private key (e.g. 4096)', [DefinedValuesValidator.new(VALID_KEY_SIZES)]),
17
+ Field.new(:endpoint, 'ACME endpoint (e.g. https://acme-v01.api.letsencrypt.org/)', [StringValidator.new]),
18
+ Field.new(:contact, 'Email address to notify on renewal', [StringValidator.new]),
19
+ Field::ListField.new(:domains, 'Comma separated list of domains. The first one will be the common name.', [ArrayValidator.new]),
20
+ Field.new(:public, 'Location on the filesystem served by the desired site (e.g. ~/webapps/myapp/public_html)', [StringValidator.new]),
21
+ Field.new(:output_dir, 'Location on the filesystem to which the certs will be saved.', [StringValidator.new])
22
+ ].freeze
23
+
24
+ # Set up getters.
25
+ FIELDS.map(&:identifier).each do |field|
26
+ attr_reader field
27
+ end
28
+
29
+ def initialize(options)
30
+ @options = options
31
+
32
+ @errors = {}
33
+
34
+ # Set defaults from default config file.
35
+ load_config!(DEFAULTS_PATH)
36
+
37
+ # TODO: Rework this to not exit on instantiation due to help text.
38
+ parse!
39
+ end
40
+
41
+ def errors
42
+ errors = {}
43
+
44
+ FIELDS.each do |field|
45
+ val = instance_variable_get("@#{field.identifier}")
46
+ next if field.valid? val
47
+ errors[field.identifier] ||= []
48
+ errors[field.identifier] << "Invalid #{field.identifier} '#{val}'"
49
+ end
50
+
51
+ errors
52
+ end
53
+
54
+ def valid?
55
+ errors.empty?
56
+ end
57
+
58
+ private
59
+
60
+ def load_config!(config_path)
61
+ config = YAML.load_file(config_path)
62
+ FIELDS.each do |field|
63
+ instance_variable_set("@#{field.identifier}", config[field.identifier.to_s])
64
+ end
65
+ end
66
+
67
+ def handle_config(opts)
68
+ opts.on('--config=CONFIG', 'Path to config file. Arguments passed to the program will override corresponding directives in the config file.') do |c|
69
+ # Set defaults.
70
+ load_config!(c)
71
+ end
72
+ end
73
+
74
+ def handle_help(opts)
75
+ opts.on('-h', '--help', 'Prints this help') do
76
+ puts opts
77
+ exit
78
+ end
79
+ end
80
+
81
+ def handle_field(opts, field)
82
+ opts.on("--#{field.identifier}=#{field.identifier.upcase}", field.description) do |val|
83
+ instance_variable_set("@#{field.identifier}", field.sanitize(val))
84
+ end
85
+ end
86
+
87
+ def opt_parser
88
+ OptionParser.new do |opts|
89
+ opts.banner = BANNER
90
+
91
+ handle_config(opts)
92
+ handle_help(opts)
93
+ FIELDS.each { |field| handle_field(opts, field) }
94
+ end
95
+ end
96
+
97
+ def parse!
98
+ opt_parser.parse!(@options)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,9 @@
1
+ module LetsencryptWebfaction
2
+ class ArgsParser
3
+ class ArrayValidator
4
+ def valid?(val)
5
+ !val.nil? && !val.empty?
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module LetsencryptWebfaction
2
+ class ArgsParser
3
+ class DefinedValuesValidator
4
+ def initialize(values = [])
5
+ @values = values
6
+ end
7
+
8
+ def valid?(val)
9
+ @values.include? val
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ module LetsencryptWebfaction
2
+ class ArgsParser
3
+ class Field
4
+ attr_reader :identifier, :description, :validators
5
+
6
+ def initialize(identifier, description, validators = [])
7
+ @identifier = identifier
8
+ @description = description
9
+ @validators = validators
10
+ end
11
+
12
+ def sanitize(val)
13
+ val
14
+ end
15
+
16
+ def valid?(val)
17
+ validators.reject { |validator| validator.valid?(val) }.empty?
18
+ end
19
+
20
+ class IntegerField < Field
21
+ def sanitize(val)
22
+ val.to_i
23
+ end
24
+ end
25
+
26
+ class ListField < Field
27
+ def sanitize(val)
28
+ val.split(',').map(&:strip).compact
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ module LetsencryptWebfaction
2
+ class ArgsParser
3
+ class StringValidator
4
+ def valid?(val)
5
+ !val.nil? && val != ''
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,35 @@
1
+ module LetsencryptWebfaction
2
+ class CertificateWriter
3
+ attr_reader :output_dir
4
+
5
+ def initialize(output_dir, domain, certificate)
6
+ @certificate = certificate
7
+
8
+ cert_date = Time.now.strftime('%Y%m%d%H%M%S')
9
+
10
+ expanded_dir = File.expand_path(output_dir)
11
+ @output_dir = File.join(expanded_dir, domain, cert_date)
12
+ end
13
+
14
+ def write!
15
+ create_folder!
16
+
17
+ # Save the certificate and key
18
+ write_file!('privkey.pem', @certificate.request.private_key.to_pem)
19
+ write_file!('cert.pem', @certificate.to_pem)
20
+ write_file!('chain.pem', @certificate.chain_to_pem)
21
+ write_file!('fullchain.pem', @certificate.fullchain_to_pem)
22
+ end
23
+
24
+ private
25
+
26
+ def create_folder!
27
+ # Make sure the output directory exists.
28
+ FileUtils.mkdir_p(output_dir)
29
+ end
30
+
31
+ def write_file!(filename, data)
32
+ File.write(File.join(@output_dir, filename), data)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,53 @@
1
+ module LetsencryptWebfaction
2
+ class DomainValidator
3
+ def initialize(domains, client, public_dir)
4
+ @domains = domains
5
+ @client = client
6
+ @public_dir = File.expand_path(public_dir)
7
+ end
8
+
9
+ def validate!
10
+ write_files!
11
+
12
+ challenges.each(&:request_verification)
13
+
14
+ i = 0
15
+ until all_challenges_valid? || i == 10
16
+ # Wait a bit for the server to make the request, or really just blink, it should be fast.
17
+ sleep(1)
18
+
19
+ i += 1
20
+ end
21
+
22
+ raise 'Failed to verify statuses in 10 seconds.' unless all_challenges_valid?
23
+ end
24
+
25
+ private
26
+
27
+ def authorizations
28
+ @domains.map { |domain| @client.authorize(domain: domain) }
29
+ end
30
+
31
+ def challenges
32
+ @challenges ||= authorizations.map(&:http01)
33
+ end
34
+
35
+ def all_challenges_valid?
36
+ challenges.reject { |challenge| challenge.verify_status == 'valid' }.empty?
37
+ end
38
+
39
+ def write_files!
40
+ challenges.each do |challenge|
41
+ # Save the file. We'll create a public directory to serve it from, and we'll creating the challenge directory.
42
+ FileUtils.mkdir_p(File.join(@public_dir, File.dirname(challenge.filename)))
43
+
44
+ # Then writing the file
45
+ File.write(File.join(@public_dir, challenge.filename), challenge.file_content)
46
+ end
47
+ end
48
+
49
+ def delete_files!
50
+ # TODO
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,35 @@
1
+ module LetsencryptWebfaction
2
+ class Instructions
3
+ def initialize(output_dir, domains)
4
+ @output_dir = output_dir
5
+ @domains = domains
6
+ end
7
+
8
+ def message
9
+ 'LetsEncrypt Webfaction has generated a new certificate for ' \
10
+ "#{to_sentence @domains}. The certificates have been placed in " \
11
+ "#{@output_dir}. You now need to request installation from the " \
12
+ "WebFaction support team.\n\n" \
13
+ 'Go to https://help.webfaction.com, log in, and paste the ' \
14
+ "following text into a new ticket:\n\n" \
15
+ "Please apply the new certificate in #{@output_dir} to " \
16
+ "#{to_sentence @domains}. Thanks!"
17
+ end
18
+
19
+ private
20
+
21
+ # Borrowed (with simplifications) from ActiveSupport.
22
+ def to_sentence(str)
23
+ case str.length
24
+ when 0
25
+ ''
26
+ when 1
27
+ str[0].to_s.dup
28
+ when 2
29
+ "#{str[0]} and #{str[1]}"
30
+ else
31
+ "#{str[0...-1].join(', ')}, and #{str[-1]}"
32
+ end
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,172 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: letsencrypt_webfaction
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - William Johnston
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-02-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: acme-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.3.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.37'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.37'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.11'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.11'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.10'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.10'
111
+ description: A tool to simplify the manual process of using LetsEncrypt on Webfaction
112
+ hosts. It can be added to cron where it will validate your domains automatically,
113
+ place the generated certs in a common folder, and then email you directions and
114
+ example text to send the WebFaction support team.
115
+ email:
116
+ - william@johnstonhaus.us
117
+ executables:
118
+ - letsencrypt_webfaction
119
+ extensions: []
120
+ extra_rdoc_files: []
121
+ files:
122
+ - ".gitignore"
123
+ - ".rspec"
124
+ - ".rubocop.yml"
125
+ - ".travis.yml"
126
+ - Gemfile
127
+ - README.md
128
+ - Rakefile
129
+ - bin/console
130
+ - bin/rake
131
+ - bin/rspec
132
+ - bin/rubocop
133
+ - bin/setup
134
+ - config.defaults.yml
135
+ - config.example.yml
136
+ - exe/letsencrypt_webfaction
137
+ - letsencrypt_webfaction.gemspec
138
+ - lib/letsencrypt_webfaction.rb
139
+ - lib/letsencrypt_webfaction/application.rb
140
+ - lib/letsencrypt_webfaction/args_parser.rb
141
+ - lib/letsencrypt_webfaction/args_parser/array_validator.rb
142
+ - lib/letsencrypt_webfaction/args_parser/defined_values_validator.rb
143
+ - lib/letsencrypt_webfaction/args_parser/field.rb
144
+ - lib/letsencrypt_webfaction/args_parser/string_validator.rb
145
+ - lib/letsencrypt_webfaction/certificate_writer.rb
146
+ - lib/letsencrypt_webfaction/domain_validator.rb
147
+ - lib/letsencrypt_webfaction/instructions.rb
148
+ homepage: https://github.com/will-in-wi/letsencrypt-webfaction
149
+ licenses:
150
+ - MIT
151
+ metadata: {}
152
+ post_install_message:
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: 2.1.0
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ requirements: []
167
+ rubyforge_project:
168
+ rubygems_version: 2.5.1
169
+ signing_key:
170
+ specification_version: 4
171
+ summary: LetsEncrypt utility client for WebFaction hosts.
172
+ test_files: []