bullion 0.4.0 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6477c9733af37acb269b660901c10e74851b023957f5fd197feafd66c680213b
4
- data.tar.gz: c7276cf7cd1dd908efbe7aca58e2df2f388319167f730ca943330c98b758ce52
3
+ metadata.gz: 010c02c548debe154dae0438bf036238266630be93974cdb264983843286a56e
4
+ data.tar.gz: 02412dd0d7268bcef1e19b7c863a21f7252d5a7633fecd236bc768758506169a
5
5
  SHA512:
6
- metadata.gz: 7ecbe32aeb35484a4c982f73b1ccd3cf7e50679ef91f80ade778159faa4f1687f3cae65b676478b236acdded4c169f6e36ea24419cb884e43d0249816147ec27
7
- data.tar.gz: 8464210e06fdb0af52ba5dff099c9615d3976f9b1cc305ce009e84c7fe19713312ae79f0bdeab5989942de86c70cfaede97eba849ee26586b95bf7151d767865
6
+ metadata.gz: 65c9667eb7c624797e20c054694b7f2da90ad36ed541836e02500b0cd7bcca0359d432d3062b77dacb4c5455e11d741a91fa5d61a596b673f6bf1fe302a8968a
7
+ data.tar.gz: 6bbb7cab6cf45c08899fafbf9291dbe9de7a61ac10af364d622d72eea5b857ee4889c3a4e813b7d5adac8383596c2dec9bb6bf111e2405501a7604293432f20e
data/.roxanne.yml CHANGED
@@ -10,8 +10,11 @@ stages:
10
10
  - ./scripts/test.sh
11
11
  release:
12
12
  image: ruby:3.2
13
+ only:
14
+ - main
15
+ publish:
16
+ image: docker:latest
13
17
  scripts:
14
- - ./scripts/release.sh
15
18
  - ./scripts/publish.sh
16
19
  only:
17
20
  - main
data/.rubocop.yml CHANGED
@@ -10,7 +10,7 @@ AllCops:
10
10
  Exclude:
11
11
  - 'db/schema.rb'
12
12
  - 'vendor/**/*'
13
- TargetRubyVersion: 3.1
13
+ TargetRubyVersion: 3.2
14
14
  NewCops: enable
15
15
 
16
16
  Metrics/AbcSize:
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bullion (0.4.0)
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
- mini_portile2 (~> 2.8.2)
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
- mini_portile2 (~> 2.8.0)
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
- ruby
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.1"
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 type
9
- "DNS01"
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 = NAMESERVERS.sample
31
+ nameserver = Bullion.config.nameservers.sample
33
32
 
34
33
  LOGGER.debug "Looking up #{dns_name}"
35
34
  records = records_for(dns_name, nameserver)
@@ -5,9 +5,8 @@ module Bullion
5
5
  # ACME HTTP01 Challenge Client
6
6
  # @see https://tools.ietf.org/html/rfc8555#section-8.3
7
7
  class HTTP < ChallengeClient
8
- def type
9
- "HTTP01"
10
- end
8
+ def self.acme_type = "http-01"
9
+ def type = "HTTP01"
11
10
 
12
11
  def perform
13
12
  response = begin
@@ -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.reject do |domain|
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 CA_DOMAINS domains...
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
@@ -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
- CA_DOMAINS.filter_map { |domain| alt.end_with?(".#{domain}") }.any?
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: { in: %w[http-01 dns-01] }
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
- case acme_type
33
- when "dns-01"
34
- DNS_CHALLENGE_CLIENT.new(self)
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
@@ -10,7 +10,7 @@ module Bullion
10
10
  set :protection, except: :http_origin
11
11
  set :logging, true
12
12
  set :logger, Bullion::LOGGER
13
- set :database, DB_CONNECTION_SETTINGS
13
+ set :database, Bullion.config.db_url
14
14
  set :show_exceptions, false
15
15
  end
16
16
 
@@ -4,6 +4,6 @@ module Bullion
4
4
  VERSION = [
5
5
  0, # major
6
6
  4, # minor
7
- 0 # patch
7
+ 2 # patch
8
8
  ].join(".")
9
9
  end
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
- # 90 days cert expiration
39
- CERT_VALIDITY_DURATION = Integer(
40
- ENV.fetch("CERT_VALIDITY_DURATION", 60 * 60 * 24 * 30 * 3)
41
- )
42
-
43
- DB_CONNECTION_SETTINGS =
44
- ENV.fetch("DATABASE_URL") do
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
- end
55
- DB_CONNECTION_SETTINGS.freeze
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
- NAMESERVERS = ENV.fetch("DNS01_NAMESERVERS", "").split(",")
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(CA_KEY_PATH), CA_SECRET)
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(CA_CERT_PATH))
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 CA_SECRET.is_a?(String)
81
- raise ConfigError, "Invalid Key Path: #{CA_KEY_PATH}" unless File.readable?(CA_KEY_PATH)
82
- raise ConfigError, "Invalid Cert Path: #{CA_CERT_PATH}" unless File.readable?(CA_CERT_PATH)
83
- raise ConfigError, "Cert Validity Too Long" if 60 * 60 * 24 * 397 < CERT_VALIDITY_DURATION
84
- raise ConfigError, "Cert Validity Too Short" if 60 * 60 * 24 * 2 > CERT_VALIDITY_DURATION
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::DNS_CHALLENGE_CLIENT = Bullion::RSpec::ChallengeClients::DNS
107
- Bullion::HTTP_CHALLENGE_CLIENT = Bullion::RSpec::ChallengeClients::HTTP
108
- else
109
- Bullion::DNS_CHALLENGE_CLIENT = Bullion::ChallengeClients::DNS
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.0
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.1'
435
+ version: '3.2'
422
436
  required_rubygems_version: !ruby/object:Gem::Requirement
423
437
  requirements:
424
438
  - - ">="