acmesmith 2.7.1 → 2.9.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 +35 -0
- data/Dockerfile +7 -6
- data/README.md +25 -3
- data/config.sample.yml +15 -0
- 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/challenge_responder_filter.rb +4 -8
- data/lib/acmesmith/challenge_responders/pebble_challtestsrv_dns.rb +1 -1
- data/lib/acmesmith/client.rb +45 -31
- data/lib/acmesmith/command.rb +53 -40
- data/lib/acmesmith/config.rb +16 -0
- data/lib/acmesmith/ordering_service.rb +24 -10
- 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/subject_name_filter.rb +17 -0
- data/lib/acmesmith/version.rb +1 -1
- metadata +8 -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
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,73 @@ 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
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
desc "list-profiles", "List available ACME certificate profiles"
|
|
58
|
+
def list_profiles
|
|
59
|
+
profiles = client.list_profiles
|
|
60
|
+
if profiles.nil? || profiles.empty?
|
|
61
|
+
puts "No profiles available from this ACME directory"
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
profiles.each do |name, description|
|
|
65
|
+
puts "#{name}: #{description}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
map 'list-profiles' => :list_profiles
|
|
69
|
+
|
|
70
|
+
desc "list [NAME]", "list certificates or its versions"
|
|
71
|
+
def list(name = nil)
|
|
72
|
+
if name
|
|
73
|
+
puts client.certificate_versions(name)
|
|
61
74
|
else
|
|
62
75
|
puts client.certificates_list
|
|
63
76
|
end
|
|
64
77
|
end
|
|
65
78
|
|
|
66
|
-
desc "current
|
|
67
|
-
def current(
|
|
68
|
-
puts client.current(
|
|
79
|
+
desc "current NAME", "show current version for certificate"
|
|
80
|
+
def current(name)
|
|
81
|
+
puts client.current(name)
|
|
69
82
|
end
|
|
70
83
|
|
|
71
|
-
desc "show-certificate
|
|
84
|
+
desc "show-certificate NAME", "show certificate"
|
|
72
85
|
method_option :version, type: :string, default: 'current'
|
|
73
86
|
method_option :type, type: :string, enum: %w(text certificate chain fullchain), default: 'text'
|
|
74
|
-
def show_certificate(
|
|
75
|
-
certs = client.get_certificate(
|
|
87
|
+
def show_certificate(name)
|
|
88
|
+
certs = client.get_certificate(name, version: options[:version], type: options[:type])
|
|
76
89
|
puts certs
|
|
77
90
|
end
|
|
78
91
|
map 'show-certiticate' => :show_certificate
|
|
79
92
|
|
|
80
|
-
desc 'save-certificate
|
|
93
|
+
desc 'save-certificate NAME', 'Save certificate to a file'
|
|
81
94
|
method_option :version, type: :string, default: 'current'
|
|
82
95
|
method_option :type, type: :string, enum: %w(certificate chain fullchain), default: 'fullchain'
|
|
83
96
|
method_option :output, type: :string, required: true, banner: 'PATH', desc: 'Path to output file'
|
|
84
97
|
method_option :mode, type: :string, default: '0600', desc: 'Mode (permission) of the output file on create'
|
|
85
|
-
def save_certificate(
|
|
86
|
-
client.save_certificate(
|
|
98
|
+
def save_certificate(name)
|
|
99
|
+
client.save_certificate(name, version: options[:version], mode: options[:mode], output: options[:output], type: options[:type])
|
|
87
100
|
end
|
|
88
101
|
|
|
89
|
-
desc "show-private-key
|
|
102
|
+
desc "show-private-key NAME", "show private key"
|
|
90
103
|
method_option :version, type: :string, default: 'current'
|
|
91
|
-
def show_private_key(
|
|
92
|
-
puts client.get_private_key(
|
|
104
|
+
def show_private_key(name)
|
|
105
|
+
puts client.get_private_key(name, version: options[:version])
|
|
93
106
|
end
|
|
94
107
|
map 'show-private-key' => :show_private_key
|
|
95
108
|
|
|
96
|
-
desc 'save-private-key
|
|
109
|
+
desc 'save-private-key NAME', 'Save private key to a file'
|
|
97
110
|
method_option :version, type: :string, default: 'current'
|
|
98
111
|
method_option :output, type: :string, required: true, banner: 'PATH', desc: 'Path to output file'
|
|
99
112
|
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(
|
|
113
|
+
def save_private_key(name)
|
|
114
|
+
client.save_private_key(name, version: options[:version], mode: options[:mode], output: options[:output])
|
|
102
115
|
end
|
|
103
116
|
|
|
104
|
-
desc 'save
|
|
117
|
+
desc 'save NAME', 'Save (or update) certificate and key files.'
|
|
105
118
|
method_option :version, type: :string, default: 'current'
|
|
106
119
|
method_option :key_mode, type: :string, default: '0600', desc: 'Mode (permission) of the key file on create'
|
|
107
120
|
method_option :certificate_mode, type: :string, default: '0644', desc: 'Mode (permission) of the certificate files on create'
|
|
@@ -111,9 +124,9 @@ module Acmesmith
|
|
|
111
124
|
method_option :chain_file, type: :string, required: false , banner: 'PATH', desc: 'Path to save a certificate chain (root and intermediate CA)'
|
|
112
125
|
method_option :certificate_file, type: :string, required: false, banner: 'PATH', desc: 'Path to save a certficiate'
|
|
113
126
|
method_option :atomic, type: :boolean, default: true, desc: 'Enable atomic file update with rename(2)'
|
|
114
|
-
def save(
|
|
127
|
+
def save(name)
|
|
115
128
|
client.save(
|
|
116
|
-
|
|
129
|
+
name,
|
|
117
130
|
version: options[:version],
|
|
118
131
|
key_mode: options[:key_mode],
|
|
119
132
|
certificate_mode: options[:certificate_mode],
|
|
@@ -127,11 +140,11 @@ module Acmesmith
|
|
|
127
140
|
)
|
|
128
141
|
end
|
|
129
142
|
|
|
130
|
-
desc 'save-pkcs12
|
|
143
|
+
desc 'save-pkcs12 NAME', 'Save ceriticate and private key to .p12 file'
|
|
131
144
|
method_option :version, type: :string, default: 'current'
|
|
132
145
|
method_option :output, type: :string, required: true, banner: 'PATH', desc: 'Path to output file'
|
|
133
146
|
method_option :mode, type: :string, default: '0600', desc: 'Mode (permission) of the output file on create'
|
|
134
|
-
def save_pkcs12(
|
|
147
|
+
def save_pkcs12(name)
|
|
135
148
|
print 'Passphrase: '
|
|
136
149
|
passphrase = $stdin.noecho { $stdin.gets }.chomp
|
|
137
150
|
print "\nPassphrase (confirm): "
|
|
@@ -139,13 +152,13 @@ module Acmesmith
|
|
|
139
152
|
puts
|
|
140
153
|
|
|
141
154
|
raise ArgumentError, "Passphrase doesn't match" if passphrase != passphrase2
|
|
142
|
-
client.save_pkcs12(
|
|
155
|
+
client.save_pkcs12(name, version: options[:version], mode: options[:mode], output: options[:output], passphrase: passphrase)
|
|
143
156
|
end
|
|
144
157
|
|
|
145
|
-
desc "autorenew [
|
|
158
|
+
desc "autorenew [NAMES]", "request renewal of certificates which expires soon"
|
|
146
159
|
method_option :days, type: :numeric, aliases: %w(-d), default: nil, desc: 'specify threshold in days to select certificates to renew'
|
|
147
160
|
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(*
|
|
161
|
+
def autorenew(*names)
|
|
149
162
|
remaining_life = case options[:remaining_life]
|
|
150
163
|
when %r{\A\d+/\d+\z}
|
|
151
164
|
Rational(options[:remaining_life])
|
|
@@ -156,12 +169,12 @@ module Acmesmith
|
|
|
156
169
|
else
|
|
157
170
|
raise ArgumentError, "invalid format for --remaining-life: it must be in '..%' or '../..'"
|
|
158
171
|
end
|
|
159
|
-
client.autorenew(days: options[:days], remaining_life: remaining_life,
|
|
172
|
+
client.autorenew(days: options[:days], remaining_life: remaining_life, names: names.empty? ? nil : names)
|
|
160
173
|
end
|
|
161
174
|
|
|
162
|
-
desc "add-san
|
|
163
|
-
def add_san(
|
|
164
|
-
client.add_san(
|
|
175
|
+
desc "add-san NAME [ADDITIONAL_SANS]", "request renewal of existing certificate with additional SANs"
|
|
176
|
+
def add_san(name, *add_sans)
|
|
177
|
+
client.add_san(name, *add_sans)
|
|
165
178
|
end
|
|
166
179
|
|
|
167
180
|
desc "register CONTACT", "(deprecated, use 'acmesmith new-account')"
|
|
@@ -175,16 +188,16 @@ module Acmesmith
|
|
|
175
188
|
new_account(contact)
|
|
176
189
|
end
|
|
177
190
|
|
|
178
|
-
desc "request
|
|
191
|
+
desc "request NAME [SAN]", "(deprecated, use 'acmesmith order')"
|
|
179
192
|
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(
|
|
193
|
+
def request(name, *sans)
|
|
181
194
|
warn "!"
|
|
182
195
|
warn "! DEPRECATION WARNING: Use 'acmesmith order' command"
|
|
183
196
|
warn "! There is no user-facing breaking changes. It takes the same arguments with 'acmesmith request'."
|
|
184
197
|
warn "!"
|
|
185
198
|
warn "! This is due to change in semantics of ACME v2. ACME v2 defines 'order' instead of 'request' in v1."
|
|
186
199
|
warn "!"
|
|
187
|
-
order(
|
|
200
|
+
order(name, *sans)
|
|
188
201
|
end
|
|
189
202
|
|
|
190
203
|
private
|
data/lib/acmesmith/config.rb
CHANGED
|
@@ -2,6 +2,7 @@ require 'yaml'
|
|
|
2
2
|
require 'acmesmith/storages'
|
|
3
3
|
require 'acmesmith/challenge_responders'
|
|
4
4
|
require 'acmesmith/challenge_responder_filter'
|
|
5
|
+
require 'acmesmith/subject_name_filter'
|
|
5
6
|
require 'acmesmith/domain_name_filter'
|
|
6
7
|
require 'acmesmith/post_issuing_hooks'
|
|
7
8
|
|
|
@@ -9,6 +10,7 @@ module Acmesmith
|
|
|
9
10
|
class Config
|
|
10
11
|
ChallengeResponderRule = Struct.new(:challenge_responder, :filter, keyword_init: true)
|
|
11
12
|
ChainPreference = Struct.new(:root_issuer_name, :root_issuer_key_id, :filter, keyword_init: true)
|
|
13
|
+
ProfileRule = Data.define(:name, :filter)
|
|
12
14
|
|
|
13
15
|
def self.load_yaml(path)
|
|
14
16
|
new YAML.load_file(path)
|
|
@@ -35,6 +37,10 @@ module Acmesmith
|
|
|
35
37
|
if @config.key?('chain_preferences') && !@config.fetch('chain_preferences').kind_of?(Array)
|
|
36
38
|
raise ArgumentError, "config['chain_preferences'] must be an Array"
|
|
37
39
|
end
|
|
40
|
+
|
|
41
|
+
if @config.key?('profiles') && !@config.fetch('profiles').kind_of?(Array)
|
|
42
|
+
raise ArgumentError, "config['profiles'] must be an Array"
|
|
43
|
+
end
|
|
38
44
|
end
|
|
39
45
|
|
|
40
46
|
def [](key)
|
|
@@ -124,6 +130,16 @@ module Acmesmith
|
|
|
124
130
|
end
|
|
125
131
|
end
|
|
126
132
|
|
|
133
|
+
def profile_rules
|
|
134
|
+
@profile_rules ||= begin
|
|
135
|
+
specs = @config['profiles'] || []
|
|
136
|
+
specs.map do |spec|
|
|
137
|
+
filter = spec.fetch('filter', {}).map { |k,v| [k.to_sym, v] }.to_h
|
|
138
|
+
ProfileRule.new(name: spec['name'], filter: SubjectNameFilter.new(**filter))
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
127
143
|
# def post_actions
|
|
128
144
|
# end
|
|
129
145
|
end
|
|
@@ -7,23 +7,28 @@ 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:, profile_rules: [], 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
|
|
21
23
|
@chain_preferences = chain_preferences
|
|
24
|
+
@profile_rules = profile_rules
|
|
22
25
|
@not_before = not_before
|
|
23
26
|
@not_after = not_after
|
|
27
|
+
|
|
28
|
+
@order_url = nil # https://github.com/unixcharles/acme-client/pull/263
|
|
24
29
|
end
|
|
25
30
|
|
|
26
|
-
attr_reader :acme, :identifiers, :private_key, :challenge_responder_rules, :chain_preferences, :not_before, :not_after
|
|
31
|
+
attr_reader :acme, :common_name, :identifiers, :private_key, :challenge_responder_rules, :chain_preferences, :profile_rules, :not_before, :not_after
|
|
27
32
|
|
|
28
33
|
def perform!
|
|
29
34
|
puts "=> Ordering a certificate for the following identifiers:"
|
|
@@ -33,9 +38,15 @@ module Acmesmith
|
|
|
33
38
|
puts " * SAN: #{san}"
|
|
34
39
|
end
|
|
35
40
|
|
|
41
|
+
resolved_profile = profile
|
|
42
|
+
if resolved_profile
|
|
43
|
+
puts
|
|
44
|
+
puts " * Profile: #{resolved_profile}"
|
|
45
|
+
end
|
|
46
|
+
|
|
36
47
|
puts
|
|
37
48
|
puts "=> Placing an order"
|
|
38
|
-
@order = acme.new_order(identifiers: identifiers, not_before: not_before, not_after: not_after)
|
|
49
|
+
@order = acme.new_order(identifiers: identifiers, not_before: not_before, not_after: not_after, profile: resolved_profile)
|
|
39
50
|
puts " * URL: #{order.url}"
|
|
40
51
|
|
|
41
52
|
ensure_authorization()
|
|
@@ -43,7 +54,7 @@ module Acmesmith
|
|
|
43
54
|
finalize_order()
|
|
44
55
|
wait_order_for_complete()
|
|
45
56
|
|
|
46
|
-
@certificate = Certificate.by_issuance(pem_chain, csr)
|
|
57
|
+
@certificate = Certificate.by_issuance(pem_chain, csr, name: common_name)
|
|
47
58
|
|
|
48
59
|
puts
|
|
49
60
|
puts "=> Certificate issued"
|
|
@@ -70,12 +81,16 @@ module Acmesmith
|
|
|
70
81
|
puts
|
|
71
82
|
|
|
72
83
|
print " * Requesting..."
|
|
84
|
+
@order_url = order.url if defined?(Acme::Client::Error::OrderNotReloadable)
|
|
73
85
|
order.finalize(csr: csr)
|
|
74
86
|
puts" [ ok ]"
|
|
75
87
|
end
|
|
76
88
|
|
|
77
89
|
def wait_order_for_complete
|
|
90
|
+
# Workaround for https://github.com/unixcharles/acme-client/pull/263
|
|
91
|
+
|
|
78
92
|
while %w(ready processing).include?(order.status)
|
|
93
|
+
order.instance_variable_set(:@url, @order_url) if @order_url
|
|
79
94
|
order.reload()
|
|
80
95
|
puts " * Waiting for complete: status=#{order.status}"
|
|
81
96
|
sleep 2
|
|
@@ -97,16 +112,15 @@ module Acmesmith
|
|
|
97
112
|
@order or raise "BUG: order not yet generated"
|
|
98
113
|
end
|
|
99
114
|
|
|
100
|
-
# @return [String]
|
|
101
|
-
def common_name
|
|
102
|
-
identifiers.first
|
|
103
|
-
end
|
|
104
|
-
|
|
105
115
|
# @return [Array<String>]
|
|
106
116
|
def sans
|
|
107
117
|
identifiers[1..-1]
|
|
108
118
|
end
|
|
109
119
|
|
|
120
|
+
def profile
|
|
121
|
+
profile_rules.find { |rule| rule.filter.match?(common_name) }&.name
|
|
122
|
+
end
|
|
123
|
+
|
|
110
124
|
# @return [Acme::Client::CertificateRequest]
|
|
111
125
|
def csr
|
|
112
126
|
@csr ||= Acme::Client::CertificateRequest.new(subject: { common_name: common_name }, names: sans, private_key: private_key)
|
|
@@ -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
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'acmesmith/domain_name_filter'
|
|
2
|
+
|
|
3
|
+
module Acmesmith
|
|
4
|
+
class SubjectNameFilter
|
|
5
|
+
def initialize(subject_name_exact: nil, subject_name_suffix: nil, subject_name_regexp: nil)
|
|
6
|
+
@domain_name_filter = DomainNameFilter.new(
|
|
7
|
+
exact: subject_name_exact,
|
|
8
|
+
suffix: subject_name_suffix,
|
|
9
|
+
regexp: subject_name_regexp,
|
|
10
|
+
)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def match?(domain)
|
|
14
|
+
@domain_name_filter.match?(domain)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
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.9.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
|
|
@@ -191,14 +183,16 @@ files:
|
|
|
191
183
|
- lib/acmesmith/storages/base.rb
|
|
192
184
|
- lib/acmesmith/storages/filesystem.rb
|
|
193
185
|
- lib/acmesmith/storages/s3.rb
|
|
186
|
+
- lib/acmesmith/subject_name_filter.rb
|
|
194
187
|
- lib/acmesmith/utils/finder.rb
|
|
195
188
|
- lib/acmesmith/version.rb
|
|
196
|
-
- script/console
|
|
197
|
-
- script/setup
|
|
198
189
|
homepage: https://github.com/sorah/acmesmith
|
|
199
190
|
licenses:
|
|
200
191
|
- MIT
|
|
201
|
-
metadata:
|
|
192
|
+
metadata:
|
|
193
|
+
homepage_uri: https://github.com/sorah/acmesmith
|
|
194
|
+
source_code_uri: https://github.com/sorah/acmesmith
|
|
195
|
+
changelog_uri: https://github.com/sorah/acmesmith/blob/master/CHANGELOG.md
|
|
202
196
|
rdoc_options: []
|
|
203
197
|
require_paths:
|
|
204
198
|
- lib
|
|
@@ -213,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
213
207
|
- !ruby/object:Gem::Version
|
|
214
208
|
version: '0'
|
|
215
209
|
requirements: []
|
|
216
|
-
rubygems_version:
|
|
210
|
+
rubygems_version: 4.0.3
|
|
217
211
|
specification_version: 4
|
|
218
212
|
summary: ACME client (Let's encrypt client) to manage certificate in multi server
|
|
219
213
|
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
|