bullion 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/Gemfile.lock +16 -7
- data/Rakefile +1 -1
- data/bullion.gemspec +2 -1
- data/lib/bullion/challenge_clients/dns.rb +3 -4
- data/lib/bullion/challenge_clients/http.rb +2 -3
- data/lib/bullion/helpers/acme.rb +9 -5
- data/lib/bullion/helpers/ssl.rb +1 -1
- data/lib/bullion/models/challenge.rb +8 -10
- data/lib/bullion/service.rb +1 -1
- data/lib/bullion/version.rb +1 -1
- data/lib/bullion.rb +82 -40
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 010c02c548debe154dae0438bf036238266630be93974cdb264983843286a56e
|
4
|
+
data.tar.gz: 02412dd0d7268bcef1e19b7c863a21f7252d5a7633fecd236bc768758506169a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65c9667eb7c624797e20c054694b7f2da90ad36ed541836e02500b0cd7bcca0359d432d3062b77dacb4c5455e11d741a91fa5d61a596b673f6bf1fe302a8968a
|
7
|
+
data.tar.gz: 6bbb7cab6cf45c08899fafbf9291dbe9de7a61ac10af364d622d72eea5b857ee4889c3a4e813b7d5adac8383596c2dec9bb6bf111e2405501a7604293432f20e
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
bullion (0.4.
|
4
|
+
bullion (0.4.2)
|
5
|
+
dry-configurable (~> 1.1)
|
5
6
|
httparty (~> 0.21)
|
6
7
|
json (~> 2.6)
|
7
8
|
jwt (~> 2.7)
|
@@ -48,6 +49,12 @@ GEM
|
|
48
49
|
docile (1.4.0)
|
49
50
|
drb (2.2.0)
|
50
51
|
ruby2_keywords
|
52
|
+
dry-configurable (1.1.0)
|
53
|
+
dry-core (~> 1.0, < 2)
|
54
|
+
zeitwerk (~> 2.6)
|
55
|
+
dry-core (1.0.1)
|
56
|
+
concurrent-ruby (~> 1.0)
|
57
|
+
zeitwerk (~> 2.6)
|
51
58
|
e2mmap (0.1.0)
|
52
59
|
faraday (2.7.12)
|
53
60
|
base64
|
@@ -70,7 +77,6 @@ GEM
|
|
70
77
|
kramdown (~> 2.0)
|
71
78
|
language_server-protocol (3.17.0.3)
|
72
79
|
mini_mime (1.1.5)
|
73
|
-
mini_portile2 (2.8.5)
|
74
80
|
minitest (5.20.0)
|
75
81
|
multi_json (1.15.0)
|
76
82
|
multi_xml (0.6.0)
|
@@ -79,8 +85,9 @@ GEM
|
|
79
85
|
mutex_m (0.2.0)
|
80
86
|
mysql2 (0.5.5)
|
81
87
|
nio4r (2.6.1)
|
82
|
-
nokogiri (1.15.5)
|
83
|
-
|
88
|
+
nokogiri (1.15.5-arm64-darwin)
|
89
|
+
racc (~> 1.4)
|
90
|
+
nokogiri (1.15.5-x86_64-linux)
|
84
91
|
racc (~> 1.4)
|
85
92
|
openssl (3.2.0)
|
86
93
|
parallel (1.23.0)
|
@@ -180,8 +187,8 @@ GEM
|
|
180
187
|
thor (~> 1.0)
|
181
188
|
tilt (~> 2.0)
|
182
189
|
yard (~> 0.9, >= 0.9.24)
|
183
|
-
sqlite3 (1.6.9)
|
184
|
-
|
190
|
+
sqlite3 (1.6.9-arm64-darwin)
|
191
|
+
sqlite3 (1.6.9-x86_64-linux)
|
185
192
|
thor (1.3.0)
|
186
193
|
tilt (2.3.0)
|
187
194
|
timeout (0.4.1)
|
@@ -189,9 +196,11 @@ GEM
|
|
189
196
|
concurrent-ruby (~> 1.0)
|
190
197
|
unicode-display_width (2.5.0)
|
191
198
|
yard (0.9.34)
|
199
|
+
zeitwerk (2.6.12)
|
192
200
|
|
193
201
|
PLATFORMS
|
194
|
-
|
202
|
+
arm64-darwin-23
|
203
|
+
x86_64-linux
|
195
204
|
|
196
205
|
DEPENDENCIES
|
197
206
|
acme-client (~> 2.0)
|
data/Rakefile
CHANGED
@@ -94,7 +94,7 @@ desc "Cleans up test or demo environment"
|
|
94
94
|
task :cleanup do
|
95
95
|
at_exit do
|
96
96
|
if File.exist?("#{File.expand_path(".")}/tmp/daemon.pid")
|
97
|
-
system("kill $(cat #{File.expand_path(".")}/tmp/daemon.pid)")
|
97
|
+
system("kill $(cat #{File.expand_path(".")}/tmp/daemon.pid) > /dev/null 2>&1")
|
98
98
|
end
|
99
99
|
FileUtils.rm_f(File.join(File.expand_path("."), "tmp", "tls.crt"))
|
100
100
|
FileUtils.rm_f(File.join(File.expand_path("."), "tmp", "tls.key"))
|
data/bullion.gemspec
CHANGED
@@ -24,8 +24,9 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
25
|
spec.require_paths = ["lib"]
|
26
26
|
|
27
|
-
spec.required_ruby_version = "~> 3.
|
27
|
+
spec.required_ruby_version = "~> 3.2"
|
28
28
|
|
29
|
+
spec.add_runtime_dependency "dry-configurable", "~> 1.1"
|
29
30
|
spec.add_runtime_dependency "httparty", "~> 0.21"
|
30
31
|
spec.add_runtime_dependency "json", "~> 2.6"
|
31
32
|
spec.add_runtime_dependency "jwt", "~> 2.7"
|
@@ -5,9 +5,8 @@ module Bullion
|
|
5
5
|
# ACME DNS01 Challenge Client
|
6
6
|
# @see https://tools.ietf.org/html/rfc8555#section-8.4
|
7
7
|
class DNS < ChallengeClient
|
8
|
-
def
|
9
|
-
|
10
|
-
end
|
8
|
+
def self.acme_type = "dns-01"
|
9
|
+
def type = "DNS01"
|
11
10
|
|
12
11
|
def perform
|
13
12
|
value = dns_value
|
@@ -29,7 +28,7 @@ module Bullion
|
|
29
28
|
|
30
29
|
def dns_value
|
31
30
|
# Randomly select a nameserver to pull the TXT record
|
32
|
-
nameserver =
|
31
|
+
nameserver = Bullion.config.nameservers.sample
|
33
32
|
|
34
33
|
LOGGER.debug "Looking up #{dns_name}"
|
35
34
|
records = records_for(dns_name, nameserver)
|
data/lib/bullion/helpers/acme.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Bullion
|
4
|
+
# Common helper functions
|
4
5
|
module Helpers
|
5
6
|
# ACME-specific helper functions
|
6
7
|
module Acme
|
@@ -144,12 +145,9 @@ module Bullion
|
|
144
145
|
end
|
145
146
|
|
146
147
|
# Extract domains that end with something in our allowed domains list
|
147
|
-
valid_domains = order_domains
|
148
|
-
endings = CA_DOMAINS.select { |d| domain["value"].end_with?(d) }
|
149
|
-
endings.empty?
|
150
|
-
end
|
148
|
+
valid_domains = extract_valid_order_domains(order_domains)
|
151
149
|
|
152
|
-
# Only allow
|
150
|
+
# Only allow configured domains...
|
153
151
|
unless order_domains == valid_domains
|
154
152
|
raise(
|
155
153
|
Bullion::Acme::Errors::InvalidOrder,
|
@@ -187,6 +185,12 @@ module Bullion
|
|
187
185
|
# rubocop:enable Metrics/AbcSize
|
188
186
|
# rubocop:enable Metrics/CyclomaticComplexity
|
189
187
|
# rubocop:enable Metrics/PerceivedComplexity
|
188
|
+
|
189
|
+
def extract_valid_order_domains(order_domains)
|
190
|
+
order_domains.reject do |domain|
|
191
|
+
Bullion.config.ca.domains.none? { domain["value"].end_with?(_1) }
|
192
|
+
end
|
193
|
+
end
|
190
194
|
end
|
191
195
|
end
|
192
196
|
end
|
data/lib/bullion/helpers/ssl.rb
CHANGED
@@ -184,7 +184,7 @@ module Bullion
|
|
184
184
|
def filter_sans(potential_sans)
|
185
185
|
# Select only those that are part of the appropriate domain
|
186
186
|
potential_sans.select do |alt|
|
187
|
-
|
187
|
+
Bullion.config.ca.domains.filter_map { |domain| alt.end_with?(".#{domain}") }.any?
|
188
188
|
end
|
189
189
|
end
|
190
190
|
|
@@ -8,12 +8,11 @@ module Bullion
|
|
8
8
|
|
9
9
|
belongs_to :authorization
|
10
10
|
|
11
|
-
validates :acme_type, inclusion: {
|
11
|
+
validates :acme_type, inclusion: {
|
12
|
+
in: -> { Bullion.config.acme.challenge_clients.map(&:acme_type) }
|
13
|
+
}
|
12
14
|
validates :status, inclusion: { in: %w[invalid pending processing valid] }
|
13
15
|
|
14
|
-
scope :dns01, -> { where(acme_type: "dns-01") }
|
15
|
-
scope :http01, -> { where(acme_type: "http-01") }
|
16
|
-
|
17
16
|
def identifier
|
18
17
|
authorization.identifier["value"]
|
19
18
|
end
|
@@ -29,15 +28,14 @@ module Bullion
|
|
29
28
|
end
|
30
29
|
|
31
30
|
def client
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
when "http-01"
|
36
|
-
HTTP_CHALLENGE_CLIENT.new(self)
|
37
|
-
else
|
31
|
+
challenge_class = Bullion.acme.challenge_clients.find { _1.acme_type == acme_type }
|
32
|
+
|
33
|
+
unless challenge_class
|
38
34
|
raise Bullion::Acme::Errors::UnsupportedChallengeType,
|
39
35
|
"Challenge type '#{acme_type}' is not supported by Bullion."
|
40
36
|
end
|
37
|
+
|
38
|
+
challenge_class.new(self)
|
41
39
|
end
|
42
40
|
end
|
43
41
|
end
|
data/lib/bullion/service.rb
CHANGED
data/lib/bullion/version.rb
CHANGED
data/lib/bullion.rb
CHANGED
@@ -9,6 +9,7 @@ require "logger"
|
|
9
9
|
require "openssl"
|
10
10
|
|
11
11
|
# External requirements
|
12
|
+
require "dry-configurable"
|
12
13
|
require "sinatra/base"
|
13
14
|
require "sinatra/contrib"
|
14
15
|
require "sinatra/custom_logger"
|
@@ -20,50 +21,57 @@ require "httparty"
|
|
20
21
|
|
21
22
|
# The top-level module for Bullion
|
22
23
|
module Bullion
|
24
|
+
extend Dry::Configurable
|
25
|
+
|
23
26
|
class Error < StandardError; end
|
24
27
|
class ConfigError < Error; end
|
25
28
|
|
29
|
+
# Set up logging
|
26
30
|
LOGGER = Logger.new($stdout)
|
27
|
-
|
28
|
-
# Config through environment variables
|
29
|
-
CA_DIR = File.expand_path ENV.fetch("CA_DIR", "tmp")
|
30
|
-
CA_SECRET = ENV.fetch("CA_SECRET", "SomeS3cret")
|
31
|
-
CA_KEY_PATH = ENV.fetch("CA_KEY_PATH") { File.join(CA_DIR, "tls.key") }
|
32
|
-
CA_CERT_PATH = ENV.fetch("CA_CERT_PATH") { File.join(CA_DIR, "tls.crt") }
|
33
|
-
CA_DOMAINS = ENV.fetch("CA_DOMAINS", "example.com").split(",")
|
34
|
-
|
35
|
-
# Set up log level
|
36
31
|
LOGGER.level = ENV.fetch("LOG_LEVEL", :warn)
|
37
32
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
adapter: "mysql2",
|
47
|
-
database: ENV.fetch("DB_NAME", "bullion"),
|
48
|
-
encoding: ENV.fetch("DB_ENCODING", "utf8mb4"),
|
49
|
-
pool: Integer(ENV.fetch("MAX_THREADS", 32)),
|
50
|
-
username: ENV.fetch("DB_USERNAME", "root"),
|
51
|
-
password: ENV.fetch("DB_PASSWORD", nil),
|
52
|
-
host: ENV.fetch("DB_HOST", "localhost")
|
33
|
+
setting :ca, reader: true do
|
34
|
+
setting :dir, default: "tmp", constructor: -> { File.expand_path(_1) }
|
35
|
+
setting :secret, default: "SomeS3cret"
|
36
|
+
setting(
|
37
|
+
:key_path,
|
38
|
+
default: "tls.key",
|
39
|
+
constructor: lambda { |v|
|
40
|
+
v.include?("/") ? File.expand_path(v) : File.join(Bullion.config.ca.dir, v)
|
53
41
|
}
|
54
|
-
|
55
|
-
|
42
|
+
)
|
43
|
+
setting(
|
44
|
+
:cert_path,
|
45
|
+
default: "tls.crt",
|
46
|
+
constructor: lambda { |v|
|
47
|
+
v.include?("/") ? File.expand_path(v) : File.join(Bullion.config.ca.dir, v)
|
48
|
+
}
|
49
|
+
)
|
50
|
+
setting :domains, default: "example.com", constructor: -> { _1.split(",") }
|
51
|
+
# 90 days cert expiration
|
52
|
+
setting :cert_validity_duration, default: 60 * 60 * 24 * 30 * 3, constructor: -> { Integer(_1) }
|
53
|
+
end
|
54
|
+
|
55
|
+
setting :acme, reader: true do
|
56
|
+
setting(
|
57
|
+
:challenge_clients,
|
58
|
+
default: ["Bullion::ChallengeClients::DNS", "Bullion::ChallengeClients::HTTP"],
|
59
|
+
constructor: -> { _1.map { |n| Kernel.const_get(n.to_s) } }
|
60
|
+
)
|
61
|
+
end
|
56
62
|
|
57
|
-
|
63
|
+
setting :db_url, reader: true
|
64
|
+
|
65
|
+
setting :nameservers, default: [], constructor: -> { _1.split(",") }
|
58
66
|
|
59
67
|
MetricsRegistry = Prometheus::Client.registry
|
60
68
|
|
61
69
|
def self.ca_key
|
62
|
-
@ca_key ||= OpenSSL::PKey::RSA.new(File.read(
|
70
|
+
@ca_key ||= OpenSSL::PKey::RSA.new(File.read(config.ca.key_path), config.ca.secret)
|
63
71
|
end
|
64
72
|
|
65
73
|
def self.ca_cert
|
66
|
-
@ca_cert ||= OpenSSL::X509::Certificate.new(File.read(
|
74
|
+
@ca_cert ||= OpenSSL::X509::Certificate.new(File.read(config.ca.cert_path))
|
67
75
|
end
|
68
76
|
|
69
77
|
def self.rotate_keys!
|
@@ -76,15 +84,50 @@ module Bullion
|
|
76
84
|
|
77
85
|
# Ensures configuration settings are valid
|
78
86
|
# @see https://support.apple.com/en-us/HT211025
|
79
|
-
def self.validate_config!
|
80
|
-
raise ConfigError, "Invalid Key Passphrase" unless
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
87
|
+
def self.validate_config! # rubocop:disable Metrics/AbcSize
|
88
|
+
raise ConfigError, "Invalid Key Passphrase" unless config.ca.secret.is_a?(String)
|
89
|
+
|
90
|
+
unless File.readable?(config.ca.key_path)
|
91
|
+
raise ConfigError,
|
92
|
+
"Invalid Key Path: #{config.ca.key_path}"
|
93
|
+
end
|
94
|
+
unless File.readable?(config.ca.cert_path)
|
95
|
+
raise ConfigError,
|
96
|
+
"Invalid Cert Path: #{config.ca.cert_path}"
|
97
|
+
end
|
98
|
+
if 60 * 60 * 24 * 397 < config.ca.cert_validity_duration
|
99
|
+
raise ConfigError,
|
100
|
+
"Cert Validity Too Long"
|
101
|
+
end
|
102
|
+
if 60 * 60 * 24 * 2 > config.ca.cert_validity_duration
|
103
|
+
raise ConfigError,
|
104
|
+
"Cert Validity Too Short"
|
105
|
+
end
|
106
|
+
raise ConfigError, "Missing DATABASE_URL" unless config.db_url
|
85
107
|
end
|
86
108
|
end
|
87
109
|
|
110
|
+
Bullion.configure do |config|
|
111
|
+
# Config through environment variables
|
112
|
+
ca_dir = ENV.fetch("CA_DIR", nil)
|
113
|
+
ca_secret = ENV.fetch("CA_SECRET", nil)
|
114
|
+
ca_key_path = ENV.fetch("CA_KEY_PATH", nil)
|
115
|
+
ca_cert_path = ENV.fetch("CA_CERT_PATH", nil)
|
116
|
+
ca_domains = ENV.fetch("CA_DOMAINS", nil)
|
117
|
+
cert_dur = ENV.fetch("CERT_VALIDITY_DURATION", nil)
|
118
|
+
db_url = ENV.fetch("DATABASE_URL", nil)
|
119
|
+
nameservers = ENV.fetch("DNS01_NAMESERVERS", nil)
|
120
|
+
|
121
|
+
config.ca.dir = ca_dir if ca_dir
|
122
|
+
config.ca.secret = ca_secret if ca_secret
|
123
|
+
config.ca.key_path = ca_key_path if ca_key_path
|
124
|
+
config.ca.cert_path = ca_cert_path if ca_cert_path
|
125
|
+
config.ca.domains = ca_domains if ca_domains
|
126
|
+
config.ca.cert_validity_duration = cert_dur if cert_dur
|
127
|
+
config.db_url = db_url if db_url
|
128
|
+
config.nameservers = nameservers if nameservers
|
129
|
+
end
|
130
|
+
|
88
131
|
# Internal requirements
|
89
132
|
require "bullion/version"
|
90
133
|
require "bullion/acme/error"
|
@@ -103,9 +146,8 @@ if %w[development test].include?(ENV["RACK_ENV"])
|
|
103
146
|
require "bullion/rspec/challenge_clients/dns"
|
104
147
|
require "bullion/rspec/challenge_clients/http"
|
105
148
|
|
106
|
-
Bullion
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
Bullion::HTTP_CHALLENGE_CLIENT = Bullion::ChallengeClients::HTTP
|
149
|
+
Bullion.config.acme.challenge_clients = [
|
150
|
+
"Bullion::RSpec::ChallengeClients::DNS",
|
151
|
+
"Bullion::RSpec::ChallengeClients::HTTP"
|
152
|
+
]
|
111
153
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bullion
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Gnagy
|
@@ -10,6 +10,20 @@ bindir: exe
|
|
10
10
|
cert_chain: []
|
11
11
|
date: 2023-11-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-configurable
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: httparty
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -418,7 +432,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
418
432
|
requirements:
|
419
433
|
- - "~>"
|
420
434
|
- !ruby/object:Gem::Version
|
421
|
-
version: '3.
|
435
|
+
version: '3.2'
|
422
436
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
423
437
|
requirements:
|
424
438
|
- - ">="
|