acme-cli 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/CHANGELOG.md +83 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +170 -0
- data/Rakefile +6 -0
- data/acme-cli.gemspec +35 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/acme-cli +5 -0
- data/letsencrypt-cli.gemspec +35 -0
- data/lib/letsencrypt-cli.rb +1 -0
- data/lib/letsencrypt/cli.rb +10 -0
- data/lib/letsencrypt/cli/acme_wrapper.rb +177 -0
- data/lib/letsencrypt/cli/app.rb +116 -0
- data/lib/letsencrypt/cli/version.rb +5 -0
- metadata +232 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2c369f88a826a9465922871deb4fbd8b3c232414
|
4
|
+
data.tar.gz: c706f4fa29eed7d87f3a3e421e4bcd7cea0fa43c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bcd2a95fea94d400d9e118c8c68034e12ddf0dd9f6e768367c33f8e0d454ab2e187502191c593c820d558e34da91be8898ba5b4c03ec0e44b0d11b871eba3218
|
7
|
+
data.tar.gz: c9dec9b4a406dc85135d1d746e0bffde678d3fdcfe4fc354496bc6203104ff0e459daa18b3703977405f2c7e70195ec0ca61ecee85c1aaa5ddc397466d5507df
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
language: ruby
|
2
|
+
sudo: false
|
3
|
+
cache: bundler
|
4
|
+
|
5
|
+
rvm:
|
6
|
+
- ruby-head
|
7
|
+
- 2.3.0
|
8
|
+
- 2.2.4
|
9
|
+
# - 2.1
|
10
|
+
# - 2.0
|
11
|
+
|
12
|
+
# before_script:
|
13
|
+
# - "bundle exec rake db:schema:load RAILS_ENV=test"
|
14
|
+
|
15
|
+
script:
|
16
|
+
- bundle exec rspec
|
17
|
+
# - ./exe/letsencrypt-cli
|
18
|
+
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
## [v0.6.0](https://github.com/zealot128/ruby-acme-cli/tree/v0.6.0)
|
4
|
+
|
5
|
+
* Rename to acme-client for trademark reasons (#22)
|
6
|
+
|
7
|
+
## [v0.5.0](https://github.com/zealot128/ruby-letsencrypt-cli/tree/v0.5.0)
|
8
|
+
|
9
|
+
* fix for CSR generated using a pre-1.0.2 OpenSSL with a client that doesn't properly specify the CSR version. See https://community.letsencrypt.org/t/openssl-bug-information/19591 (Acme::Client::Error::Malformed) https://github.com/zealot128/ruby-letsencrypt-cli/commit/b7fd1d592e9a74905f5067b64e0ac88a526cfeed
|
10
|
+
* explicitly require colorize Gem so color support does work https://github.com/zealot128/ruby-letsencrypt-cli/commit/b43510d1be1a495923ea8e27051b3a0bae4e23b0
|
11
|
+
|
12
|
+
## [v0.4.1](https://github.com/zealot128/ruby-letsencrypt-cli/tree/v0.4.1)
|
13
|
+
|
14
|
+
* fix renewing via manage command when certificate is not expired https://github.com/zealot128/ruby-letsencrypt-cli/commit/8e6b9cd4a2b1d0caa7a85d6ead410b98555cb499
|
15
|
+
* require logger in beginning of file
|
16
|
+
|
17
|
+
## [v0.4.0](https://github.com/zealot128/ruby-letsencrypt-cli/tree/v0.4.0)
|
18
|
+
|
19
|
+
* New ``--sub-directory`` option to use that instead of first domain name
|
20
|
+
* Solves issue #10 -- certificate_exists_and_valid_and_all_domains_included? returns true when cert is expired
|
21
|
+
|
22
|
+
## [v0.3.0](https://github.com/zealot128/ruby-letsencrypt-cli/tree/v0.3.0)
|
23
|
+
|
24
|
+
* Certificate creation checks if existing certificate includes all requested domains. If at least one is missing, a new cert will be requested
|
25
|
+
* Added Ruby 2.3.0 and Ruby head to the build matrix
|
26
|
+
|
27
|
+
[Full Changelog](https://github.com/zealot128/ruby-letsencrypt-cli/compare/v0.2.0...v0.3.0)
|
28
|
+
|
29
|
+
## [v0.2.0](https://github.com/zealot128/ruby-letsencrypt-cli/tree/v0.2.0)
|
30
|
+
|
31
|
+
[Full Changelog](https://github.com/zealot128/ruby-letsencrypt-cli/compare/v0.1.4...v0.2.0)
|
32
|
+
|
33
|
+
**Closed issues:**
|
34
|
+
|
35
|
+
- cf1e0d9 Exit code 2, if certificate is still valid
|
36
|
+
|
37
|
+
**Merged pull requests:**
|
38
|
+
|
39
|
+
- Apply strict permissions on private key [\#4](https://github.com/zealot128/ruby-letsencrypt-cli/pull/4) ([zygiss](https://github.com/zygiss))
|
40
|
+
- Fix typo in README [\#2](https://github.com/zealot128/ruby-letsencrypt-cli/pull/2) ([kenrick](https://github.com/kenrick))
|
41
|
+
|
42
|
+
## [v0.1.4](https://github.com/zealot128/ruby-letsencrypt-cli/tree/v0.1.4) (2015-12-08)
|
43
|
+
|
44
|
+
* require higher acme-client version, that generated correct fullchain certs.
|
45
|
+
fullchain.pem is chain.pem + cert.pem, should be cert.pem + chain.pem [\#1](https://github.com/zealot128/ruby-letsencrypt-cli/issues/1)
|
46
|
+
|
47
|
+
[Full Changelog](https://github.com/zealot128/ruby-letsencrypt-cli/compare/v0.1.3...v0.1.4)
|
48
|
+
|
49
|
+
## [v0.1.3](https://github.com/zealot128/ruby-letsencrypt-cli/tree/v0.1.3) (2015-12-06)
|
50
|
+
|
51
|
+
* Fixed registration
|
52
|
+
* Added various specs
|
53
|
+
|
54
|
+
[Full Changelog](https://github.com/zealot128/ruby-letsencrypt-cli/compare/v0.1.2...v0.1.3)
|
55
|
+
|
56
|
+
## [v0.1.2](https://github.com/zealot128/ruby-letsencrypt-cli/tree/v0.1.2) (2015-12-05)
|
57
|
+
|
58
|
+
* Added manage command
|
59
|
+
* Improved nginx doc + Ruby installation
|
60
|
+
|
61
|
+
[Full Changelog](https://github.com/zealot128/ruby-letsencrypt-cli/compare/v0.1.1...v0.1.2)
|
62
|
+
|
63
|
+
## [v0.1.1](https://github.com/zealot128/ruby-letsencrypt-cli/tree/v0.1.1) (2015-12-05)
|
64
|
+
|
65
|
+
[Full Changelog](https://github.com/zealot128/ruby-letsencrypt-cli/compare/v0.1.0...v0.1.1)
|
66
|
+
|
67
|
+
* b654469 new command: check PATH_TO_CERT
|
68
|
+
|
69
|
+
## [v0.1.0](https://github.com/zealot128/ruby-letsencrypt-cli/tree/v0.1.0) (2015-12-05)
|
70
|
+
|
71
|
+
* released first public version
|
72
|
+
* added --version flag
|
73
|
+
* added explicit production server
|
74
|
+
|
75
|
+
[Full Changelog](https://github.com/zealot128/ruby-letsencrypt-cli/compare/v0.1.0.beta1...v0.1.0)
|
76
|
+
|
77
|
+
## [v0.1.0.beta1](https://github.com/zealot128/ruby-letsencrypt-cli/tree/v0.1.0.beta1) (2015-12-05)
|
78
|
+
[Full Changelog](https://github.com/zealot128/ruby-letsencrypt-cli/compare/v0.1.0.pre...v0.1.0.beta1)
|
79
|
+
|
80
|
+
## [v0.1.0.pre](https://github.com/zealot128/ruby-letsencrypt-cli/tree/v0.1.0.pre) (2015-12-05)
|
81
|
+
|
82
|
+
|
83
|
+
\* *This Change Log was (partially) automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 TODO: Write your name
|
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,170 @@
|
|
1
|
+
# ACME-Cli
|
2
|
+
|
3
|
+
[](https://travis-ci.org/zealot128/ruby-acme-cli)
|
4
|
+
[](https://badge.fury.io/rb/acme-cli)
|
5
|
+
|
6
|
+
Yet another ACME client (e.g. to use together with Letsencrypt CA to issue TLS certs) for command lines using Ruby.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
* This tool needs Ruby >= 2.1 (as the dependency ``acme-client`` needs that because of use of keyword arguments).
|
11
|
+
* OpenSSL bindings
|
12
|
+
* no sudo! (needs access to webserver-root ``/.well-known/acme-challenges`` alias for all domains - See later section for Nginx example)
|
13
|
+
|
14
|
+
```
|
15
|
+
# check your ruby version:
|
16
|
+
$ ruby --version
|
17
|
+
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-linux]
|
18
|
+
|
19
|
+
$ gem install acme-cli
|
20
|
+
|
21
|
+
$ acme-cli --version
|
22
|
+
0.2.0
|
23
|
+
```
|
24
|
+
|
25
|
+
### Troubleshooting Ruby version
|
26
|
+
|
27
|
+
Unfortunately, most Linux distributions does not ship a current Ruby version (Version 1.9.3 or 2.0). Check, if your ruby version is at least 2.2. Otherwise you need to update the Ruby.
|
28
|
+
|
29
|
+
If you are installing this as a non-root user, you might want to try RVM. Installation itself needs no root, but needs some packages:
|
30
|
+
|
31
|
+
```
|
32
|
+
sudo apt-get install curl bison build-essential zlib1g-dev libssl-dev libreadline6-dev libxml2-dev libgmp-dev git-core
|
33
|
+
```
|
34
|
+
|
35
|
+
To install RVM:
|
36
|
+
|
37
|
+
```
|
38
|
+
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
|
39
|
+
\curl -sSL https://get.rvm.io | bash -s stable --autolibs=disable --auto-dotfiles
|
40
|
+
|
41
|
+
rvm install 2.2
|
42
|
+
source ~/.bashrc # or ~/.profile RVM tells you to reload your shell
|
43
|
+
|
44
|
+
ruby --version
|
45
|
+
```
|
46
|
+
|
47
|
+
Notice: If you are using RVM, all your cronjobs must be run as a login shell, otherwise RVM does not work:
|
48
|
+
|
49
|
+
```cron
|
50
|
+
* * * * * /bin/bash -l -c "acme-cli manage ..."
|
51
|
+
```
|
52
|
+
|
53
|
+
Another way, e.g. on Ubuntu 14.04 might be to use the [Brightbox ppa](https://www.brightbox.com/blog/2015/01/05/ruby-2-2-0-packages-for-ubuntu/).
|
54
|
+
|
55
|
+
## Usage
|
56
|
+
|
57
|
+
Specify ``-t`` to use Letsencrypt test server. Without it, all requests are called against the production server, that might have some more strict rate limiting. If you are just toying around, add the -t flag.
|
58
|
+
|
59
|
+
```bash
|
60
|
+
# show all commands
|
61
|
+
|
62
|
+
acme-cli help
|
63
|
+
|
64
|
+
# show options for an individual command
|
65
|
+
acme-cli help cert
|
66
|
+
|
67
|
+
# creates account_key.json in current_dir
|
68
|
+
acme-cli register -t myemail@example.com
|
69
|
+
|
70
|
+
# authorize one or more domains/subdomains
|
71
|
+
acme-cli authorize -t --webroot-path /var/www/default example.com www.example.com somedir.example.com
|
72
|
+
|
73
|
+
# experimental: authorize all server_names in /etc/nginx/sites-enabled/*
|
74
|
+
acme-cli authorize_all -t --webroot-path /var/www/default
|
75
|
+
|
76
|
+
# create a certificate for domains that are already authorized within the last minutes (1h-2h I think)
|
77
|
+
# the first domain will be the cn subject. All other are subjectAlternateName
|
78
|
+
# if cert.pem already exists, will only create a new one if the old is expired
|
79
|
+
# (30 days before expiration) -> see full help
|
80
|
+
acme-cli help cert
|
81
|
+
|
82
|
+
acme-cli cert -t example.com www.example.com somdir.example.com
|
83
|
+
# will create key.pem fullchain.pem chain.pem and cert.pem in current directory
|
84
|
+
|
85
|
+
# checks validation date of given certificate.
|
86
|
+
# Exists non-zero if:
|
87
|
+
# * not exists (exit 1)
|
88
|
+
# * will expire in more than 30 days (exit code 2)
|
89
|
+
acme-cli check --days-valid 30 cert.pem
|
90
|
+
```
|
91
|
+
|
92
|
+
|
93
|
+
And last but not least, the meta command ``manage`` that integrated check + authorize + cert (intended to be run as cronjob):
|
94
|
+
|
95
|
+
```bash
|
96
|
+
$ acme-cli manage --days-valid 30 \
|
97
|
+
--account-key /home/acme/account_key.pem \
|
98
|
+
--webroot-path /home/acme/webroot/.well-known/acme-challenge \
|
99
|
+
--key-directory /home/acme/certs \
|
100
|
+
example.com www.example.com
|
101
|
+
|
102
|
+
2015-12-05 23:40:04 +0100: Certificate /home/acme/certs/example.com/cert.pem does not exists
|
103
|
+
2015-12-05 23:40:04 +0100: Authorizing example.com...
|
104
|
+
2015-12-05 23:40:04 +0100: existing account key found
|
105
|
+
2015-12-05 23:40:06 +0100: Authorization successful for example.com
|
106
|
+
2015-12-05 23:40:06 +0100: Authorizing www.example.com
|
107
|
+
2015-12-05 23:40:08 +0100: Authorization successful for www.example.com
|
108
|
+
2015-12-05 23:40:08 +0100: creating new private key to /home/acme/certs/example.com/key.pem...
|
109
|
+
2015-12-05 23:40:09 +0100: Certificate successfully created to /home/acme/certs/example.com/fullchain.pem /home/acme/certs/example.com/chain.pem and /home/acme/certs/example.com/cert.pem!
|
110
|
+
2015-12-05 23:40:09 +0100: Certificate valid until: 2016-03-04 21:40:00 UTC
|
111
|
+
|
112
|
+
# Run command again exits immediately:
|
113
|
+
$ acme-cli manage --days-valid 30 --account-key /home/acme/account_key.pem --webroot-path /home/acme/webroot/.wel
|
114
|
+
l-known/acme-challenge --key-directory /home/acme/certs \
|
115
|
+
example.com www.example.com
|
116
|
+
2015-12-05 23:40:17 +0100: Certificate '/home/acme/certs/example.com/cert.pem' valid until 2016-03-04.
|
117
|
+
$ echo $?
|
118
|
+
1
|
119
|
+
```
|
120
|
+
|
121
|
+
This had:
|
122
|
+
|
123
|
+
1. check if /home/acme/certs/example.com/cert.pem exists and expires in less than 30 days (or exit 1 at this point)
|
124
|
+
2. authorize all domains + subdomains
|
125
|
+
3. issue one certificate with those domains and place it under /home/acme/certs/example.com/[key.pem,fullchain.pem,chain.pem,cert.pem]
|
126
|
+
4. exit 0 -> so can be && with ``service nginx reload`` or mail deliver
|
127
|
+
|
128
|
+
For running as cron, reducing log level to fatal might be desirable: ``acme-cli manage --log-level fatal``.
|
129
|
+
|
130
|
+
## Example integration Nginx:
|
131
|
+
|
132
|
+
```nginx
|
133
|
+
server {
|
134
|
+
listen 80;
|
135
|
+
server_name example.com www.example.com somedir.example.com
|
136
|
+
location /.well-known/acme-challenge {
|
137
|
+
alias /home/acme/webroot/.well-known/acme-challenge;
|
138
|
+
default_type "text/plain";
|
139
|
+
try_files $uri =404;
|
140
|
+
}
|
141
|
+
```
|
142
|
+
|
143
|
+
notice the location - alias. Use this dir with ``--webroot-path`` for authorization.
|
144
|
+
|
145
|
+
Afterwards, use the fullchain.pem and key.pem:
|
146
|
+
|
147
|
+
```nginx
|
148
|
+
server {
|
149
|
+
listen 443 ssl;
|
150
|
+
server_name example.com www.example.com;
|
151
|
+
ssl on;
|
152
|
+
ssl_certificate_key /home/acme/certs/example.com/key.pem;
|
153
|
+
ssl_certificate /home/acme/certs/example.com/fullchain.pem;
|
154
|
+
|
155
|
+
# use the settings from: https://gist.github.com/konklone/6532544
|
156
|
+
```
|
157
|
+
|
158
|
+
## Development
|
159
|
+
|
160
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
161
|
+
|
162
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
163
|
+
|
164
|
+
## Contributing
|
165
|
+
|
166
|
+
1. Fork it ( https://github.com/zealot128/ruby-acme-cli/fork )
|
167
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
168
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
169
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
170
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/acme-cli.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'letsencrypt/cli/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "acme-cli"
|
8
|
+
spec.version = Letsencrypt::Cli::VERSION
|
9
|
+
spec.authors = ["Stefan Wienert"]
|
10
|
+
spec.email = ["stwienert@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{slim ACME (e. g. letsencrypt) client for quickly authorizing (multiple) domains and issuing certificates}
|
13
|
+
spec.homepage = "https://github.com/zealot28/acme-cli"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = '>= 2.0.0'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'acme-client', '>= 0.2.4'
|
23
|
+
spec.add_runtime_dependency 'thor'
|
24
|
+
spec.add_runtime_dependency 'colorize'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'pry'
|
27
|
+
spec.add_development_dependency 'activesupport', '>= 3.0'
|
28
|
+
spec.add_development_dependency 'simplecov'
|
29
|
+
spec.add_development_dependency 'vcr', "~> 3.0"
|
30
|
+
spec.add_development_dependency 'webmock', "~> 1.22"
|
31
|
+
spec.add_development_dependency 'timecop', "~> 0.8"
|
32
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
33
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
35
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "letsencrypt/cli"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/acme-cli
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'letsencrypt/cli/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "letsencrypt-cli"
|
8
|
+
spec.version = Letsencrypt::Cli::VERSION
|
9
|
+
spec.authors = ["Stefan Wienert"]
|
10
|
+
spec.email = ["stwienert@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{slim letsencrypt client for quickly authorizing (multiple) domains and issuing certificates}
|
13
|
+
spec.homepage = "https://github.com/zealot128/letsencrypt-cli"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = '>= 2.0.0'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'acme-client', '>= 0.2.4'
|
23
|
+
spec.add_runtime_dependency 'thor'
|
24
|
+
spec.add_runtime_dependency 'colorize'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'pry'
|
27
|
+
spec.add_development_dependency 'activesupport', '>= 3.0'
|
28
|
+
spec.add_development_dependency 'simplecov'
|
29
|
+
spec.add_development_dependency 'vcr', "~> 3.0"
|
30
|
+
spec.add_development_dependency 'webmock', "~> 1.22"
|
31
|
+
spec.add_development_dependency 'timecop', "~> 0.8"
|
32
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
33
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
35
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'letsencrypt/cli'
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'acme-client'
|
3
|
+
require 'logger'
|
4
|
+
require 'colorize'
|
5
|
+
|
6
|
+
class AcmeWrapper
|
7
|
+
def initialize(options)
|
8
|
+
@options = options
|
9
|
+
if !@options[:color]
|
10
|
+
String.disable_colorization = true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def log(message, severity=:info)
|
15
|
+
@logger ||= Logger.new(STDOUT).tap {|logger|
|
16
|
+
logger.level = Logger::SEV_LABEL.index(@options[:log_level].upcase)
|
17
|
+
logger.formatter = proc do |sev, datetime, progname, msg|
|
18
|
+
"#{datetime.to_s.light_black}: #{msg}\n"
|
19
|
+
end
|
20
|
+
}
|
21
|
+
@logger.send(severity, message)
|
22
|
+
end
|
23
|
+
|
24
|
+
def client
|
25
|
+
@client ||= Acme::Client.new(private_key: account_key, endpoint: endpoint)
|
26
|
+
end
|
27
|
+
|
28
|
+
def authorize(domain)
|
29
|
+
FileUtils.mkdir_p(@options[:webroot_path])
|
30
|
+
log "Authorizing #{domain.blue}.."
|
31
|
+
authorization = client.authorize(domain: domain)
|
32
|
+
|
33
|
+
challenge = authorization.http01
|
34
|
+
|
35
|
+
challenge_file = File.join(@options[:webroot_path], challenge.filename.split('/').last)
|
36
|
+
log "Writing challenge to #{challenge_file}", :debug
|
37
|
+
File.write(challenge_file, challenge.file_content)
|
38
|
+
|
39
|
+
challenge.request_verification
|
40
|
+
|
41
|
+
5.times do
|
42
|
+
log "Checking verification...", :debug
|
43
|
+
sleep 1
|
44
|
+
break if challenge.verify_status != 'pending'
|
45
|
+
end
|
46
|
+
if challenge.verify_status == 'valid'
|
47
|
+
log "Authorization successful for #{domain.green}"
|
48
|
+
File.unlink(challenge_file)
|
49
|
+
true
|
50
|
+
else
|
51
|
+
log "Authorization error for #{domain.red}", :error
|
52
|
+
log challenge.error['detail']
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def cert(domains)
|
58
|
+
return if certificate_exists_and_valid_and_all_domains_included?(domains)
|
59
|
+
csr = OpenSSL::X509::Request.new
|
60
|
+
certificate_private_key = find_or_create_pkey(@options[:private_key_path], "private key", @options[:key_length] || 2048)
|
61
|
+
|
62
|
+
csr.subject = OpenSSL::X509::Name.new([
|
63
|
+
# ['C', options[:country], OpenSSL::ASN1::PRINTABLESTRING],
|
64
|
+
# ['ST', options[:state], OpenSSL::ASN1::PRINTABLESTRING],
|
65
|
+
# ['L', options[:city], OpenSSL::ASN1::PRINTABLESTRING],
|
66
|
+
# ['O', options[:organization], OpenSSL::ASN1::UTF8STRING],
|
67
|
+
# ['OU', options[:department], OpenSSL::ASN1::UTF8STRING],
|
68
|
+
# ['CN', options[:common_name], OpenSSL::ASN1::UTF8STRING],
|
69
|
+
# ['emailAddress', options[:email], OpenSSL::ASN1::UTF8STRING]
|
70
|
+
['CN', domains.first, OpenSSL::ASN1::UTF8STRING]
|
71
|
+
])
|
72
|
+
if domains.count > 1
|
73
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
74
|
+
exts = [ ef.create_extension( "subjectAltName", domains.map{|domain| "DNS:#{domain}"}.join(','), false ) ]
|
75
|
+
attrval = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(exts)])
|
76
|
+
attrs = [
|
77
|
+
OpenSSL::X509::Attribute.new('extReq', attrval),
|
78
|
+
OpenSSL::X509::Attribute.new('msExtReq', attrval),
|
79
|
+
]
|
80
|
+
attrs.each do |attr|
|
81
|
+
csr.add_attribute(attr)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
csr.version = 2
|
85
|
+
csr.public_key = certificate_private_key.public_key
|
86
|
+
csr.sign(certificate_private_key, OpenSSL::Digest::SHA256.new)
|
87
|
+
certificate = client.new_certificate(csr)
|
88
|
+
File.write(@options[:fullchain_path], certificate.fullchain_to_pem)
|
89
|
+
File.write(@options[:chain_path], certificate.chain_to_pem)
|
90
|
+
File.write(@options[:certificate_path], certificate.to_pem)
|
91
|
+
log "Certificate successfully created to #{@options[:fullchain_path]} #{@options[:chain_path]} and #{@options[:certificate_path]}!".green
|
92
|
+
log "Certificate valid until: #{certificate.x509.not_after}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def check_certificate(path)
|
96
|
+
unless File.exists?(path)
|
97
|
+
log "Certificate #{path} does not exists", :warn
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
cert = OpenSSL::X509::Certificate.new(File.read(path))
|
101
|
+
renew_on = cert.not_after.to_date - @options[:days_valid]
|
102
|
+
log "Certificate '#{path}' valid until #{cert.not_after.to_date}.", :info
|
103
|
+
if Date.today >= renew_on
|
104
|
+
log "Certificate '#{path}' should be renewed!", :warn
|
105
|
+
return false
|
106
|
+
else
|
107
|
+
true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def revoke_certificate(path)
|
112
|
+
unless File.exists?(path)
|
113
|
+
log "Certificate #{path} does not exists", :warn
|
114
|
+
return false
|
115
|
+
end
|
116
|
+
cert = OpenSSL::X509::Certificate.new(File.read(path))
|
117
|
+
if client.revoke_certificate(cert)
|
118
|
+
log "Certificate '#{path}' was revoked", :info
|
119
|
+
end
|
120
|
+
true
|
121
|
+
rescue Acme::Client::Error::Malformed => e
|
122
|
+
log e.message, :error
|
123
|
+
return false
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def certificate_exists_and_valid_and_all_domains_included?(domains)
|
129
|
+
return false if !File.exists?(@options[:certificate_path])
|
130
|
+
cert = OpenSSL::X509::Certificate.new(File.read(@options[:certificate_path]))
|
131
|
+
domains_in_cert = cert.extensions.map(&:to_h).select{|i| i['oid'] == 'subjectAltName' }.map{|i| i['value']}.join(', ').split(/, */).map{|i| i.sub(/^DNS:/, '') } +
|
132
|
+
[ cert.subject.to_s.sub(%r{/CN=}, '') ].uniq.sort
|
133
|
+
missing_domains = domains.sort.uniq - domains_in_cert
|
134
|
+
if missing_domains != []
|
135
|
+
log "Certificate '#{@options[:certificate_path]}' missing domains #{missing_domains.join(' ')}. Existing: #{domains_in_cert.join(' ')}", :warn
|
136
|
+
return false
|
137
|
+
end
|
138
|
+
expires_on = cert.not_after.to_date
|
139
|
+
if expires_on <= Date.today
|
140
|
+
log "Certificate '#{@options[:certificate_path]}' has expired on #{expires_on}.", :warn
|
141
|
+
return false
|
142
|
+
end
|
143
|
+
renew_on = expires_on - @options[:days_valid]
|
144
|
+
if renew_on > Date.today
|
145
|
+
log "Certificate '#{@options[:certificate_path]}' still valid till #{cert.not_after.to_date}.", :warn
|
146
|
+
log "Won't renew until #{renew_on} (#{@options[:days_valid]} days before)", :warn
|
147
|
+
exit 2
|
148
|
+
end
|
149
|
+
|
150
|
+
false
|
151
|
+
end
|
152
|
+
|
153
|
+
def endpoint
|
154
|
+
if @options[:test]
|
155
|
+
"https://acme-staging.api.letsencrypt.org"
|
156
|
+
else
|
157
|
+
"https://acme-v01.api.letsencrypt.org"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def account_key
|
162
|
+
@account_key ||= find_or_create_pkey(@options[:account_key], "account key", @options[:key_length] || 4096)
|
163
|
+
end
|
164
|
+
|
165
|
+
def find_or_create_pkey(file_path, name, length)
|
166
|
+
if File.exists?(file_path)
|
167
|
+
log "existing account key found"
|
168
|
+
OpenSSL::PKey::RSA.new File.read file_path
|
169
|
+
else
|
170
|
+
log "creating new private key to #{file_path}..."
|
171
|
+
private_key = OpenSSL::PKey::RSA.new(length)
|
172
|
+
File.write(file_path, private_key.to_s)
|
173
|
+
File.chmod(0400, file_path)
|
174
|
+
private_key
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'colorize'
|
3
|
+
require 'fileutils'
|
4
|
+
module Letsencrypt
|
5
|
+
module Cli
|
6
|
+
class App < Thor
|
7
|
+
class_option :account_key, desc: "Path to private key file (will be created if not exists)", aliases: "-a", default: 'account_key.pem'
|
8
|
+
class_option :test, desc: "Use staging url of Letsencrypt instead of production server", aliases: "-t", type: :boolean
|
9
|
+
class_option :log_level, desc: "Log Level (debug, info, warn, error, fatal)", default: "info"
|
10
|
+
class_option :color, desc: "Disable colorize", default: true, type: :boolean
|
11
|
+
|
12
|
+
desc 'register EMAIL', 'Register account'
|
13
|
+
method_option :key_length, desc: "Length of generated private key", type: :numeric, default: 4096
|
14
|
+
def register(email)
|
15
|
+
if email.nil? || email == ""
|
16
|
+
wrapper.log "no E-Mail specified!", :fatal
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
if !email[/.*@.*/]
|
20
|
+
wrapper.log "not an email", :fatal
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
registration = wrapper.client.register(contact: "mailto:" + email)
|
24
|
+
registration.agree_terms
|
25
|
+
wrapper.log "Account created, Terms accepted"
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'authorize_all', "Verify all server_names in /etc/nginx/sites-enabled/* (needs read access)"
|
29
|
+
method_option :webroot_path, desc: "Path to mapped .acme-challenge folder (no subdir)", aliases: '-w', required: true
|
30
|
+
method_option :webserver_dir, desc: "Path to webserver configs", default: "/etc/nginx/sites-enabled"
|
31
|
+
def authorize_all
|
32
|
+
lines = Dir[ File.join(@options[:webserver_dir], "*")].map{|file| File.read(file).lines.grep(/^\s*server_name/) }.flatten
|
33
|
+
domains = lines.flatten.map{|i| i.strip.split(/[; ]/).drop(1) }.flatten.reject{|i| i.length < 3 }.uniq
|
34
|
+
authorize(*domains)
|
35
|
+
end
|
36
|
+
|
37
|
+
desc 'authorize [DOMAINS]', 'Authorize all domains'
|
38
|
+
method_option :webroot_path, desc: "Path to mapped .well-known/acme-challenge folder (no subdirs will be created)", aliases: '-w', required: true
|
39
|
+
def authorize(*domains)
|
40
|
+
rc = 0
|
41
|
+
domains.each do |domain|
|
42
|
+
if !wrapper.authorize(domain)
|
43
|
+
rc = 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
if rc != 0
|
47
|
+
exit rc
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "cert [DOMAINS]", "create certificate and private key pair for domains. The first domain is the main CN domain, the reset will be added as SAN. If the given certificate-path already exists, script will exit non-zero if the certificate is still valid until the given number of days before."
|
52
|
+
method_option :private_key_path, desc: "Path to private key. Will be created if non existant", aliases: '-k', default: 'key.pem'
|
53
|
+
method_option :key_length, desc: "Length of private key", default: 2048, type: :numeric
|
54
|
+
method_option :fullchain_path, desc: "Path to fullchain certificate (Nginx) (will be overwritten if exists!)", aliases: '-f', default: 'fullchain.pem'
|
55
|
+
method_option :certificate_path, desc: "Path to certificate (Apache)", aliases: '-c', default: 'cert.pem'
|
56
|
+
method_option :chain_path, desc: "Path to chain (Apache)", aliases: '-n', default: 'chain.pem'
|
57
|
+
method_option :days_valid, desc: "If the --certificate-path already exists, only create new stuff, if that certificate isn't valid for less than the given number of days", default: 30, type: :numeric
|
58
|
+
def cert(*domains)
|
59
|
+
if domains.length == 0
|
60
|
+
wrapper.log "no domains given", :fatal
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
wrapper.cert(domains)
|
64
|
+
end
|
65
|
+
|
66
|
+
desc "check PATH_TO_CERTIFICATE", "checks, if a given certificate exists and is valid until DAYS_VALID"
|
67
|
+
method_option :days_valid, desc: "If the --certificate-path already exists, only create new stuff, if that certificate isn't valid for less than the given number of days", default: 30, type: :numeric
|
68
|
+
def check(path)
|
69
|
+
if !wrapper.check_certificate(path)
|
70
|
+
exit 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "revoke PATH_TO_CERTIFICATE", "revokes a given certificate"
|
75
|
+
def revoke(path)
|
76
|
+
if !wrapper.revoke_certificate(path)
|
77
|
+
exit 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "manage DOMAINS", "meta command that will: check if cert already exists / still valid (exits zero if nothing todo, exits 2 if certificate is still valid) + authorize given domains + issue certificate for given domains"
|
82
|
+
method_option :key_length, desc: "Length of private key", default: 2048, type: :numeric
|
83
|
+
method_option :days_valid, desc: "If the --certificate-path already exists, only create new stuff, if that certificate isn't valid for less than the given number of days", default: 30, type: :numeric
|
84
|
+
method_option :webroot_path, desc: "Path to mapped .well-known/acme-challenge folder (no subdirs will be created)", aliases: '-w', required: true
|
85
|
+
method_option :key_directory, desc: "Base directory for certificate storage.", default: "~/certs/"
|
86
|
+
method_option :sub_directory, desc: "Sub-directory name in base directory where all certs + key are stored. If not set, the first domain name will be used", default: nil
|
87
|
+
def manage(*domains)
|
88
|
+
key_dir = File.join(@options[:key_directory], @options[:sub_directory] || domains.first)
|
89
|
+
FileUtils.mkdir_p(key_dir)
|
90
|
+
@options = @options.merge(
|
91
|
+
:private_key_path => File.join(key_dir, 'key.pem'),
|
92
|
+
:fullchain_path => File.join(key_dir, 'fullchain.pem'),
|
93
|
+
:certificate_path => File.join(key_dir, 'cert.pem'),
|
94
|
+
:chain_path => File.join(key_dir, 'chain.pem'),
|
95
|
+
)
|
96
|
+
if wrapper.check_certificate(@options[:certificate_path])
|
97
|
+
exit 2
|
98
|
+
end
|
99
|
+
authorize(*domains)
|
100
|
+
cert(*domains)
|
101
|
+
end
|
102
|
+
|
103
|
+
map %w[--version -v] => :__print_version
|
104
|
+
desc "--version, -v", "print the version"
|
105
|
+
def __print_version
|
106
|
+
puts Letsencrypt::Cli::VERSION
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def wrapper
|
112
|
+
@wrapper ||= AcmeWrapper.new(options)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
metadata
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acme-cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stefan Wienert
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-02-20 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.2.4
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.4
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: colorize
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activesupport
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
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'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: vcr
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: webmock
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '1.22'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '1.22'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: timecop
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.8'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.8'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: bundler
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '1.7'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '1.7'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rake
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '10.0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '10.0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rspec
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '3.0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '3.0'
|
181
|
+
description:
|
182
|
+
email:
|
183
|
+
- stwienert@gmail.com
|
184
|
+
executables:
|
185
|
+
- acme-cli
|
186
|
+
extensions: []
|
187
|
+
extra_rdoc_files: []
|
188
|
+
files:
|
189
|
+
- ".gitignore"
|
190
|
+
- ".rspec"
|
191
|
+
- ".travis.yml"
|
192
|
+
- CHANGELOG.md
|
193
|
+
- Gemfile
|
194
|
+
- LICENSE.txt
|
195
|
+
- README.md
|
196
|
+
- Rakefile
|
197
|
+
- acme-cli.gemspec
|
198
|
+
- bin/console
|
199
|
+
- bin/setup
|
200
|
+
- exe/acme-cli
|
201
|
+
- letsencrypt-cli.gemspec
|
202
|
+
- lib/letsencrypt-cli.rb
|
203
|
+
- lib/letsencrypt/cli.rb
|
204
|
+
- lib/letsencrypt/cli/acme_wrapper.rb
|
205
|
+
- lib/letsencrypt/cli/app.rb
|
206
|
+
- lib/letsencrypt/cli/version.rb
|
207
|
+
homepage: https://github.com/zealot28/acme-cli
|
208
|
+
licenses:
|
209
|
+
- MIT
|
210
|
+
metadata: {}
|
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: 2.0.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.6.14
|
228
|
+
signing_key:
|
229
|
+
specification_version: 4
|
230
|
+
summary: slim ACME (e. g. letsencrypt) client for quickly authorizing (multiple) domains
|
231
|
+
and issuing certificates
|
232
|
+
test_files: []
|