acmesmith 2.7.0 → 2.8.0
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 +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +1 -1
- data/docs/post_issuing_hooks/shell.md +7 -4
- data/lib/acmesmith/certificate.rb +34 -9
- data/lib/acmesmith/certificate_retrieving_service.rb +3 -3
- data/lib/acmesmith/client.rb +48 -32
- data/lib/acmesmith/command.rb +40 -40
- data/lib/acmesmith/ordering_service.rb +6 -9
- data/lib/acmesmith/post_issuing_hooks/shell.rb +2 -2
- data/lib/acmesmith/save_certificate_service.rb +1 -1
- data/lib/acmesmith/storages/base.rb +8 -8
- data/lib/acmesmith/storages/filesystem.rb +16 -16
- data/lib/acmesmith/storages/s3.rb +16 -16
- data/lib/acmesmith/version.rb +1 -1
- metadata +7 -14
- data/.github/FUNDING.yml +0 -2
- data/.github/stale.yml +0 -17
- data/.github/workflows/build.yml +0 -113
- data/.gitignore +0 -12
- data/.rspec +0 -2
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -84
- data/acmesmith.gemspec +0 -33
- data/script/console +0 -14
- data/script/setup +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 252b913c94d04e35760101aedc61410f00bc59c806830f86d1348237c213700b
|
|
4
|
+
data.tar.gz: e494fc15b7bbdbc1b5b59dfcbb654d654c7908defa749106c8e37cd52d3d882e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b075a5fc9d32491774864c8035a6de3b3eb8dd03ccea006330f63cce4beac3c8ca4304b51d6b6d6bf406aacf196424c8dd7aaa7f772226240aa2b0138c40474
|
|
7
|
+
data.tar.gz: c209c626c45fa16f547ca0bea14271bb8c850592b5ef205a7c19269d5c7f0cb941101caaf75e9cdd510e46f5731723bf7cf73abf9878de95171ecd07aed0f5cc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## v2.8.0 (2025-11-11)
|
|
2
|
+
|
|
3
|
+
### Enhancements
|
|
4
|
+
|
|
5
|
+
- post_issuing_hooks/shell: Gained `$CERT_NAME` environment variable which is to replace `$COMMON_NAME` for certificates without CN field. [#76](https://github.com/sorah/acmesmith/pull/76)
|
|
6
|
+
- certificate: gained `name` method (Certificate#name) in addition to Certificate#common_name, for certificates without CN field. Plugin authors are encouraged to migrate on this new API. [#76](https://github.com/sorah/acmesmith/pull/76)
|
|
7
|
+
|
|
8
|
+
### Fixes
|
|
9
|
+
|
|
10
|
+
- A certificate name is now inherited to a new certificate during autorenew and add-san command for stability, otherwise it could be saved under an another name when CA has issued the new certificate with different subject field or SANs field due to its policy/behaviour change; If you're using 3rd party storage plugins, it has to be updated to use Certificate#name instead of Certificate#common_name to support certificates without CN field. [#77](https://github.com/sorah/acmesmith/pull/77)
|
|
11
|
+
|
|
12
|
+
### Updates
|
|
13
|
+
|
|
14
|
+
- Update gemspec to the latest bundler's provided template. This removes certain irrevant files from a released gem package. [#73](https://github.com/sorah/acmesmith/issues/73)
|
|
15
|
+
- Use rubygems.org trusted publishing via GitHub Actions [#73](https://github.com/sorah/acmesmith/issues/73)
|
|
16
|
+
|
|
17
|
+
## v2.7.1 (2025-08-21)
|
|
18
|
+
|
|
19
|
+
### Bug fixes
|
|
20
|
+
|
|
21
|
+
- autorenew: Failed when expired certificates were present. [#72](https://github.com/sorah/acmesmith/issues/72)
|
|
22
|
+
|
|
1
23
|
## v2.7.0 (2025-04-28)
|
|
2
24
|
|
|
3
25
|
### Enhancements
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Acmesmith: A simple, effective ACME v2 client to use with many servers and a cloud
|
|
2
2
|
|
|
3
|
-
](https://github.com/sorah/acmesmith/actions/workflows/build.yml) <a href='https://ko-fi.com/J3J8CKMUU' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://cdn.ko-fi.com/cdn/kofi3.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
Acmesmith is an [ACME (Automatic Certificate Management Environment)](https://github.com/ietf-wg-acme/acme) client that works perfect on environment with multiple servers. This client saves certificate and keys on cloud services (e.g. AWS S3) securely, then allow to deploy issued certificates onto your servers smoothly. This works well on [Let's encrypt](https://letsencrypt.org).
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
# Post Issuing Hook: Shell
|
|
2
2
|
|
|
3
|
-
Execute specified command on a shell. Environment variable `${
|
|
3
|
+
Execute specified command on a shell. Environment variable `${CERT_NAME}` is available.
|
|
4
4
|
|
|
5
5
|
```yaml
|
|
6
6
|
post_issuing_hooks:
|
|
7
7
|
"test.example.com":
|
|
8
8
|
- shell:
|
|
9
|
-
command: mail -s "New cert for ${
|
|
9
|
+
command: mail -s "New cert for ${CERT_NAME} has been issued" user@example.com < /dev/null
|
|
10
10
|
- shell:
|
|
11
|
-
command: touch /tmp/certs-has-been-issued-${
|
|
11
|
+
command: touch /tmp/certs-has-been-issued-${CERT_NAME}
|
|
12
12
|
"admin.example.com":
|
|
13
13
|
- shell:
|
|
14
|
-
command: /usr/bin/dosomethingelse ${
|
|
14
|
+
command: /usr/bin/dosomethingelse ${CERT_NAME}
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## What happened to `${COMMON_NAME}`?
|
|
17
18
|
|
|
19
|
+
In modern CA behavior, certificates may be missing CN field, or entire subject field.
|
|
20
|
+
It is still available, but we recommend to use `${CERT_NAME}` instead, which obtains a certificate name from certain sources.
|
|
@@ -17,10 +17,11 @@ module Acmesmith
|
|
|
17
17
|
# Return Acmesmith::Certificate by an issued certificate
|
|
18
18
|
# @param pem_chain [String]
|
|
19
19
|
# @param csr [Acme::Client::CertificateRequest]
|
|
20
|
+
# @param name [String, nil]
|
|
20
21
|
# @return [Acmesmith::Certificate]
|
|
21
|
-
def self.by_issuance(pem_chain, csr)
|
|
22
|
+
def self.by_issuance(pem_chain, csr, name: nil)
|
|
22
23
|
pems = split_pems(pem_chain)
|
|
23
|
-
new(pems[0], pems[1..-1], csr.private_key, nil, csr)
|
|
24
|
+
new(pems[0], pems[1..-1], csr.private_key, nil, csr, name: name)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
# @param certificate [OpenSSL::X509::Certificate, String]
|
|
@@ -28,7 +29,9 @@ module Acmesmith
|
|
|
28
29
|
# @param private_key [String, OpenSSL::PKey::PKey]
|
|
29
30
|
# @param key_passphrase [String, nil]
|
|
30
31
|
# @param csr [String, OpenSSL::X509::Request, nil]
|
|
31
|
-
|
|
32
|
+
# @param name [String, nil]
|
|
33
|
+
def initialize(certificate, chain, private_key, key_passphrase = nil, csr = nil, name: nil)
|
|
34
|
+
@name = name
|
|
32
35
|
@certificate = case certificate
|
|
33
36
|
when OpenSSL::X509::Certificate
|
|
34
37
|
certificate
|
|
@@ -94,6 +97,8 @@ module Acmesmith
|
|
|
94
97
|
# @return [OpenSSL::X509::Request]
|
|
95
98
|
attr_reader :csr
|
|
96
99
|
|
|
100
|
+
attr_writer :name
|
|
101
|
+
|
|
97
102
|
# Try to decrypt private_key if encrypted.
|
|
98
103
|
# @param pw [String] passphrase for encrypted PEM
|
|
99
104
|
# @raise [PrivateKeyDecrypted] if private_key is decrypted
|
|
@@ -128,18 +133,38 @@ module Acmesmith
|
|
|
128
133
|
chain.map(&:to_pem).join("\n")
|
|
129
134
|
end
|
|
130
135
|
|
|
131
|
-
#
|
|
136
|
+
# Returns a predicted certificate name, taken from common name or first SAN.
|
|
137
|
+
# Note that this value can contain colons (':') if name is taken from non-DNS subject alternative name.
|
|
138
|
+
# @return [String] certificate name
|
|
139
|
+
def name
|
|
140
|
+
@name || common_name || sans.first || all_sans.first
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Returns a certificate common name taken from the certificate subject's CN field.
|
|
144
|
+
# Under the real CA, CNs can be missing. Use #name instead to retrieve the certificate name for most cases.
|
|
145
|
+
# ref. https://github.com/letsencrypt/pebble/pull/491#pullrequestreview-2718607820
|
|
146
|
+
# @return [String, nil] common name
|
|
132
147
|
def common_name
|
|
133
|
-
certificate.subject.to_a.assoc('CN')
|
|
148
|
+
certificate.subject.to_a.assoc('CN')&.fetch(1)
|
|
134
149
|
end
|
|
135
150
|
|
|
136
|
-
#
|
|
137
|
-
|
|
151
|
+
# Returns a list of subject alternative names included in the certificate.
|
|
152
|
+
# @return [Array<String>] Subject Alternative Names
|
|
153
|
+
def all_sans
|
|
138
154
|
certificate.extensions.select { |_| _.oid == 'subjectAltName' }.flat_map do |ext|
|
|
139
|
-
ext.value.split(/,\s*/)
|
|
155
|
+
ext.value.split(/,\s*/)
|
|
140
156
|
end
|
|
141
157
|
end
|
|
142
158
|
|
|
159
|
+
# Returns a list of DNS subject alternative names included in the certificate.
|
|
160
|
+
# Strips DNS: prefix from returned values.
|
|
161
|
+
# @return [Array<String>] Subject Alternative Names (dNSname)
|
|
162
|
+
def sans
|
|
163
|
+
all_sans.select do |san|
|
|
164
|
+
san.start_with?('DNS:')
|
|
165
|
+
end.map { |_| _[4..-1] }
|
|
166
|
+
end
|
|
167
|
+
|
|
143
168
|
# @return [String] Version string (consists of NotBefore time & certificate serial)
|
|
144
169
|
def version
|
|
145
170
|
"#{certificate.not_before.utc.strftime('%Y%m%d-%H%M%S')}_#{certificate.serial.to_i.to_s(16)}"
|
|
@@ -147,7 +172,7 @@ module Acmesmith
|
|
|
147
172
|
|
|
148
173
|
# @return [OpenSSL::PKCS12]
|
|
149
174
|
def pkcs12(passphrase)
|
|
150
|
-
OpenSSL::PKCS12.create(passphrase,
|
|
175
|
+
OpenSSL::PKCS12.create(passphrase, name, private_key, certificate, chain)
|
|
151
176
|
end
|
|
152
177
|
|
|
153
178
|
# @return [CertificateExport]
|
|
@@ -3,13 +3,13 @@ require 'acmesmith/certificate'
|
|
|
3
3
|
module Acmesmith
|
|
4
4
|
class CertificateRetrievingService
|
|
5
5
|
# @param acme [Acme::Client]
|
|
6
|
-
# @param
|
|
6
|
+
# @param name [String]
|
|
7
7
|
# @param url [String] ACME Certificate URL
|
|
8
8
|
# @param chain_preferences [Array<Acmesmith::Config::ChainPreference>]
|
|
9
|
-
def initialize(acme,
|
|
9
|
+
def initialize(acme, name, url, chain_preferences: [])
|
|
10
10
|
@acme = acme
|
|
11
11
|
@url = url
|
|
12
|
-
@chain_preferences = chain_preferences.select { |_| _.filter.match?(
|
|
12
|
+
@chain_preferences = chain_preferences.select { |_| _.filter.match?(name) }
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
attr_reader :acme
|
data/lib/acmesmith/client.rb
CHANGED
|
@@ -30,35 +30,35 @@ module Acmesmith
|
|
|
30
30
|
raise NotImplementedError, "Domain authorization in advance is still not available in acme-client (v2). Required authorizations will be performed when ordering certificates"
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
def post_issue_hooks(
|
|
34
|
-
cert =
|
|
33
|
+
def post_issue_hooks(name)
|
|
34
|
+
cert = load_certificate_from_storage(name)
|
|
35
35
|
execute_post_issue_hooks(cert)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def execute_post_issue_hooks(certificate)
|
|
39
|
-
hooks = config.post_issuing_hooks(certificate.
|
|
39
|
+
hooks = config.post_issuing_hooks(certificate.name)
|
|
40
40
|
return if hooks.empty?
|
|
41
|
-
puts "=> Executing post issuing hooks for CN=#{certificate.
|
|
41
|
+
puts "=> Executing post issuing hooks for CN=#{certificate.name}"
|
|
42
42
|
hooks.each do |hook|
|
|
43
43
|
hook.run(certificate: certificate)
|
|
44
44
|
end
|
|
45
45
|
puts
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
def certificate_versions(
|
|
49
|
-
storage.list_certificate_versions(
|
|
48
|
+
def certificate_versions(name)
|
|
49
|
+
storage.list_certificate_versions(name).sort
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def certificates_list
|
|
53
53
|
storage.list_certificates.sort
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
def current(
|
|
57
|
-
storage.get_current_certificate_version(
|
|
56
|
+
def current(name)
|
|
57
|
+
storage.get_current_certificate_version(name)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
def get_certificate(
|
|
61
|
-
cert = storage.get_certificate(
|
|
60
|
+
def get_certificate(name, version: 'current', type: 'text')
|
|
61
|
+
cert = storage.get_certificate(name, version: version)
|
|
62
62
|
|
|
63
63
|
certs = []
|
|
64
64
|
case type
|
|
@@ -76,8 +76,8 @@ module Acmesmith
|
|
|
76
76
|
certs
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
def save_certificate(
|
|
80
|
-
cert = storage.get_certificate(
|
|
79
|
+
def save_certificate(name, version: 'current', mode: '0600', output:, type: 'fullchain')
|
|
80
|
+
cert = storage.get_certificate(name, version: version)
|
|
81
81
|
File.open(output, 'w', mode.to_i(8)) do |f|
|
|
82
82
|
case type
|
|
83
83
|
when 'certificate'
|
|
@@ -90,23 +90,23 @@ module Acmesmith
|
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
-
def get_private_key(
|
|
94
|
-
cert = storage.get_certificate(
|
|
93
|
+
def get_private_key(name, version: 'current')
|
|
94
|
+
cert = storage.get_certificate(name, version: version)
|
|
95
95
|
cert.key_passphrase = certificate_key_passphrase if certificate_key_passphrase
|
|
96
96
|
|
|
97
97
|
cert.private_key.to_pem
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
def save_private_key(
|
|
101
|
-
cert = storage.get_certificate(
|
|
100
|
+
def save_private_key(name, version: 'current', mode: '0600', output:)
|
|
101
|
+
cert = storage.get_certificate(name, version: version)
|
|
102
102
|
cert.key_passphrase = certificate_key_passphrase if certificate_key_passphrase
|
|
103
103
|
File.open(output, 'w', mode.to_i(8)) do |f|
|
|
104
104
|
f.puts(cert.private_key)
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
def save_pkcs12(
|
|
109
|
-
cert = storage.get_certificate(
|
|
108
|
+
def save_pkcs12(name, version: 'current', mode: '0600', output:, passphrase:)
|
|
109
|
+
cert = storage.get_certificate(name, version: version)
|
|
110
110
|
cert.key_passphrase = certificate_key_passphrase if certificate_key_passphrase
|
|
111
111
|
|
|
112
112
|
p12 = cert.pkcs12(passphrase)
|
|
@@ -115,17 +115,18 @@ module Acmesmith
|
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
def save(
|
|
119
|
-
cert = storage.get_certificate(
|
|
118
|
+
def save(name, version: 'current', **kwargs)
|
|
119
|
+
cert = storage.get_certificate(name, version: version)
|
|
120
120
|
cert.key_passphrase = certificate_key_passphrase if certificate_key_passphrase
|
|
121
121
|
|
|
122
122
|
SaveCertificateService.new(cert, **kwargs).perform!
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
-
def autorenew(days: 30, remaining_life: nil,
|
|
126
|
-
(
|
|
125
|
+
def autorenew(days: 30, remaining_life: nil, names: nil)
|
|
126
|
+
(names || storage.list_certificates).each do |cn|
|
|
127
127
|
puts "=> #{cn}"
|
|
128
|
-
cert =
|
|
128
|
+
cert = load_certificate_from_storage(cn)
|
|
129
|
+
|
|
129
130
|
not_after = cert.certificate.not_after.utc
|
|
130
131
|
|
|
131
132
|
lifetime = cert.certificate.not_after.utc - cert.certificate.not_before.utc
|
|
@@ -139,24 +140,30 @@ module Acmesmith
|
|
|
139
140
|
puts " Not valid after: #{not_after} (lifetime=#{format_duration(lifetime+1)}, remaining=#{format_duration(remaining)}, #{"%0.2f" % (ratio.to_f*100)}%)"
|
|
140
141
|
next unless has_to_renew
|
|
141
142
|
|
|
142
|
-
puts " * Renewing:
|
|
143
|
-
order_with_private_key(cert.
|
|
143
|
+
puts " * Renewing: #{cert.name.inspect}, SANs=#{cert.sans.join(',')}"
|
|
144
|
+
order_with_private_key(cert.name, *cert.sans, private_key: regenerate_private_key(cert.public_key))
|
|
144
145
|
end
|
|
145
146
|
end
|
|
146
147
|
|
|
147
|
-
def add_san(
|
|
148
|
-
puts "=> reissuing
|
|
149
|
-
cert =
|
|
148
|
+
def add_san(name, *add_sans)
|
|
149
|
+
puts "=> reissuing #{name.inspect} with new SANs #{add_sans.join(?,)}"
|
|
150
|
+
cert = load_certificate_from_storage(name)
|
|
150
151
|
sans = cert.sans + add_sans
|
|
151
152
|
puts " * SANs will be: #{sans.join(?,)}"
|
|
152
|
-
order_with_private_key(cert.
|
|
153
|
+
order_with_private_key(cert.name, *sans, private_key: regenerate_private_key(cert.public_key))
|
|
153
154
|
end
|
|
154
155
|
|
|
155
156
|
private
|
|
156
157
|
|
|
157
158
|
# @param [Numeric] duration
|
|
158
159
|
def format_duration(duration)
|
|
159
|
-
raise ArgumentError if !duration.is_a?(Numeric)
|
|
160
|
+
raise ArgumentError if !duration.is_a?(Numeric)
|
|
161
|
+
negative = if duration < 0
|
|
162
|
+
duration *= -1
|
|
163
|
+
true
|
|
164
|
+
else
|
|
165
|
+
false
|
|
166
|
+
end
|
|
160
167
|
|
|
161
168
|
# Calculate components using divmod
|
|
162
169
|
days, remainder = duration.divmod(86400)
|
|
@@ -167,6 +174,7 @@ module Acmesmith
|
|
|
167
174
|
[[days, 'd'], [hours, 'h'], [minutes, 'm'], [seconds, 's']]
|
|
168
175
|
.select { |v,| v > 0 }
|
|
169
176
|
.map { |v, unit| "#{v.to_i}#{unit}" }
|
|
177
|
+
.then { |a| negative ? ['-', *a] : a }
|
|
170
178
|
.join
|
|
171
179
|
end
|
|
172
180
|
|
|
@@ -205,10 +213,11 @@ module Acmesmith
|
|
|
205
213
|
end
|
|
206
214
|
end
|
|
207
215
|
|
|
208
|
-
def order_with_private_key(*identifiers, private_key:, not_before: nil, not_after: nil)
|
|
216
|
+
def order_with_private_key(name, *identifiers, private_key:, not_before: nil, not_after: nil)
|
|
209
217
|
order = OrderingService.new(
|
|
210
218
|
acme: acme,
|
|
211
|
-
|
|
219
|
+
common_name: name,
|
|
220
|
+
identifiers: [name, *identifiers],
|
|
212
221
|
private_key: private_key,
|
|
213
222
|
challenge_responder_rules: config.challenge_responders,
|
|
214
223
|
chain_preferences: config.chain_preferences,
|
|
@@ -251,5 +260,12 @@ module Acmesmith
|
|
|
251
260
|
raise ArgumentError, "Unknown key type: #{template.class}"
|
|
252
261
|
end
|
|
253
262
|
end
|
|
263
|
+
|
|
264
|
+
# Load certificate from storage, inherit name property to loaded certificate to ensure stability of #name during renewal.
|
|
265
|
+
def load_certificate_from_storage(name)
|
|
266
|
+
retval = storage.get_certificate(name)
|
|
267
|
+
retval.name = name
|
|
268
|
+
retval
|
|
269
|
+
end
|
|
254
270
|
end
|
|
255
271
|
end
|
data/lib/acmesmith/command.rb
CHANGED
|
@@ -30,14 +30,14 @@ module Acmesmith
|
|
|
30
30
|
# client.authorize(*domains)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
desc "order
|
|
33
|
+
desc "order NAME [SAN]", "order certificate for CN +NAME+ with SANs +SAN+"
|
|
34
34
|
method_option :show_certificate, type: :boolean, aliases: %w(-s), default: true, desc: 'show an issued certificate in PEM and text when exiting'
|
|
35
35
|
method_option :key_type, type: :string, enum: %w(rsa ec), default: 'rsa', desc: 'key type'
|
|
36
36
|
method_option :rsa_key_size, type: :numeric, default: 2048, desc: 'size of RSA key'
|
|
37
37
|
method_option :elliptic_curve, type: :string, default: 'prime256v1', desc: 'elliptic curve group for EC key'
|
|
38
|
-
def order(
|
|
38
|
+
def order(name, *sans)
|
|
39
39
|
cert = client.order(
|
|
40
|
-
|
|
40
|
+
name, *sans,
|
|
41
41
|
key_type: options[:key_type],
|
|
42
42
|
rsa_key_size: options[:rsa_key_size],
|
|
43
43
|
elliptic_curve: options[:elliptic_curve],
|
|
@@ -48,60 +48,60 @@ module Acmesmith
|
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
desc "post-issue-hooks
|
|
52
|
-
def post_issue_hooks(
|
|
53
|
-
client.post_issue_hooks(
|
|
51
|
+
desc "post-issue-hooks NAME", "Run all post-issuing hooks for common name. (for testing purpose)"
|
|
52
|
+
def post_issue_hooks(name)
|
|
53
|
+
client.post_issue_hooks(name)
|
|
54
54
|
end
|
|
55
55
|
map 'post-issue-hooks' => :post_issue_hooks
|
|
56
56
|
|
|
57
|
-
desc "list [
|
|
58
|
-
def list(
|
|
59
|
-
if
|
|
60
|
-
puts client.certificate_versions(
|
|
57
|
+
desc "list [NAME]", "list certificates or its versions"
|
|
58
|
+
def list(name = nil)
|
|
59
|
+
if name
|
|
60
|
+
puts client.certificate_versions(name)
|
|
61
61
|
else
|
|
62
62
|
puts client.certificates_list
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
desc "current
|
|
67
|
-
def current(
|
|
68
|
-
puts client.current(
|
|
66
|
+
desc "current NAME", "show current version for certificate"
|
|
67
|
+
def current(name)
|
|
68
|
+
puts client.current(name)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
-
desc "show-certificate
|
|
71
|
+
desc "show-certificate NAME", "show certificate"
|
|
72
72
|
method_option :version, type: :string, default: 'current'
|
|
73
73
|
method_option :type, type: :string, enum: %w(text certificate chain fullchain), default: 'text'
|
|
74
|
-
def show_certificate(
|
|
75
|
-
certs = client.get_certificate(
|
|
74
|
+
def show_certificate(name)
|
|
75
|
+
certs = client.get_certificate(name, version: options[:version], type: options[:type])
|
|
76
76
|
puts certs
|
|
77
77
|
end
|
|
78
78
|
map 'show-certiticate' => :show_certificate
|
|
79
79
|
|
|
80
|
-
desc 'save-certificate
|
|
80
|
+
desc 'save-certificate NAME', 'Save certificate to a file'
|
|
81
81
|
method_option :version, type: :string, default: 'current'
|
|
82
82
|
method_option :type, type: :string, enum: %w(certificate chain fullchain), default: 'fullchain'
|
|
83
83
|
method_option :output, type: :string, required: true, banner: 'PATH', desc: 'Path to output file'
|
|
84
84
|
method_option :mode, type: :string, default: '0600', desc: 'Mode (permission) of the output file on create'
|
|
85
|
-
def save_certificate(
|
|
86
|
-
client.save_certificate(
|
|
85
|
+
def save_certificate(name)
|
|
86
|
+
client.save_certificate(name, version: options[:version], mode: options[:mode], output: options[:output], type: options[:type])
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
desc "show-private-key
|
|
89
|
+
desc "show-private-key NAME", "show private key"
|
|
90
90
|
method_option :version, type: :string, default: 'current'
|
|
91
|
-
def show_private_key(
|
|
92
|
-
puts client.get_private_key(
|
|
91
|
+
def show_private_key(name)
|
|
92
|
+
puts client.get_private_key(name, version: options[:version])
|
|
93
93
|
end
|
|
94
94
|
map 'show-private-key' => :show_private_key
|
|
95
95
|
|
|
96
|
-
desc 'save-private-key
|
|
96
|
+
desc 'save-private-key NAME', 'Save private key to a file'
|
|
97
97
|
method_option :version, type: :string, default: 'current'
|
|
98
98
|
method_option :output, type: :string, required: true, banner: 'PATH', desc: 'Path to output file'
|
|
99
99
|
method_option :mode, type: :string, default: '0600', desc: 'Mode (permission) of the output file on create'
|
|
100
|
-
def save_private_key(
|
|
101
|
-
client.save_private_key(
|
|
100
|
+
def save_private_key(name)
|
|
101
|
+
client.save_private_key(name, version: options[:version], mode: options[:mode], output: options[:output])
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
-
desc 'save
|
|
104
|
+
desc 'save NAME', 'Save (or update) certificate and key files.'
|
|
105
105
|
method_option :version, type: :string, default: 'current'
|
|
106
106
|
method_option :key_mode, type: :string, default: '0600', desc: 'Mode (permission) of the key file on create'
|
|
107
107
|
method_option :certificate_mode, type: :string, default: '0644', desc: 'Mode (permission) of the certificate files on create'
|
|
@@ -111,9 +111,9 @@ module Acmesmith
|
|
|
111
111
|
method_option :chain_file, type: :string, required: false , banner: 'PATH', desc: 'Path to save a certificate chain (root and intermediate CA)'
|
|
112
112
|
method_option :certificate_file, type: :string, required: false, banner: 'PATH', desc: 'Path to save a certficiate'
|
|
113
113
|
method_option :atomic, type: :boolean, default: true, desc: 'Enable atomic file update with rename(2)'
|
|
114
|
-
def save(
|
|
114
|
+
def save(name)
|
|
115
115
|
client.save(
|
|
116
|
-
|
|
116
|
+
name,
|
|
117
117
|
version: options[:version],
|
|
118
118
|
key_mode: options[:key_mode],
|
|
119
119
|
certificate_mode: options[:certificate_mode],
|
|
@@ -127,11 +127,11 @@ module Acmesmith
|
|
|
127
127
|
)
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
-
desc 'save-pkcs12
|
|
130
|
+
desc 'save-pkcs12 NAME', 'Save ceriticate and private key to .p12 file'
|
|
131
131
|
method_option :version, type: :string, default: 'current'
|
|
132
132
|
method_option :output, type: :string, required: true, banner: 'PATH', desc: 'Path to output file'
|
|
133
133
|
method_option :mode, type: :string, default: '0600', desc: 'Mode (permission) of the output file on create'
|
|
134
|
-
def save_pkcs12(
|
|
134
|
+
def save_pkcs12(name)
|
|
135
135
|
print 'Passphrase: '
|
|
136
136
|
passphrase = $stdin.noecho { $stdin.gets }.chomp
|
|
137
137
|
print "\nPassphrase (confirm): "
|
|
@@ -139,13 +139,13 @@ module Acmesmith
|
|
|
139
139
|
puts
|
|
140
140
|
|
|
141
141
|
raise ArgumentError, "Passphrase doesn't match" if passphrase != passphrase2
|
|
142
|
-
client.save_pkcs12(
|
|
142
|
+
client.save_pkcs12(name, version: options[:version], mode: options[:mode], output: options[:output], passphrase: passphrase)
|
|
143
143
|
end
|
|
144
144
|
|
|
145
|
-
desc "autorenew [
|
|
145
|
+
desc "autorenew [NAMES]", "request renewal of certificates which expires soon"
|
|
146
146
|
method_option :days, type: :numeric, aliases: %w(-d), default: nil, desc: 'specify threshold in days to select certificates to renew'
|
|
147
147
|
method_option :remaining_life, type: :string, aliases: %w(-r), default: '1/3', desc: "Specify threshold based on remaining life. Accepts a percentage ('20%') or fraction ('1/3')"
|
|
148
|
-
def autorenew(*
|
|
148
|
+
def autorenew(*names)
|
|
149
149
|
remaining_life = case options[:remaining_life]
|
|
150
150
|
when %r{\A\d+/\d+\z}
|
|
151
151
|
Rational(options[:remaining_life])
|
|
@@ -156,12 +156,12 @@ module Acmesmith
|
|
|
156
156
|
else
|
|
157
157
|
raise ArgumentError, "invalid format for --remaining-life: it must be in '..%' or '../..'"
|
|
158
158
|
end
|
|
159
|
-
client.autorenew(days: options[:days], remaining_life: remaining_life,
|
|
159
|
+
client.autorenew(days: options[:days], remaining_life: remaining_life, names: names.empty? ? nil : names)
|
|
160
160
|
end
|
|
161
161
|
|
|
162
|
-
desc "add-san
|
|
163
|
-
def add_san(
|
|
164
|
-
client.add_san(
|
|
162
|
+
desc "add-san NAME [ADDITIONAL_SANS]", "request renewal of existing certificate with additional SANs"
|
|
163
|
+
def add_san(name, *add_sans)
|
|
164
|
+
client.add_san(name, *add_sans)
|
|
165
165
|
end
|
|
166
166
|
|
|
167
167
|
desc "register CONTACT", "(deprecated, use 'acmesmith new-account')"
|
|
@@ -175,16 +175,16 @@ module Acmesmith
|
|
|
175
175
|
new_account(contact)
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
-
desc "request
|
|
178
|
+
desc "request NAME [SAN]", "(deprecated, use 'acmesmith order')"
|
|
179
179
|
method_option :show_certificate, type: :boolean, aliases: %w(-s), default: true, desc: 'show an issued certificate in PEM and text when exiting'
|
|
180
|
-
def request(
|
|
180
|
+
def request(name, *sans)
|
|
181
181
|
warn "!"
|
|
182
182
|
warn "! DEPRECATION WARNING: Use 'acmesmith order' command"
|
|
183
183
|
warn "! There is no user-facing breaking changes. It takes the same arguments with 'acmesmith request'."
|
|
184
184
|
warn "!"
|
|
185
185
|
warn "! This is due to change in semantics of ACME v2. ACME v2 defines 'order' instead of 'request' in v1."
|
|
186
186
|
warn "!"
|
|
187
|
-
order(
|
|
187
|
+
order(name, *sans)
|
|
188
188
|
end
|
|
189
189
|
|
|
190
190
|
private
|
|
@@ -7,14 +7,16 @@ module Acmesmith
|
|
|
7
7
|
class NotCompleted < StandardError; end
|
|
8
8
|
|
|
9
9
|
# @param acme [Acme::Client] ACME client
|
|
10
|
-
# @param
|
|
10
|
+
# @param common_name [String] Common Name for a ordering certificate
|
|
11
|
+
# @param identifiers [Array<String>] Array of domain names for a ordering certificate. common_name has to be explicitly included in this argument.
|
|
11
12
|
# @param private_key [OpenSSL::PKey::PKey] Private key
|
|
12
13
|
# @param challenge_responder_rules [Array<Acmesmith::Config::ChallengeResponderRule>] responders
|
|
13
14
|
# @param chain_preferences [Array<Acmesmith::Config::ChainPreference>] chain_preferences
|
|
14
15
|
# @param not_before [Time]
|
|
15
16
|
# @param not_after [Time]
|
|
16
|
-
def initialize(acme:, identifiers:, private_key:, challenge_responder_rules:, chain_preferences:, not_before: nil, not_after: nil)
|
|
17
|
+
def initialize(acme:, common_name:, identifiers:, private_key:, challenge_responder_rules:, chain_preferences:, not_before: nil, not_after: nil)
|
|
17
18
|
@acme = acme
|
|
19
|
+
@common_name = common_name
|
|
18
20
|
@identifiers = identifiers
|
|
19
21
|
@private_key = private_key
|
|
20
22
|
@challenge_responder_rules = challenge_responder_rules
|
|
@@ -23,7 +25,7 @@ module Acmesmith
|
|
|
23
25
|
@not_after = not_after
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
attr_reader :acme, :identifiers, :private_key, :challenge_responder_rules, :chain_preferences, :not_before, :not_after
|
|
28
|
+
attr_reader :acme, :common_name, :identifiers, :private_key, :challenge_responder_rules, :chain_preferences, :not_before, :not_after
|
|
27
29
|
|
|
28
30
|
def perform!
|
|
29
31
|
puts "=> Ordering a certificate for the following identifiers:"
|
|
@@ -43,7 +45,7 @@ module Acmesmith
|
|
|
43
45
|
finalize_order()
|
|
44
46
|
wait_order_for_complete()
|
|
45
47
|
|
|
46
|
-
@certificate = Certificate.by_issuance(pem_chain, csr)
|
|
48
|
+
@certificate = Certificate.by_issuance(pem_chain, csr, name: common_name)
|
|
47
49
|
|
|
48
50
|
puts
|
|
49
51
|
puts "=> Certificate issued"
|
|
@@ -97,11 +99,6 @@ module Acmesmith
|
|
|
97
99
|
@order or raise "BUG: order not yet generated"
|
|
98
100
|
end
|
|
99
101
|
|
|
100
|
-
# @return [String]
|
|
101
|
-
def common_name
|
|
102
|
-
identifiers.first
|
|
103
|
-
end
|
|
104
|
-
|
|
105
102
|
# @return [Array<String>]
|
|
106
103
|
def sans
|
|
107
104
|
identifiers[1..-1]
|
|
@@ -10,10 +10,10 @@ module Acmesmith
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def execute
|
|
13
|
-
puts "=> Executing Post
|
|
13
|
+
puts "=> Executing Post Issuing Hook for #{certificate.name.inspect} in #{self.class.name}"
|
|
14
14
|
puts " $ #{@command}"
|
|
15
15
|
|
|
16
|
-
status = system({"COMMON_NAME" => common_name}, @command)
|
|
16
|
+
status = system({"CERT_NAME" => certificate.name, "COMMON_NAME" => common_name}.compact, @command)
|
|
17
17
|
|
|
18
18
|
unless status
|
|
19
19
|
if @ignore_failure
|
|
@@ -23,7 +23,7 @@ module Acmesmith
|
|
|
23
23
|
return
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
log "Saving certificate
|
|
26
|
+
log "Saving certificate #{cert.name.inspect} (ver: #{cert.version})"
|
|
27
27
|
|
|
28
28
|
write_file(key_file, key_mode, cert.private_key)
|
|
29
29
|
write_file(certificate_file, certificate_mode, cert.certificate.to_pem)
|
|
@@ -25,28 +25,28 @@ module Acmesmith
|
|
|
25
25
|
raise NotImplementedError
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
# @param
|
|
28
|
+
# @param name [String]
|
|
29
29
|
# @param version [String, nil]
|
|
30
30
|
# @return [Acmesmith::Certificate]
|
|
31
|
-
def get_certificate(
|
|
31
|
+
def get_certificate(name, version: 'current')
|
|
32
32
|
raise NotImplementedError
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# @param
|
|
36
|
-
# @return [String] array of
|
|
35
|
+
# @param name [String]
|
|
36
|
+
# @return [String] array of certificate names
|
|
37
37
|
def list_certificates
|
|
38
38
|
raise NotImplementedError
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
# @param
|
|
41
|
+
# @param name [String]
|
|
42
42
|
# @return [String] array of versions
|
|
43
|
-
def list_certificate_versions(
|
|
43
|
+
def list_certificate_versions(name)
|
|
44
44
|
raise NotImplementedError
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
# @param
|
|
47
|
+
# @param name [String]
|
|
48
48
|
# @return [String] current version
|
|
49
|
-
def get_current_certificate_version(
|
|
49
|
+
def get_current_certificate_version(name)
|
|
50
50
|
raise NotImplementedError
|
|
51
51
|
end
|
|
52
52
|
end
|
|
@@ -25,22 +25,22 @@ module Acmesmith
|
|
|
25
25
|
|
|
26
26
|
def put_certificate(cert, passphrase = nil, update_current: true)
|
|
27
27
|
h = cert.export(passphrase)
|
|
28
|
-
certificate_base_path(cert.
|
|
29
|
-
File.write certificate_path(cert.
|
|
30
|
-
File.write chain_path(cert.
|
|
31
|
-
File.write fullchain_path(cert.
|
|
32
|
-
File.write private_key_path(cert.
|
|
28
|
+
certificate_base_path(cert.name, cert.version).mkpath
|
|
29
|
+
File.write certificate_path(cert.name, cert.version), "#{h[:certificate].rstrip}\n"
|
|
30
|
+
File.write chain_path(cert.name, cert.version), "#{h[:chain].rstrip}\n"
|
|
31
|
+
File.write fullchain_path(cert.name, cert.version), "#{h[:fullchain].rstrip}\n"
|
|
32
|
+
File.write private_key_path(cert.name, cert.version), "#{h[:private_key].rstrip}\n", 0, perm: 0600
|
|
33
33
|
if update_current
|
|
34
|
-
File.symlink(cert.version, certificate_base_path(cert.
|
|
35
|
-
File.rename(certificate_base_path(cert.
|
|
34
|
+
File.symlink(cert.version, certificate_base_path(cert.name, 'current.new'))
|
|
35
|
+
File.rename(certificate_base_path(cert.name, 'current.new'), certificate_base_path(cert.name, 'current'))
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def get_certificate(
|
|
40
|
-
raise NotExist.new("Certificate for #{
|
|
41
|
-
certificate = certificate_path(
|
|
42
|
-
chain = chain_path(
|
|
43
|
-
private_key = private_key_path(
|
|
39
|
+
def get_certificate(name, version: 'current')
|
|
40
|
+
raise NotExist.new("Certificate for #{name.inspect} of #{version} version doesn't exist") unless certificate_base_path(name, version).exist?
|
|
41
|
+
certificate = certificate_path(name, version).read
|
|
42
|
+
chain = chain_path(name, version).read
|
|
43
|
+
private_key = private_key_path(name, version).read
|
|
44
44
|
Certificate.new(certificate, chain, private_key)
|
|
45
45
|
end
|
|
46
46
|
|
|
@@ -48,12 +48,12 @@ module Acmesmith
|
|
|
48
48
|
Dir[path.join('certs', '*').to_s].map { |_| File.basename(_) }
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
def list_certificate_versions(
|
|
52
|
-
Dir[path.join('certs',
|
|
51
|
+
def list_certificate_versions(name)
|
|
52
|
+
Dir[path.join('certs', name, '*').to_s].map { |_| File.basename(_) }.reject { |_| _ == 'current' }
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
def get_current_certificate_version(
|
|
56
|
-
path.join('certs',
|
|
55
|
+
def get_current_certificate_version(name)
|
|
56
|
+
path.join('certs', name, 'current').readlink
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
private
|
|
@@ -83,34 +83,34 @@ module Acmesmith
|
|
|
83
83
|
@s3.put_object(params)
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
put.call certificate_key(cert.
|
|
87
|
-
put.call chain_key(cert.
|
|
88
|
-
put.call fullchain_key(cert.
|
|
89
|
-
put.call private_key_key(cert.
|
|
86
|
+
put.call certificate_key(cert.name, cert.version), "#{h[:certificate].rstrip}\n", false
|
|
87
|
+
put.call chain_key(cert.name, cert.version), "#{h[:chain].rstrip}\n", false
|
|
88
|
+
put.call fullchain_key(cert.name, cert.version), "#{h[:fullchain].rstrip}\n", false
|
|
89
|
+
put.call private_key_key(cert.name, cert.version), "#{h[:private_key].rstrip}\n", use_kms
|
|
90
90
|
|
|
91
91
|
if generate_pkcs12?(cert)
|
|
92
|
-
put.call pkcs12_key(cert.
|
|
92
|
+
put.call pkcs12_key(cert.name, cert.version), "#{cert.pkcs12(@pkcs12_passphrase).to_der}\n", use_kms, 'application/x-pkcs12'
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
if update_current
|
|
96
96
|
@s3.put_object(
|
|
97
97
|
bucket: bucket,
|
|
98
|
-
key: certificate_current_key(cert.
|
|
98
|
+
key: certificate_current_key(cert.name),
|
|
99
99
|
content_type: 'text/plain',
|
|
100
100
|
body: cert.version,
|
|
101
101
|
)
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
-
def get_certificate(
|
|
106
|
-
version = certificate_current(
|
|
105
|
+
def get_certificate(name, version: 'current')
|
|
106
|
+
version = certificate_current(name) if version == 'current'
|
|
107
107
|
|
|
108
|
-
certificate = @s3.get_object(bucket: bucket, key: certificate_key(
|
|
109
|
-
chain = @s3.get_object(bucket: bucket, key: chain_key(
|
|
110
|
-
private_key = @s3.get_object(bucket: bucket, key: private_key_key(
|
|
108
|
+
certificate = @s3.get_object(bucket: bucket, key: certificate_key(name, version)).body.read
|
|
109
|
+
chain = @s3.get_object(bucket: bucket, key: chain_key(name, version)).body.read
|
|
110
|
+
private_key = @s3.get_object(bucket: bucket, key: private_key_key(name, version)).body.read
|
|
111
111
|
Certificate.new(certificate, chain, private_key)
|
|
112
112
|
rescue Aws::S3::Errors::NoSuchKey
|
|
113
|
-
raise NotExist.new("Certificate for #{
|
|
113
|
+
raise NotExist.new("Certificate for #{name.inspect} of #{version} version doesn't exist")
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
def list_certificates
|
|
@@ -125,8 +125,8 @@ module Acmesmith
|
|
|
125
125
|
end
|
|
126
126
|
end
|
|
127
127
|
|
|
128
|
-
def list_certificate_versions(
|
|
129
|
-
cert_ver_prefix = "#{prefix}certs/#{
|
|
128
|
+
def list_certificate_versions(name)
|
|
129
|
+
cert_ver_prefix = "#{prefix}certs/#{name}/"
|
|
130
130
|
@s3.list_objects(
|
|
131
131
|
bucket: bucket,
|
|
132
132
|
delimiter: '/',
|
|
@@ -137,8 +137,8 @@ module Acmesmith
|
|
|
137
137
|
end.reject { |_| _ == 'current' }
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
-
def get_current_certificate_version(
|
|
141
|
-
certificate_current(
|
|
140
|
+
def get_current_certificate_version(name)
|
|
141
|
+
certificate_current(name)
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
private
|
data/lib/acmesmith/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: acmesmith
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sorah Fukumori
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: acme-client
|
|
@@ -141,20 +141,12 @@ extensions: []
|
|
|
141
141
|
extra_rdoc_files: []
|
|
142
142
|
files:
|
|
143
143
|
- ".dockerignore"
|
|
144
|
-
- ".github/FUNDING.yml"
|
|
145
|
-
- ".github/stale.yml"
|
|
146
|
-
- ".github/workflows/build.yml"
|
|
147
|
-
- ".gitignore"
|
|
148
|
-
- ".rspec"
|
|
149
144
|
- ".travis.yml"
|
|
150
145
|
- CHANGELOG.md
|
|
151
146
|
- Dockerfile
|
|
152
|
-
- Gemfile
|
|
153
|
-
- Gemfile.lock
|
|
154
147
|
- LICENSE.txt
|
|
155
148
|
- README.md
|
|
156
149
|
- Rakefile
|
|
157
|
-
- acmesmith.gemspec
|
|
158
150
|
- bin/acmesmith
|
|
159
151
|
- config.sample.yml
|
|
160
152
|
- docs/challenge_responders/route53.md
|
|
@@ -193,12 +185,13 @@ files:
|
|
|
193
185
|
- lib/acmesmith/storages/s3.rb
|
|
194
186
|
- lib/acmesmith/utils/finder.rb
|
|
195
187
|
- lib/acmesmith/version.rb
|
|
196
|
-
- script/console
|
|
197
|
-
- script/setup
|
|
198
188
|
homepage: https://github.com/sorah/acmesmith
|
|
199
189
|
licenses:
|
|
200
190
|
- MIT
|
|
201
|
-
metadata:
|
|
191
|
+
metadata:
|
|
192
|
+
homepage_uri: https://github.com/sorah/acmesmith
|
|
193
|
+
source_code_uri: https://github.com/sorah/acmesmith
|
|
194
|
+
changelog_uri: https://github.com/sorah/acmesmith/blob/master/CHANGELOG.md
|
|
202
195
|
rdoc_options: []
|
|
203
196
|
require_paths:
|
|
204
197
|
- lib
|
|
@@ -213,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
213
206
|
- !ruby/object:Gem::Version
|
|
214
207
|
version: '0'
|
|
215
208
|
requirements: []
|
|
216
|
-
rubygems_version: 3.6.
|
|
209
|
+
rubygems_version: 3.6.9
|
|
217
210
|
specification_version: 4
|
|
218
211
|
summary: ACME client (Let's encrypt client) to manage certificate in multi server
|
|
219
212
|
environment with cloud services (e.g. AWS)
|
data/.github/FUNDING.yml
DELETED
data/.github/stale.yml
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Number of days of inactivity before an issue becomes stale
|
|
2
|
-
daysUntilStale: 30
|
|
3
|
-
# Number of days of inactivity before a stale issue is closed
|
|
4
|
-
daysUntilClose: 7
|
|
5
|
-
# Issues with these labels will never be considered stale
|
|
6
|
-
exemptLabels:
|
|
7
|
-
- pinned
|
|
8
|
-
- security
|
|
9
|
-
# Label to use when marking an issue as stale
|
|
10
|
-
staleLabel: rotten
|
|
11
|
-
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
12
|
-
markComment: >
|
|
13
|
-
This issue has been automatically marked as stale because it has not had
|
|
14
|
-
recent activity. It will be closed if no further activity occurs. Thank you
|
|
15
|
-
for your contributions.
|
|
16
|
-
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
17
|
-
closeComment: false
|
data/.github/workflows/build.yml
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
name: ci
|
|
2
|
-
on:
|
|
3
|
-
schedule:
|
|
4
|
-
- cron: '36 7 2,12,22 * *'
|
|
5
|
-
create: {}
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [master]
|
|
8
|
-
push:
|
|
9
|
-
branches: [master, ci-test]
|
|
10
|
-
|
|
11
|
-
env:
|
|
12
|
-
DOCKER_REPO: 'sorah/acmesmith'
|
|
13
|
-
|
|
14
|
-
jobs:
|
|
15
|
-
test:
|
|
16
|
-
name: rspec
|
|
17
|
-
runs-on: ubuntu-latest
|
|
18
|
-
strategy:
|
|
19
|
-
fail-fast: false
|
|
20
|
-
matrix:
|
|
21
|
-
ruby-version: ['3.2', '3.3', '3.4']
|
|
22
|
-
steps:
|
|
23
|
-
- uses: actions/checkout@master
|
|
24
|
-
- uses: sorah-rbpkg/actions@v2
|
|
25
|
-
with:
|
|
26
|
-
ruby-version: "${{ matrix.ruby-version }}"
|
|
27
|
-
bundler-cache: true
|
|
28
|
-
- run: 'bundle exec rspec -fd'
|
|
29
|
-
|
|
30
|
-
integration-pebble:
|
|
31
|
-
name: integration-pebble
|
|
32
|
-
runs-on: ubuntu-latest
|
|
33
|
-
strategy:
|
|
34
|
-
fail-fast: false
|
|
35
|
-
matrix:
|
|
36
|
-
ruby-version: ['3.2', '3.3', '3.4']
|
|
37
|
-
|
|
38
|
-
# FIXME: once GitHub Actions gains support of adding command line arguments to container
|
|
39
|
-
# services:
|
|
40
|
-
# # https://github.com/letsencrypt/pebble
|
|
41
|
-
# pebble:
|
|
42
|
-
# image: letsencrypt/pebble
|
|
43
|
-
# ports:
|
|
44
|
-
# - 14000:14000 # ACME port
|
|
45
|
-
# - 15000:15000 # Management port
|
|
46
|
-
# options: "pebble -config /test/config/pebble-config.json -strict -dnsserver 127.0.0.1:8053"
|
|
47
|
-
#
|
|
48
|
-
# challtestsrv:
|
|
49
|
-
# image: letsencrypt/pebble-challtestsrv:latest
|
|
50
|
-
# ports:
|
|
51
|
-
# - 8055:8055 # HTTP Management API
|
|
52
|
-
# - 8053:8053/udp # DNS
|
|
53
|
-
# - 8053:8053 # DNS
|
|
54
|
-
# options: 'pebble-challtestsrv -management :8055 -defaultIPv4 127.0.0.1'
|
|
55
|
-
|
|
56
|
-
steps:
|
|
57
|
-
- uses: actions/checkout@master
|
|
58
|
-
|
|
59
|
-
- uses: sorah-rbpkg/actions@v2
|
|
60
|
-
with:
|
|
61
|
-
ruby-version: "${{ matrix.ruby-version }}"
|
|
62
|
-
bundler-cache: true
|
|
63
|
-
|
|
64
|
-
- run: 'docker run -d --net=host --rm letsencrypt/pebble pebble -config /test/config/pebble-config.json -strict -dnsserver 127.0.0.1:8053'
|
|
65
|
-
- run: 'docker run -d --net=host --rm letsencrypt/pebble-challtestsrv pebble-challtestsrv -management :8055 -defaultIPv4 127.0.0.1'
|
|
66
|
-
- run: 'bundle exec rspec -fd -t integration_pebble'
|
|
67
|
-
|
|
68
|
-
docker-build:
|
|
69
|
-
name: docker-build
|
|
70
|
-
runs-on: ubuntu-latest
|
|
71
|
-
steps:
|
|
72
|
-
- uses: actions/checkout@master
|
|
73
|
-
- run: 'echo $GITHUB_SHA > REVISION'
|
|
74
|
-
|
|
75
|
-
- run: "docker pull ${DOCKER_REPO}:latest || :"
|
|
76
|
-
- name: "docker tag ${DOCKER_REPO}:${TAG} ${DOCKER_REPO}:latest"
|
|
77
|
-
run: |
|
|
78
|
-
TAG=$(basename "${{ github.ref }}")
|
|
79
|
-
docker pull ${DOCKER_REPO}:${TAG} || :
|
|
80
|
-
docker tag ${DOCKER_REPO}:${TAG} ${DOCKER_REPO}:latest || :
|
|
81
|
-
if: "${{ startsWith(github.ref, 'refs/tags/v') }}"
|
|
82
|
-
|
|
83
|
-
- run: "docker pull ${DOCKER_REPO}:builder || :"
|
|
84
|
-
|
|
85
|
-
- run: "docker build --pull --cache-from ${DOCKER_REPO}:builder --target builder -t ${DOCKER_REPO}:builder -f Dockerfile ."
|
|
86
|
-
- run: "docker build --pull --cache-from ${DOCKER_REPO}:builder --cache-from ${DOCKER_REPO}:latest -t ${DOCKER_REPO}:${GITHUB_SHA} -f Dockerfile ."
|
|
87
|
-
|
|
88
|
-
- run: "echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u sorah --password-stdin"
|
|
89
|
-
if: "${{ github.event_name != 'pull_request' }}"
|
|
90
|
-
|
|
91
|
-
- run: "docker push ${DOCKER_REPO}:builder"
|
|
92
|
-
if: "${{ github.ref == 'refs/heads/master' }}"
|
|
93
|
-
- run: "docker push ${DOCKER_REPO}:${GITHUB_SHA}"
|
|
94
|
-
if: "${{ github.event_name != 'pull_request' }}"
|
|
95
|
-
|
|
96
|
-
docker-push:
|
|
97
|
-
name: docker-push
|
|
98
|
-
needs: [test, integration-pebble, docker-build]
|
|
99
|
-
if: "${{ github.event_name == 'push' || github.event_name == 'create' }}"
|
|
100
|
-
runs-on: ubuntu-latest
|
|
101
|
-
steps:
|
|
102
|
-
- run: "echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u sorah --password-stdin"
|
|
103
|
-
- run: "docker pull ${DOCKER_REPO}:${GITHUB_SHA}"
|
|
104
|
-
|
|
105
|
-
- run: |
|
|
106
|
-
docker tag ${DOCKER_REPO}:${GITHUB_SHA} ${DOCKER_REPO}:latest
|
|
107
|
-
docker push ${DOCKER_REPO}:latest
|
|
108
|
-
if: "${{ github.ref == 'refs/heads/master' }}"
|
|
109
|
-
- run: |
|
|
110
|
-
TAG=$(basename "${{ github.ref }}")
|
|
111
|
-
docker tag ${DOCKER_REPO}:${GITHUB_SHA} ${DOCKER_REPO}:${TAG}
|
|
112
|
-
docker push ${DOCKER_REPO}:${TAG}
|
|
113
|
-
if: "${{ startsWith(github.ref, 'refs/tags/v') }}"
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/Gemfile
DELETED
data/Gemfile.lock
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
PATH
|
|
2
|
-
remote: .
|
|
3
|
-
specs:
|
|
4
|
-
acmesmith (2.7.0)
|
|
5
|
-
acme-client (>= 2.0.7, < 3)
|
|
6
|
-
aws-sdk-acm
|
|
7
|
-
aws-sdk-route53
|
|
8
|
-
aws-sdk-s3
|
|
9
|
-
thor
|
|
10
|
-
|
|
11
|
-
GEM
|
|
12
|
-
remote: https://rubygems.org/
|
|
13
|
-
specs:
|
|
14
|
-
acme-client (2.0.19)
|
|
15
|
-
base64 (~> 0.2.0)
|
|
16
|
-
faraday (>= 1.0, < 3.0.0)
|
|
17
|
-
faraday-retry (>= 1.0, < 3.0.0)
|
|
18
|
-
aws-eventstream (1.3.0)
|
|
19
|
-
aws-partitions (1.1018.0)
|
|
20
|
-
aws-sdk-acm (1.81.0)
|
|
21
|
-
aws-sdk-core (~> 3, >= 3.210.0)
|
|
22
|
-
aws-sigv4 (~> 1.5)
|
|
23
|
-
aws-sdk-core (3.214.0)
|
|
24
|
-
aws-eventstream (~> 1, >= 1.3.0)
|
|
25
|
-
aws-partitions (~> 1, >= 1.992.0)
|
|
26
|
-
aws-sigv4 (~> 1.9)
|
|
27
|
-
jmespath (~> 1, >= 1.6.1)
|
|
28
|
-
aws-sdk-kms (1.96.0)
|
|
29
|
-
aws-sdk-core (~> 3, >= 3.210.0)
|
|
30
|
-
aws-sigv4 (~> 1.5)
|
|
31
|
-
aws-sdk-route53 (1.105.0)
|
|
32
|
-
aws-sdk-core (~> 3, >= 3.210.0)
|
|
33
|
-
aws-sigv4 (~> 1.5)
|
|
34
|
-
aws-sdk-s3 (1.176.0)
|
|
35
|
-
aws-sdk-core (~> 3, >= 3.210.0)
|
|
36
|
-
aws-sdk-kms (~> 1)
|
|
37
|
-
aws-sigv4 (~> 1.5)
|
|
38
|
-
aws-sigv4 (1.10.1)
|
|
39
|
-
aws-eventstream (~> 1, >= 1.0.2)
|
|
40
|
-
base64 (0.2.0)
|
|
41
|
-
diff-lcs (1.5.1)
|
|
42
|
-
faraday (2.12.1)
|
|
43
|
-
faraday-net_http (>= 2.0, < 3.5)
|
|
44
|
-
json
|
|
45
|
-
logger
|
|
46
|
-
faraday-net_http (3.4.0)
|
|
47
|
-
net-http (>= 0.5.0)
|
|
48
|
-
faraday-retry (2.2.1)
|
|
49
|
-
faraday (~> 2.0)
|
|
50
|
-
jmespath (1.6.2)
|
|
51
|
-
json (2.9.0)
|
|
52
|
-
logger (1.6.2)
|
|
53
|
-
net-http (0.6.0)
|
|
54
|
-
uri
|
|
55
|
-
rake (13.2.1)
|
|
56
|
-
rexml (3.4.1)
|
|
57
|
-
rspec (3.13.0)
|
|
58
|
-
rspec-core (~> 3.13.0)
|
|
59
|
-
rspec-expectations (~> 3.13.0)
|
|
60
|
-
rspec-mocks (~> 3.13.0)
|
|
61
|
-
rspec-core (3.13.2)
|
|
62
|
-
rspec-support (~> 3.13.0)
|
|
63
|
-
rspec-expectations (3.13.3)
|
|
64
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
65
|
-
rspec-support (~> 3.13.0)
|
|
66
|
-
rspec-mocks (3.13.2)
|
|
67
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
68
|
-
rspec-support (~> 3.13.0)
|
|
69
|
-
rspec-support (3.13.2)
|
|
70
|
-
thor (1.3.2)
|
|
71
|
-
uri (1.0.3)
|
|
72
|
-
|
|
73
|
-
PLATFORMS
|
|
74
|
-
ruby
|
|
75
|
-
|
|
76
|
-
DEPENDENCIES
|
|
77
|
-
acmesmith!
|
|
78
|
-
bundler
|
|
79
|
-
rake
|
|
80
|
-
rexml
|
|
81
|
-
rspec
|
|
82
|
-
|
|
83
|
-
BUNDLED WITH
|
|
84
|
-
2.5.23
|
data/acmesmith.gemspec
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# coding: utf-8
|
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
-
require 'acmesmith/version'
|
|
5
|
-
|
|
6
|
-
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name = "acmesmith"
|
|
8
|
-
spec.version = Acmesmith::VERSION
|
|
9
|
-
spec.authors = ["Sorah Fukumori"]
|
|
10
|
-
spec.email = ["her@sorah.jp"]
|
|
11
|
-
|
|
12
|
-
spec.summary = %q{ACME client (Let's encrypt client) to manage certificate in multi server environment with cloud services (e.g. AWS)}
|
|
13
|
-
spec.description = <<-EOF
|
|
14
|
-
Acmesmith is an [ACME (Automatic Certificate Management Environment)](https://github.com/ietf-wg-acme/acme) client that works perfect on environment with multiple servers. This client saves certificate and keys on cloud services (e.g. AWS S3) securely, then allow to deploy issued certificates onto your servers smoothly. This works well on [Let's encrypt](https://letsencrypt.org).
|
|
15
|
-
EOF
|
|
16
|
-
spec.homepage = "https://github.com/sorah/acmesmith"
|
|
17
|
-
spec.license = "MIT"
|
|
18
|
-
|
|
19
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
20
|
-
spec.bindir = "bin"
|
|
21
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
22
|
-
spec.require_paths = ["lib"]
|
|
23
|
-
|
|
24
|
-
spec.add_dependency "acme-client", '>= 2.0.7', '< 3'
|
|
25
|
-
spec.add_dependency "aws-sdk-acm"
|
|
26
|
-
spec.add_dependency "aws-sdk-route53"
|
|
27
|
-
spec.add_dependency "aws-sdk-s3"
|
|
28
|
-
spec.add_dependency "thor"
|
|
29
|
-
|
|
30
|
-
spec.add_development_dependency "bundler"
|
|
31
|
-
spec.add_development_dependency "rake"
|
|
32
|
-
spec.add_development_dependency "rspec"
|
|
33
|
-
end
|
data/script/console
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "acmesmith"
|
|
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
|