anchor-pki 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anchor-pki
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
- - Anchor
7
+ - Anchor Security, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-18 00:00:00.000000000 Z
11
+ date: 2023-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: acme-client
@@ -24,18 +24,141 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 2.0.13
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.14'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.14'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.9'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.9'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.50'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.50'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.22'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.22'
97
+ - !ruby/object:Gem::Dependency
98
+ name: vcr
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '6.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '6.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.8'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.8'
27
125
  description: Anchor is a hosted PKI platform for your internal organization.
28
- email: support@anchor.dev
126
+ email:
29
127
  executables: []
30
128
  extensions: []
31
- extra_rdoc_files: []
129
+ extra_rdoc_files:
130
+ - LICENSE.txt
131
+ - README.md
132
+ - CHANGELOG.md
32
133
  files:
134
+ - CHANGELOG.md
135
+ - Gemfile
136
+ - Gemfile.lock
137
+ - LICENSE.txt
138
+ - README.md
139
+ - Rakefile
33
140
  - lib/anchor-pki.rb
34
- - lib/anchor-pki/auto_cert.rb
35
- homepage: https://anchor.dev/
141
+ - lib/anchor.rb
142
+ - lib/anchor/auto_cert.rb
143
+ - lib/anchor/auto_cert/configuration.rb
144
+ - lib/anchor/auto_cert/identifier_policy.rb
145
+ - lib/anchor/auto_cert/integration.rb
146
+ - lib/anchor/auto_cert/integration/puma.rb
147
+ - lib/anchor/auto_cert/manager.rb
148
+ - lib/anchor/auto_cert/policy_check.rb
149
+ - lib/anchor/auto_cert/policy_check/for_hostname.rb
150
+ - lib/anchor/auto_cert/policy_check/for_ipaddr.rb
151
+ - lib/anchor/auto_cert/policy_check/for_wildcard_hostname.rb
152
+ - lib/anchor/auto_cert/railtie.rb
153
+ - lib/anchor/auto_cert/registry.rb
154
+ - lib/anchor/auto_cert/terms_of_service_acceptor.rb
155
+ - lib/anchor/version.rb
156
+ homepage: https://anchor.dev
36
157
  licenses:
37
158
  - MIT
38
- metadata: {}
159
+ metadata:
160
+ homepage_uri: https://anchor.dev
161
+ rubygems_mfa_required: 'true'
39
162
  post_install_message:
40
163
  rdoc_options: []
41
164
  require_paths:
@@ -44,7 +167,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
44
167
  requirements:
45
168
  - - ">="
46
169
  - !ruby/object:Gem::Version
47
- version: '0'
170
+ version: '2.3'
48
171
  required_rubygems_version: !ruby/object:Gem::Requirement
49
172
  requirements:
50
173
  - - ">="
@@ -1,134 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'acme/client'
4
- require 'uri'
5
-
6
- module Anchor
7
- # AutoCert provides automatic access to certificates from Anchor, but
8
- # theoretically it could work with Let's Encrypt and any other ACME-based CA.
9
- class AutoCert
10
- ACCEPT_ANY_TOS = [//].freeze
11
-
12
- def initialize(name_policies, tos_acceptor, directory, account: {}, **opts)
13
- @name_policies = name_policies
14
- @tos_acceptor = tos_acceptor
15
- @directory_url = URI.parse(directory)
16
-
17
- @renew_before = opts[:renew_before]
18
- @work_dir = Pathname(opts[:work_dir] || Dir.mktmpdir)
19
- @cache = opts[:cache]
20
- @client = opts[:client]
21
-
22
- @client ||= new_client(**account)
23
- @account_opts = account
24
- end
25
-
26
- def certificate(*names, algo: :ecdsa, common_name: names.first, **opts)
27
- if (arr = unmatched_names(names)).size.positive?
28
- raise StandardError, "unallowed names '#{arr.join(',')}'"
29
- end
30
-
31
- key_pem = @cache&.read("#{common_name}+#{algo}")
32
- cert_pem = @cache&.read(common_name)
33
-
34
- if key_pem.nil? || cert_pem.nil? || needs_renewal?(cert_pem)
35
- cert_pem, key_pem = provision(names, algo, common_name, **opts)
36
-
37
- @cache&.write("#{common_name}+#{algo}", key_pem)
38
- @cache&.write(common_name, cert_pem)
39
- end
40
-
41
- cert = (@work_dir / common_name).open('w') { |f| f << cert_pem }.path
42
- key = (@work_dir / "#{common_name}+#{algo}").open('w') { |f| f << key_pem }.path
43
-
44
- [cert, key]
45
- end
46
-
47
- private
48
-
49
- def provision(names, algo, common_name, **opts)
50
- load_or_build_account
51
-
52
- order = @client.new_order(identifiers: names, **opts)
53
-
54
- key_pem ||= new_key(algo).to_pem
55
- csr = Acme::Client::CertificateRequest.new(private_key: parse_key_pem(key_pem), common_name: common_name, names: names)
56
-
57
- order.finalize(csr: csr)
58
- # TODO: loop over order.authorizations and process the challenges
59
-
60
- while order.status == 'processing'
61
- sleep(1)
62
- order.reload
63
- end
64
-
65
- return order.certificate, key_pem
66
- end
67
-
68
- def load_or_build_account
69
- @account ||= build_account(**@account_opts)
70
- end
71
-
72
- def build_account(contact: nil, external_account_binding: nil, terms_of_service_agreed: false, **)
73
- terms_of_service_agreed ||= @tos_acceptor.any? { |a| a.match?(@client.terms_of_service) }
74
-
75
- @client.new_account(contact: contact, terms_of_service_agreed: terms_of_service_agreed, external_account_binding: external_account_binding)
76
- end
77
-
78
- def needs_renewal?(cert_pem)
79
- cert = OpenSSL::X509::Certificate.new(cert_pem)
80
-
81
- (Time.zone.now + @renew_before.to_i) > cert.not_after
82
- end
83
-
84
- def new_client(account_key: nil, contact: nil, **)
85
- account_key ||= fetch_account_key(contact) { new_key(:ecdsa) }
86
-
87
- Acme::Client.new(private_key: account_key, directory: @directory_url)
88
- end
89
-
90
- def new_key(algo)
91
- case algo
92
- when :ecdsa then OpenSSL::PKey::EC.generate('prime256v1')
93
- else
94
- raise "unknown key algo '#{algo}'"
95
- end
96
- end
97
-
98
- def fetch_account_key(contact)
99
- id = "#{contact || 'default'}+#{@directory_url.host}+key"
100
- parse_key_pem(@cache&.fetch(id) { parse_key_pem(yield.to_pem) } || yield.to_pem)
101
- end
102
-
103
- def parse_key_pem(data)
104
- begin
105
- OpenSSL::PKey::RSA.new(data)
106
- rescue StandardError
107
- nil
108
- end ||
109
- begin
110
- OpenSSL::PKey::EC.new(data)
111
- rescue StandardError
112
- nil
113
- end ||
114
- (raise 'unknown key data format')
115
- end
116
-
117
- def unmatched_names(names)
118
- names.reject do |name|
119
- @name_policies.any? do |policy|
120
- case policy
121
- when String then name == policy
122
- when Regexp then policy.match?(name)
123
- when IPAddr
124
- begin
125
- policy.include?(name)
126
- rescue IPAddr::Error
127
- nil
128
- end
129
- end
130
- end
131
- end
132
- end
133
- end
134
- end