bullion 0.4.1 → 0.4.2
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/.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
|
- - ">="
|