varanus 0.1.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +8 -27
- data/.travis.yml +1 -2
- data/CHANGELOG.md +20 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +71 -39
- data/README.md +46 -13
- data/Rakefile +2 -0
- data/bin/console +1 -0
- data/docker-compose.yml +1 -1
- data/lib/varanus.rb +33 -0
- data/lib/varanus/dcv.rb +62 -0
- data/lib/varanus/error.rb +2 -0
- data/lib/varanus/reports.rb +73 -0
- data/lib/varanus/rest_resource.rb +56 -0
- data/lib/varanus/ssl.rb +35 -59
- data/lib/varanus/ssl/csr.rb +56 -9
- data/lib/varanus/version.rb +3 -1
- data/varanus.gemspec +4 -3
- metadata +22 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72820b52f9184bfc3c35816e39b1c721d0e4eabf6c660b6c4da95d5cdb1bf025
|
4
|
+
data.tar.gz: 3cc5f9ac737e5c375027db08860443b6ece64dac58ad7cf7e515a9f6009e825a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 614b412e36992ee4a7c99f06a26e7e5f3768960200e2d962e8ffd9d0aaf64502dc81643be9c038b151ca44de0dea84a952f8a58b1d67b1379f1cf0806a3fdbd5
|
7
|
+
data.tar.gz: 484a02bacc17c1b26ad7749d10142bf48570a6260b5557267318d25bb3d051ac3a19030f7522ef9684274424d4a7f25d2e863faedf6d64c75932eb1a9fd63af8
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
AllCops:
|
2
|
-
|
2
|
+
NewCops: disable
|
3
|
+
TargetRubyVersion: 2.5
|
3
4
|
|
4
5
|
Bundler/OrderedGems:
|
5
6
|
AutoCorrect: false
|
6
7
|
|
8
|
+
Layout/LineLength:
|
9
|
+
Max: 90
|
10
|
+
Exclude:
|
11
|
+
- 'test/**/*'
|
12
|
+
|
7
13
|
Metrics/AbcSize:
|
8
14
|
Max: 25
|
9
15
|
Exclude:
|
@@ -14,11 +20,6 @@ Metrics/ClassLength:
|
|
14
20
|
Exclude:
|
15
21
|
- 'test/**/*'
|
16
22
|
|
17
|
-
Metrics/LineLength:
|
18
|
-
Max: 90
|
19
|
-
Exclude:
|
20
|
-
- 'test/**/*'
|
21
|
-
|
22
23
|
Metrics/MethodLength:
|
23
24
|
Max: 20
|
24
25
|
Exclude:
|
@@ -28,23 +29,12 @@ Naming/FileName:
|
|
28
29
|
Exclude:
|
29
30
|
- Gemfile
|
30
31
|
|
31
|
-
Naming/UncommunicativeMethodParamName:
|
32
|
-
AllowedNames:
|
33
|
-
- gb
|
34
|
-
- id
|
35
|
-
- ip
|
36
|
-
- os
|
37
|
-
- vm
|
38
|
-
|
39
32
|
Style/ClassAndModuleChildren:
|
40
|
-
|
33
|
+
EnforcedStyle: compact
|
41
34
|
|
42
35
|
Style/ConditionalAssignment:
|
43
36
|
Enabled: false
|
44
37
|
|
45
|
-
Style/FrozenStringLiteralComment:
|
46
|
-
Enabled: false
|
47
|
-
|
48
38
|
Style/MethodDefParentheses:
|
49
39
|
EnforcedStyle: require_no_parentheses_except_multiline
|
50
40
|
|
@@ -57,14 +47,5 @@ Style/RescueModifier:
|
|
57
47
|
Style/SymbolArray:
|
58
48
|
EnforcedStyle: brackets
|
59
49
|
|
60
|
-
Style/TrailingCommaInArguments:
|
61
|
-
EnforcedStyleForMultiline: no_comma
|
62
|
-
|
63
|
-
Style/TrailingCommaInArrayLiteral:
|
64
|
-
EnforcedStyleForMultiline: no_comma
|
65
|
-
|
66
|
-
Style/TrailingCommaInHashLiteral:
|
67
|
-
EnforcedStyleForMultiline: no_comma
|
68
|
-
|
69
50
|
Style/WordArray:
|
70
51
|
EnforcedStyle: brackets
|
data/.travis.yml
CHANGED
@@ -6,10 +6,9 @@ sudo: false
|
|
6
6
|
language: ruby
|
7
7
|
cache: bundler
|
8
8
|
rvm:
|
9
|
-
- 2.3
|
10
|
-
- 2.4
|
11
9
|
- 2.5
|
12
10
|
- 2.6
|
11
|
+
- 2.7
|
13
12
|
before_install: gem install bundler -v 1.16.5
|
14
13
|
before_script:
|
15
14
|
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
### 0.4.0 (2021-01-06)
|
2
|
+
* Add Varanus::DCV
|
3
|
+
|
4
|
+
### 0.3.1 (2020-10-14)
|
5
|
+
* Fix issue when Sectigo reports two identical 'Short Life' certs
|
6
|
+
|
7
|
+
### 0.3.0 (2020-08-24)
|
8
|
+
* Add support for new 'Short Life' certs
|
9
|
+
|
10
|
+
### 0.2.1 (2018-11-13)
|
11
|
+
* Increase timeout value for SSL requests
|
12
|
+
|
13
|
+
### 0.2.0 (2018-11-09)
|
14
|
+
* Added Varanus::SSL::CSR.generate
|
15
|
+
* Added Reports
|
16
|
+
* Varanus::Reports#ssl - list of SSL/TLS certs
|
17
|
+
* Varanus::Reports#domains - list of domains validated with DCV
|
18
|
+
|
19
|
+
### 0.1.0 (2018-11-07)
|
20
|
+
* Initial release
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,61 +1,93 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
varanus (0.
|
4
|
+
varanus (0.4.0)
|
5
5
|
faraday
|
6
6
|
faraday_middleware
|
7
|
+
savon (~> 2.0)
|
7
8
|
|
8
9
|
GEM
|
9
10
|
remote: https://rubygems.org/
|
10
11
|
specs:
|
11
|
-
addressable (2.
|
12
|
-
public_suffix (>= 2.0.2, <
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
addressable (2.7.0)
|
13
|
+
public_suffix (>= 2.0.2, < 5.0)
|
14
|
+
akami (1.3.1)
|
15
|
+
gyoku (>= 0.4.0)
|
16
|
+
nokogiri
|
17
|
+
ast (2.4.1)
|
18
|
+
builder (3.2.4)
|
19
|
+
crack (0.4.5)
|
20
|
+
rexml
|
21
|
+
docile (1.3.4)
|
22
|
+
faraday (1.3.0)
|
23
|
+
faraday-net_http (~> 1.0)
|
18
24
|
multipart-post (>= 1.2, < 3)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
25
|
+
ruby2_keywords
|
26
|
+
faraday-net_http (1.0.0)
|
27
|
+
faraday_middleware (1.0.0)
|
28
|
+
faraday (~> 1.0)
|
29
|
+
gyoku (1.3.1)
|
30
|
+
builder (>= 2.1.2)
|
31
|
+
hashdiff (1.0.1)
|
32
|
+
httpi (2.4.5)
|
33
|
+
rack
|
34
|
+
socksify
|
35
|
+
minitest (5.14.3)
|
26
36
|
minitest-rg (5.2.0)
|
27
37
|
minitest (~> 5.0)
|
28
|
-
mocha (1.
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
38
|
+
mocha (1.12.0)
|
39
|
+
multipart-post (2.1.1)
|
40
|
+
nokogiri (1.11.1-x86_64-linux)
|
41
|
+
racc (~> 1.4)
|
42
|
+
nori (2.6.0)
|
43
|
+
parallel (1.20.1)
|
44
|
+
parser (3.0.0.0)
|
45
|
+
ast (~> 2.4.1)
|
46
|
+
public_suffix (4.0.6)
|
47
|
+
racc (1.5.2)
|
48
|
+
rack (2.2.3)
|
36
49
|
rainbow (3.0.0)
|
37
50
|
rake (10.5.0)
|
38
|
-
|
39
|
-
|
51
|
+
regexp_parser (2.0.3)
|
52
|
+
rexml (3.2.4)
|
53
|
+
rubocop (1.7.0)
|
40
54
|
parallel (~> 1.10)
|
41
|
-
parser (>= 2.
|
42
|
-
powerpack (~> 0.1)
|
55
|
+
parser (>= 2.7.1.5)
|
43
56
|
rainbow (>= 2.2.2, < 4.0)
|
57
|
+
regexp_parser (>= 1.8, < 3.0)
|
58
|
+
rexml
|
59
|
+
rubocop-ast (>= 1.2.0, < 2.0)
|
44
60
|
ruby-progressbar (~> 1.7)
|
45
|
-
unicode-display_width (
|
46
|
-
|
47
|
-
|
48
|
-
|
61
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
62
|
+
rubocop-ast (1.4.0)
|
63
|
+
parser (>= 2.7.1.5)
|
64
|
+
ruby-progressbar (1.11.0)
|
65
|
+
ruby2_keywords (0.0.2)
|
66
|
+
savon (2.12.1)
|
67
|
+
akami (~> 1.2)
|
68
|
+
builder (>= 2.1.2)
|
69
|
+
gyoku (~> 1.2)
|
70
|
+
httpi (~> 2.3)
|
71
|
+
nokogiri (>= 1.8.1)
|
72
|
+
nori (~> 2.4)
|
73
|
+
wasabi (~> 3.4)
|
74
|
+
simplecov (0.21.1)
|
49
75
|
docile (~> 1.1)
|
50
|
-
|
51
|
-
|
52
|
-
simplecov-html (0.
|
53
|
-
|
54
|
-
|
76
|
+
simplecov-html (~> 0.11)
|
77
|
+
simplecov_json_formatter (~> 0.1)
|
78
|
+
simplecov-html (0.12.3)
|
79
|
+
simplecov_json_formatter (0.1.2)
|
80
|
+
socksify (1.7.1)
|
81
|
+
unicode-display_width (1.7.0)
|
82
|
+
wasabi (3.6.1)
|
83
|
+
addressable
|
84
|
+
httpi (~> 2.0)
|
85
|
+
nokogiri (>= 1.4.2)
|
86
|
+
webmock (3.11.0)
|
55
87
|
addressable (>= 2.3.6)
|
56
88
|
crack (>= 0.3.2)
|
57
|
-
hashdiff
|
58
|
-
yard (0.9.
|
89
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
90
|
+
yard (0.9.26)
|
59
91
|
|
60
92
|
PLATFORMS
|
61
93
|
ruby
|
@@ -73,4 +105,4 @@ DEPENDENCIES
|
|
73
105
|
yard
|
74
106
|
|
75
107
|
BUNDLED WITH
|
76
|
-
1.17.
|
108
|
+
1.17.3
|
data/README.md
CHANGED
@@ -7,24 +7,29 @@ Support for Sectigo's other APIs (S/MIME, code signing, device certificates, etc
|
|
7
7
|
be added at a later date. Merge requests to add some of this functionality would be
|
8
8
|
greatly appreciated.
|
9
9
|
|
10
|
-
|
10
|
+
[![Build Status](https://travis-ci.org/duke-automation/varanus.svg?branch=master)](https://travis-ci.org/duke-automation/varanus)
|
11
|
+
[![Gem Version](https://badge.fury.io/rb/varanus.svg)](http://badge.fury.io/rb/varanus)
|
12
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/593ef1aa2ba757b5374f/maintainability)](https://codeclimate.com/github/duke-automation/varanus/maintainability)
|
13
|
+
[![Test Coverage](https://api.codeclimate.com/v1/badges/593ef1aa2ba757b5374f/test_coverage)](https://codeclimate.com/github/duke-automation/varanus/test_coverage)
|
11
14
|
|
12
|
-
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
#### Generate and sign SSL cert
|
13
18
|
|
14
19
|
```ruby
|
15
|
-
|
20
|
+
key, csr = Varanus::SSL::CSR.generate(['example.com'])
|
21
|
+
varanus = Varanus.new(customer_uri, username, password)
|
22
|
+
id = varanus.ssl.sign csr, org_id
|
23
|
+
begin
|
24
|
+
cert = varanus.ssl.collect id
|
25
|
+
rescue Varanus::Error::StillProcessing
|
26
|
+
sleep 1
|
27
|
+
retry
|
28
|
+
end
|
29
|
+
puts key
|
30
|
+
puts cert
|
16
31
|
```
|
17
32
|
|
18
|
-
And then execute:
|
19
|
-
|
20
|
-
$ bundle
|
21
|
-
|
22
|
-
Or install it yourself as:
|
23
|
-
|
24
|
-
$ gem install varanus
|
25
|
-
|
26
|
-
## Usage
|
27
|
-
|
28
33
|
#### Sign SSL cert from CSR
|
29
34
|
|
30
35
|
```ruby
|
@@ -46,6 +51,18 @@ puts cert
|
|
46
51
|
Varanus.new(customer_uri, username, password).ssl.revoke(id)
|
47
52
|
```
|
48
53
|
|
54
|
+
#### Reports
|
55
|
+
|
56
|
+
Report on all SSL certs
|
57
|
+
```ruby
|
58
|
+
pp Varanus.new(customer_uri, usernams, password).reports.ssl
|
59
|
+
```
|
60
|
+
|
61
|
+
Report on all domains (DCV status)
|
62
|
+
```ruby
|
63
|
+
pp Varanus.new(customer_uri, usernams, password).reports.domains
|
64
|
+
```
|
65
|
+
|
49
66
|
#### Authentication
|
50
67
|
|
51
68
|
Authentication requires the same credentials you use to login to cert-manager.com as well as the ```customer_uri```. If your URL to log into cert-manager.com is https://cert-manager.com/customer/MyCompany then your ```customer_uri``` will be ```'MyCompany'```
|
@@ -56,6 +73,22 @@ Signing a cert requires specifying an ```org_id```. Each department in cert-man
|
|
56
73
|
|
57
74
|
To find the ```org_id```, log into cert-manager.com, go to **Settings** -> **Departments**, then click to edit the department you are interested in. The value you want is in the **OrgID** field.
|
58
75
|
|
76
|
+
## Installation
|
77
|
+
|
78
|
+
Add this line to your application's Gemfile:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
gem 'varanus'
|
82
|
+
```
|
83
|
+
|
84
|
+
And then execute:
|
85
|
+
|
86
|
+
$ bundle
|
87
|
+
|
88
|
+
Or install it yourself as:
|
89
|
+
|
90
|
+
$ gem install varanus
|
91
|
+
|
59
92
|
## Development
|
60
93
|
|
61
94
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
data/docker-compose.yml
CHANGED
data/lib/varanus.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Interface for Sectigo's (formerly Comodo CA) API.
|
2
4
|
class Varanus
|
3
5
|
attr_reader :customer_uri, :username, :password
|
@@ -12,6 +14,33 @@ class Varanus
|
|
12
14
|
@password = password
|
13
15
|
end
|
14
16
|
|
17
|
+
# :nodoc:
|
18
|
+
def connection
|
19
|
+
@connection ||= Faraday.new(url: 'https://cert-manager.com/api',
|
20
|
+
request: { timeout: 300 }) do |conn|
|
21
|
+
conn.request :json
|
22
|
+
conn.response :json, content_type: /\bjson$/
|
23
|
+
|
24
|
+
conn.headers['login'] = @username
|
25
|
+
conn.headers['password'] = @password
|
26
|
+
conn.headers['customerUri'] = @customer_uri
|
27
|
+
|
28
|
+
conn.adapter Faraday.default_adapter
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Retrive DCV instance
|
33
|
+
# @return [Varanus::DCV]
|
34
|
+
def dcv
|
35
|
+
@dcv ||= DCV.new(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Retrieve Reports instance
|
39
|
+
# @return [Varanus::Reports]
|
40
|
+
def reports
|
41
|
+
@reports ||= Reports.new(self)
|
42
|
+
end
|
43
|
+
|
15
44
|
# Retrive SSL instance
|
16
45
|
# @return [Varanus::SSL]
|
17
46
|
def ssl
|
@@ -23,9 +52,13 @@ end
|
|
23
52
|
require 'faraday'
|
24
53
|
require 'faraday_middleware'
|
25
54
|
require 'openssl'
|
55
|
+
require 'savon'
|
26
56
|
|
27
57
|
# Require other files in this gem
|
28
58
|
require 'varanus/error'
|
59
|
+
require 'varanus/rest_resource'
|
60
|
+
require 'varanus/dcv'
|
61
|
+
require 'varanus/reports'
|
29
62
|
require 'varanus/ssl'
|
30
63
|
require 'varanus/ssl/csr'
|
31
64
|
require 'varanus/version'
|
data/lib/varanus/dcv.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# An connection to the DCV API. This should not be initialized directly. Instead,
|
4
|
+
# use Varanus#dcv
|
5
|
+
class Varanus::DCV < Varanus::RestResource
|
6
|
+
# Returns an Array of DCV information about searched for domains.
|
7
|
+
# This method will automatically page through all results
|
8
|
+
# @param opts [Hash] - all opts are optional
|
9
|
+
# @option opts [String] :domain Domain to search for
|
10
|
+
# @option opts [Integer] :org ID of organization
|
11
|
+
# @option opts [Integer] :department ID of department
|
12
|
+
# @option opts [String] :dcvStatus
|
13
|
+
# @option opts [String] :orderStatus
|
14
|
+
# @option opts [Integer] :expiresIn Expires in (days)
|
15
|
+
#
|
16
|
+
# Results will included an extra 'expiration_date_obj' if 'expirationDate' is in the
|
17
|
+
# response
|
18
|
+
def search opts = {}
|
19
|
+
get_with_size_and_position('dcv/v2/validation', opts).map(&method(:_format_status))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Start domain validation process. This must be called before #submit is called
|
23
|
+
# @option domain [String] domain to validate
|
24
|
+
# @option type [String] Type of validation. Must be one of 'http', 'https', 'cname',
|
25
|
+
# or 'email'
|
26
|
+
def start domain, type
|
27
|
+
post("dcv/v1/validation/start/domain/#{type}", domain: domain)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Retrieve DCV status for a single domain
|
31
|
+
# Result will included an extra 'expiration_date_obj' if 'expirationDate' is in the
|
32
|
+
# response
|
33
|
+
def status domain
|
34
|
+
_format_status(post('dcv/v2/validation/status', domain: domain))
|
35
|
+
end
|
36
|
+
|
37
|
+
# Submit domain validation for verficiation. This must be called after #start
|
38
|
+
# @option domain [String] domain to validate
|
39
|
+
# @option type [String] Type of validation. Must be one of 'http', 'https', 'cname',
|
40
|
+
# or 'email'
|
41
|
+
# @option email_address [String] This is required of +type+ is 'email'. Otherwise, it is
|
42
|
+
# ignored.
|
43
|
+
def submit domain, type, email_address = nil
|
44
|
+
if type.to_s == 'email'
|
45
|
+
raise ArgumentError, 'email_address must be specified' if email_address.nil?
|
46
|
+
|
47
|
+
post('dcv/v1/validation/submit/domain/email', domain: domain,
|
48
|
+
email: email_address)
|
49
|
+
else
|
50
|
+
post("dcv/v1/validation/submit/domain/#{type}", domain: domain)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def _format_status status
|
57
|
+
return status unless status['expirationDate']
|
58
|
+
|
59
|
+
status.merge('expiration_date_obj' =>
|
60
|
+
Date.strptime(status['expirationDate'], '%Y-%m-%d'))
|
61
|
+
end
|
62
|
+
end
|
data/lib/varanus/error.rb
CHANGED
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# An connection to the Reports API. This should not be initialized directly. Instead,
|
4
|
+
# use Varanus#reports
|
5
|
+
class Varanus::Reports
|
6
|
+
SSL_CERT_STATUSES = {
|
7
|
+
any: 0,
|
8
|
+
requested: 1,
|
9
|
+
downloaded: 2,
|
10
|
+
revoked: 3,
|
11
|
+
expired: 4,
|
12
|
+
pending_download: 5,
|
13
|
+
not_enrolled: 6
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
# @note Do not call this directly. Use {Varanus#reports} to initialize
|
17
|
+
def initialize varanus
|
18
|
+
@varanus = varanus
|
19
|
+
end
|
20
|
+
|
21
|
+
def domains
|
22
|
+
r = soap_call :get_domain_report, {}
|
23
|
+
format_results r[:report_row_domains]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return report on SSL request
|
27
|
+
# @param [opts] [Hash]
|
28
|
+
# @option opts [String, Array] :orgs Name(s) of organizations (departments) to limit
|
29
|
+
# the report to. If this is unset, results from all departments are returned.
|
30
|
+
# @option opts [Symbol] :status (:any) One of :any, :requested, :downloaded, :revoked,
|
31
|
+
# :expired, :pending_download, :not_enrolled. :downloaded and :pending_download
|
32
|
+
# mean the cert has been enrolled/signed.
|
33
|
+
# @return [Array<Hash>]
|
34
|
+
def ssl opts = {}
|
35
|
+
msg = { organizationNames: nil, certificateStatus: 0 }
|
36
|
+
|
37
|
+
msg[:organizationNames] = Array(opts[:orgs]).join(',') if opts.include? :orgs
|
38
|
+
if opts.include? :status
|
39
|
+
msg[:certificateStatus] = SSL_CERT_STATUSES[opts[:status]]
|
40
|
+
raise ArgumentError, 'Invalid status' if msg[:certificateStatus].nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
r = soap_call :get_SSL_report, msg
|
44
|
+
format_results r[:reports]
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def format_results results
|
50
|
+
if results.is_a? Hash
|
51
|
+
[results]
|
52
|
+
else
|
53
|
+
results.to_a
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def savon
|
58
|
+
@savon ||= Savon.client(
|
59
|
+
namespace: 'http://report.ws.epki.comodo.com/',
|
60
|
+
endpoint: 'https://cert-manager.com:443/ws/ReportService',
|
61
|
+
log: false
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def soap_call func, opts = {}
|
66
|
+
msg = opts.dup
|
67
|
+
msg[:authData] = { customerLoginUri: @varanus.customer_uri, login: @varanus.username,
|
68
|
+
password: @varanus.password }
|
69
|
+
|
70
|
+
result = savon.call func, message: msg
|
71
|
+
result.body[(func.to_s.downcase + '_response').to_sym][:return]
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# An abstract class for rest resources
|
4
|
+
# Rest resources should not be initialized directly. They should be created by methods
|
5
|
+
# on Varanus
|
6
|
+
class Varanus::RestResource
|
7
|
+
# :nodoc:
|
8
|
+
def initialize varanus
|
9
|
+
@varanus = varanus
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def check_result result
|
15
|
+
body = result.body
|
16
|
+
return unless body.is_a?(Hash)
|
17
|
+
return if body['code'].nil?
|
18
|
+
|
19
|
+
klass = Varanus::Error
|
20
|
+
if body['code'] == 0 && body['description'] =~ /process/
|
21
|
+
klass = Varanus::Error::StillProcessing
|
22
|
+
end
|
23
|
+
|
24
|
+
raise klass.new(body['code'], body['description'])
|
25
|
+
end
|
26
|
+
|
27
|
+
def get path, *args
|
28
|
+
result = @varanus.connection.get(path, *args)
|
29
|
+
check_result result
|
30
|
+
result.body
|
31
|
+
end
|
32
|
+
|
33
|
+
# Performs multiple GETs with varying positions to ensure all results are returned.
|
34
|
+
def get_with_size_and_position path, opts = {}
|
35
|
+
size = opts[:size] || 200
|
36
|
+
position = opts[:position] || 0
|
37
|
+
|
38
|
+
results = []
|
39
|
+
loop do
|
40
|
+
params = { size: size, position: position }.merge(opts)
|
41
|
+
new_results = get(path, params)
|
42
|
+
results += new_results
|
43
|
+
break if new_results.length < size
|
44
|
+
|
45
|
+
position += size
|
46
|
+
end
|
47
|
+
|
48
|
+
results
|
49
|
+
end
|
50
|
+
|
51
|
+
def post path, *args
|
52
|
+
result = @varanus.connection.post(path, *args)
|
53
|
+
check_result result
|
54
|
+
result.body
|
55
|
+
end
|
56
|
+
end
|
data/lib/varanus/ssl.rb
CHANGED
@@ -1,34 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# An connection to the SSL/TSL API. This should not be initialized directly. Instead,
|
2
4
|
# use Varanus#ssl
|
3
|
-
class Varanus::SSL
|
4
|
-
# @note Do not call this directly. Use {Varanus#ssl} to initialize
|
5
|
-
def initialize varanus
|
6
|
-
@varanus = varanus
|
7
|
-
end
|
8
|
-
|
5
|
+
class Varanus::SSL < Varanus::RestResource
|
9
6
|
# Returns the option from #certificate_types that best matches the csr.
|
10
7
|
# @param csr [Varanus::SSL::CSR]
|
11
8
|
# @return [Hash] The option from {#certificate_types} that best matches the csr
|
12
|
-
def certificate_type_from_csr csr
|
13
|
-
|
14
|
-
types
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
types.find do |ct|
|
23
|
-
ct['name'] =~ /\bSSL\b/ && ct['name'] !~ /(?:Multi.?Domain|Wildcard)/i
|
24
|
-
end
|
9
|
+
def certificate_type_from_csr csr, days = nil
|
10
|
+
types = certificate_types_standard(days)
|
11
|
+
return types.first if types.length <= 1
|
12
|
+
|
13
|
+
regexp = cert_type_regexp(csr)
|
14
|
+
typ = types.find { |ct| ct['name'] =~ regexp } if regexp
|
15
|
+
return typ unless typ.nil?
|
16
|
+
|
17
|
+
types.find do |ct|
|
18
|
+
ct['name'] =~ /\bSSL\b/ && ct['name'] !~ /(?:Multi.?Domain|Wildcard)/i
|
25
19
|
end
|
26
20
|
end
|
27
21
|
|
28
22
|
# Certificate types that can be used to sign a cert
|
29
23
|
# @return [Array<Hash>]
|
30
24
|
def certificate_types
|
31
|
-
@certificate_types ||= get('types')
|
25
|
+
@certificate_types ||= get('ssl/v1/types')
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return Array of certificate types based on standard sorting.
|
29
|
+
# @param days [Integer] if present, only include types that support the given day count
|
30
|
+
# @return [Array<Hash>]
|
31
|
+
def certificate_types_standard days = nil
|
32
|
+
types = certificate_types.reject do |ct|
|
33
|
+
ct['name'] =~ /\b(?:EV|ECC|AMT|Elite)\b/
|
34
|
+
end
|
35
|
+
types = types.select! { |t| t['terms'].include? days } unless days.nil?
|
36
|
+
|
37
|
+
types
|
32
38
|
end
|
33
39
|
|
34
40
|
# Retrieves the cert.
|
@@ -46,7 +52,7 @@ class Varanus::SSL
|
|
46
52
|
# @raise [Varanus::Error::StillProcessing] Cert is still being signed
|
47
53
|
# @return [String] Certificate
|
48
54
|
def collect id, type = 'x509'
|
49
|
-
get("collect/#{id}/#{type}")
|
55
|
+
get("ssl/v1/collect/#{id}/#{type}")
|
50
56
|
end
|
51
57
|
|
52
58
|
# Revoke an ssl cert
|
@@ -54,7 +60,7 @@ class Varanus::SSL
|
|
54
60
|
# @param reason [String] Reason for revoking. Sectigo's API will return an error if it
|
55
61
|
# is blank.
|
56
62
|
def revoke id, reason
|
57
|
-
post("revoke/#{id}", reason: reason)
|
63
|
+
post("ssl/v1/revoke/#{id}", reason: reason)
|
58
64
|
nil
|
59
65
|
end
|
60
66
|
|
@@ -73,6 +79,7 @@ class Varanus::SSL
|
|
73
79
|
# specified, lowest allowed for the cert type will be used)
|
74
80
|
# @return [Integer] Id of SSL cert.
|
75
81
|
def sign csr, org_id, opts = {}
|
82
|
+
opts[:days] ||= opts[:years] * 365 unless opts[:years].nil?
|
76
83
|
csr = Varanus::SSL::CSR.new(csr) unless csr.is_a?(Varanus::SSL::CSR)
|
77
84
|
cert_type_id = opts_to_cert_type_id opts, csr
|
78
85
|
args = {
|
@@ -85,41 +92,17 @@ class Varanus::SSL
|
|
85
92
|
comments: opts[:comments].to_s[0, 1024],
|
86
93
|
externalRequester: opts[:external_requester].to_s[0, 512]
|
87
94
|
}
|
88
|
-
post('enroll', args)['sslId']
|
95
|
+
post('ssl/v1/enroll', args)['sslId']
|
89
96
|
end
|
90
97
|
|
91
98
|
private
|
92
99
|
|
93
|
-
def
|
94
|
-
|
95
|
-
return unless body.is_a?(Hash)
|
96
|
-
return if body['code'].nil?
|
97
|
-
|
98
|
-
klass = Varanus::Error
|
99
|
-
if body['code'] == 0 && body['description'] =~ /process/
|
100
|
-
klass = Varanus::Error::StillProcessing
|
101
|
-
end
|
102
|
-
|
103
|
-
raise klass.new(body['code'], body['description'])
|
104
|
-
end
|
105
|
-
|
106
|
-
def connection
|
107
|
-
@connection ||= Faraday.new(url: 'https://cert-manager.com/api/ssl/v1') do |conn|
|
108
|
-
conn.request :json
|
109
|
-
conn.response :json, content_type: /\bjson$/
|
100
|
+
def cert_type_regexp csr
|
101
|
+
return /Wildcard.+SSL/i if csr.all_names.any? { |n| n.start_with?('*.') }
|
110
102
|
|
111
|
-
|
112
|
-
conn.headers['password'] = @varanus.password
|
113
|
-
conn.headers['customerUri'] = @varanus.customer_uri
|
103
|
+
return /Multi.?Domain.+SSL/i if csr.subject_alt_names.any?
|
114
104
|
|
115
|
-
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def get path
|
120
|
-
result = connection.get(path)
|
121
|
-
check_result result
|
122
|
-
result.body
|
105
|
+
nil
|
123
106
|
end
|
124
107
|
|
125
108
|
def opts_to_cert_type_id opts, csr
|
@@ -129,19 +112,12 @@ class Varanus::SSL
|
|
129
112
|
when String
|
130
113
|
certificate_types.find { |ct| ct['name'] == opts[:cert_type] }['id']
|
131
114
|
else
|
132
|
-
certificate_type_from_csr(csr)['id']
|
115
|
+
certificate_type_from_csr(csr, opts[:days])['id']
|
133
116
|
end
|
134
117
|
end
|
135
118
|
|
136
|
-
def post path, *args
|
137
|
-
result = connection.post(path, *args)
|
138
|
-
check_result result
|
139
|
-
result.body
|
140
|
-
end
|
141
|
-
|
142
119
|
def opts_to_term opts, cert_type_id
|
143
120
|
term = opts[:days]
|
144
|
-
term ||= opts[:years] * 365 unless opts[:years].nil?
|
145
121
|
term ||= certificate_types.find { |ct| ct['id'] == cert_type_id }['terms'].min
|
146
122
|
term
|
147
123
|
end
|
data/lib/varanus/ssl/csr.rb
CHANGED
@@ -1,23 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Wrapper class around a OpenSSL::X509::Request
|
2
4
|
# Provides helper functions to make reading information from the CSR easier
|
3
5
|
class Varanus::SSL::CSR
|
6
|
+
# Key size used when calling {.generate}
|
7
|
+
DEFAULT_KEY_SIZE = 4096
|
8
|
+
|
9
|
+
# Generate a CSR
|
10
|
+
# @param names [Array<String>] List of DNS names. The first one will be the CN
|
11
|
+
# @param key [OpenSSL::PKey::RSA, OpenSSL::PKey::DSA, nil] Secret key for the cert.
|
12
|
+
# A DSA key will be generated if +nil+ is passed in.
|
13
|
+
# @param subject [Hash] Options for the subject of the cert. By default only CN will
|
14
|
+
# be set
|
15
|
+
# @return [Array(OpenSSL::PKey::PKey, Varanus::SSL::CSR)] The private key for the cert
|
16
|
+
# and CSR
|
17
|
+
def self.generate names, key = nil, subject = {}
|
18
|
+
raise ArgumentError, 'names cannot be empty' if names.empty?
|
19
|
+
|
20
|
+
subject = subject.dup
|
21
|
+
subject['CN'] = names.first
|
22
|
+
|
23
|
+
key ||= OpenSSL::PKey::DSA.new(DEFAULT_KEY_SIZE)
|
24
|
+
|
25
|
+
request = OpenSSL::X509::Request.new
|
26
|
+
request.version = 0
|
27
|
+
request.subject = OpenSSL::X509::Name.parse subject.map { |k, v| "/#{k}=#{v}" }.join
|
28
|
+
request.add_attribute names_to_san_attribute(names)
|
29
|
+
request.public_key = key.public_key
|
30
|
+
|
31
|
+
request.sign(key, OpenSSL::Digest::SHA256.new)
|
32
|
+
|
33
|
+
[key, Varanus::SSL::CSR.new(request)]
|
34
|
+
end
|
35
|
+
|
36
|
+
# :nodoc:
|
37
|
+
# Create a Subject Alternate Names attribute from an Array of dns names
|
38
|
+
def self.names_to_san_attribute names
|
39
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
40
|
+
name_str = names.map { |n| "DNS:#{n}" }.join(', ')
|
41
|
+
ext = ef.create_extension('subjectAltName', name_str, false)
|
42
|
+
seq = OpenSSL::ASN1::Sequence([ext])
|
43
|
+
ext_req = OpenSSL::ASN1::Set([seq])
|
44
|
+
OpenSSL::X509::Attribute.new('extReq', ext_req)
|
45
|
+
end
|
46
|
+
|
4
47
|
# Common Name (CN) for cert.
|
5
48
|
# @return [String]
|
6
49
|
attr_reader :cn
|
7
50
|
|
51
|
+
# OpenSSL::X509::Request representation of CSR
|
52
|
+
# @return [OpenSSL::X509::Request]
|
53
|
+
attr_reader :request
|
54
|
+
|
8
55
|
# @param csr [String, OpenSSL::X509::Request]
|
9
56
|
def initialize csr
|
10
57
|
if csr.is_a? OpenSSL::X509::Request
|
11
|
-
@
|
58
|
+
@request = csr
|
12
59
|
@text = csr.to_s
|
13
60
|
else
|
14
61
|
@text = csr.to_s
|
15
|
-
@
|
62
|
+
@request = OpenSSL::X509::Request.new @text
|
16
63
|
end
|
17
64
|
|
18
|
-
raise 'Improperly signed CSR' unless @
|
65
|
+
raise 'Improperly signed CSR' unless @request.verify @request.public_key
|
19
66
|
|
20
|
-
cn_ref = @
|
67
|
+
cn_ref = @request.subject.to_a.find { |a| a[0] == 'CN' }
|
21
68
|
@cn = cn_ref && cn_ref[1].downcase
|
22
69
|
|
23
70
|
_parse_sans
|
@@ -35,13 +82,13 @@ class Varanus::SSL::CSR
|
|
35
82
|
# Key size for the cert
|
36
83
|
# @return [Integer]
|
37
84
|
def key_size
|
38
|
-
case @
|
85
|
+
case @request.public_key
|
39
86
|
when OpenSSL::PKey::RSA
|
40
|
-
@
|
87
|
+
@request.public_key.n.num_bytes * 8
|
41
88
|
when OpenSSL::PKey::DSA
|
42
|
-
@
|
89
|
+
@request.public_key.p.num_bytes * 8
|
43
90
|
else
|
44
|
-
raise "Unknown public key type: #{@
|
91
|
+
raise "Unknown public key type: #{@request.public_key.class}"
|
45
92
|
end
|
46
93
|
end
|
47
94
|
|
@@ -59,7 +106,7 @@ class Varanus::SSL::CSR
|
|
59
106
|
private
|
60
107
|
|
61
108
|
def _parse_sans
|
62
|
-
extensions = @
|
109
|
+
extensions = @request.attributes.select { |at| at.oid == 'extReq' }
|
63
110
|
sans_extensions = extensions.flat_map do |extension|
|
64
111
|
extension.value.value[0].value
|
65
112
|
.select { |ext| ext.first.value == 'subjectAltName' }
|
data/lib/varanus/version.rb
CHANGED
data/varanus.gemspec
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
lib = File.expand_path('lib', __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
5
|
require 'varanus/version'
|
4
6
|
|
5
|
-
# rubocop:disable Metrics/BlockLength
|
6
7
|
Gem::Specification.new do |spec|
|
7
8
|
spec.name = 'varanus'
|
8
9
|
spec.version = Varanus::VERSION
|
@@ -26,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
26
27
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
27
28
|
end
|
28
29
|
spec.require_paths = ['lib']
|
29
|
-
spec.required_ruby_version = '>= 2.
|
30
|
+
spec.required_ruby_version = '>= 2.5.0'
|
30
31
|
|
31
32
|
spec.add_development_dependency 'bundler', '~> 1.16'
|
32
33
|
spec.add_development_dependency 'minitest', '~> 5.0'
|
@@ -40,5 +41,5 @@ Gem::Specification.new do |spec|
|
|
40
41
|
|
41
42
|
spec.add_runtime_dependency 'faraday'
|
42
43
|
spec.add_runtime_dependency 'faraday_middleware'
|
44
|
+
spec.add_runtime_dependency 'savon', '~> 2.0'
|
43
45
|
end
|
44
|
-
# rubocop:enable Metrics/BlockLength
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: varanus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Dilda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -164,6 +164,20 @@ dependencies:
|
|
164
164
|
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: savon
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '2.0'
|
174
|
+
type: :runtime
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '2.0'
|
167
181
|
description: |
|
168
182
|
This gem provides an interface to Sectigo's (formerly Comodo CA) APIs for working
|
169
183
|
with SSL/TLS certificates as well as its reporting API.
|
@@ -180,6 +194,7 @@ files:
|
|
180
194
|
- ".gitignore"
|
181
195
|
- ".rubocop.yml"
|
182
196
|
- ".travis.yml"
|
197
|
+
- CHANGELOG.md
|
183
198
|
- Gemfile
|
184
199
|
- Gemfile.lock
|
185
200
|
- LICENSE.txt
|
@@ -189,7 +204,10 @@ files:
|
|
189
204
|
- bin/setup
|
190
205
|
- docker-compose.yml
|
191
206
|
- lib/varanus.rb
|
207
|
+
- lib/varanus/dcv.rb
|
192
208
|
- lib/varanus/error.rb
|
209
|
+
- lib/varanus/reports.rb
|
210
|
+
- lib/varanus/rest_resource.rb
|
193
211
|
- lib/varanus/ssl.rb
|
194
212
|
- lib/varanus/ssl/csr.rb
|
195
213
|
- lib/varanus/version.rb
|
@@ -206,15 +224,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
206
224
|
requirements:
|
207
225
|
- - ">="
|
208
226
|
- !ruby/object:Gem::Version
|
209
|
-
version: 2.
|
227
|
+
version: 2.5.0
|
210
228
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
211
229
|
requirements:
|
212
230
|
- - ">="
|
213
231
|
- !ruby/object:Gem::Version
|
214
232
|
version: '0'
|
215
233
|
requirements: []
|
216
|
-
|
217
|
-
rubygems_version: 2.7.8
|
234
|
+
rubygems_version: 3.0.3
|
218
235
|
signing_key:
|
219
236
|
specification_version: 4
|
220
237
|
summary: Interface for Sectigo's (formerly Comodo CA) API.
|