acmesmith-ns1 0.1.0
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 +7 -0
- data/.gitignore +10 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +43 -0
- data/Rakefile +6 -0
- data/acmesmith-ns1.gemspec +27 -0
- data/lib/acmesmith-ns1/version.rb +3 -0
- data/lib/acmesmith/challenge_responders/ns1.rb +173 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1a78e9feeca154a78c89dfbab1a2bdfbe44a6480d856d466d3412f0727bbb2b2
|
4
|
+
data.tar.gz: 624e8bf9b35f7d5066763f8a91b976ff270cb7675f1f7481e985597ffb11fe8b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2581375c8e69540d1ac61af348d1d11e1690c634ab7179baa3ab33424a9381b65e2541934bb55819d8e5bee1a9594c1dc5c4b6e5e2ec2a361ef7c0fe4204056b
|
7
|
+
data.tar.gz: b143b0753e39a8d56f60a8ba48225c9d279ff4abc98f9b78468a53291a9fa4811edcfdf254843fb37ddc21f6f457fda2e5f787f5c03bd7d2f6672561b6e65bbd
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2019 benkap
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# acmesmith-ns1
|
2
|
+
|
3
|
+
A plugin for [Acmesmith](https://github.com/sorah/acmesmith) and implements an automated `dns-01` challenge responder using NS1 API.
|
4
|
+
|
5
|
+
With this plugin and Acmesmith, you can automate and authorize your domain hosted on [NS1 Portal](https://my.nsone.net) and request TLS certificates for the domains against [Let's Encrypt](https://letsencrypt.org/) and other CAs supporting the ACME protocol.
|
6
|
+
|
7
|
+
For more infromation on NS1 API - [API Documentation](https://ns1.com/api)
|
8
|
+
NS1 calls are managed by `ns-1` gem see [ns-1](https://rubygems.org/gems/ns-1)
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
### Prerequisites
|
12
|
+
- You need to issue an API token from your [NS1 Portal](https://my.nsone.net).
|
13
|
+
- And attached the relevant permissions to the API token
|
14
|
+
|
15
|
+
### Installation
|
16
|
+
Install `acmesith-ns1` gem along with `acmesmith`. You can just do `gem install acmesith-ns1` or use Bundler if you want.
|
17
|
+
|
18
|
+
### Configuration
|
19
|
+
Use `ns1` challenge responder in your `acmesmith.yml`. General instructions about `acmesmith.yml` is available in the manual of [Acmesmith](https://github.com/sorah/acmesmith).
|
20
|
+
|
21
|
+
The mandatory options for the `acmesmith.yml` (Or other file specified from command line) are:
|
22
|
+
- token: `NS1 API Token`
|
23
|
+
|
24
|
+
Optional option is:
|
25
|
+
- ttl: `Integer` -> Where default TTL is 3600 if this option is omitted.
|
26
|
+
|
27
|
+
```yaml
|
28
|
+
---
|
29
|
+
directory: https://acme-v02.api.letsencrypt.org/directory
|
30
|
+
|
31
|
+
storage:
|
32
|
+
type: filesystem
|
33
|
+
path: /path/to/key/storage
|
34
|
+
|
35
|
+
challenge_responders:
|
36
|
+
- ns1:
|
37
|
+
token: "API_TOKEN" # (required)
|
38
|
+
ttl: 60 # (optional)
|
39
|
+
```
|
40
|
+
|
41
|
+
## License
|
42
|
+
|
43
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'acmesmith-ns1/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'acmesmith-ns1'
|
8
|
+
spec.version = AcmesmithNS1::VERSION
|
9
|
+
spec.authors = ['Ben Kaplan']
|
10
|
+
|
11
|
+
spec.summary = %q{acmesmith plugin implementing dns-01 using NS1 REST API}
|
12
|
+
spec.description = %q{This gem is a plugin for acmesmith and implements an automated dns-01 challenge responder using NS1 API.}
|
13
|
+
spec.homepage = 'https://github.com/benkap/acmesmith-ns1'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split(?\0).reject { |f| f.match(%r{^(test|spec|features|tmp)/}) }
|
17
|
+
spec.bindir = 'exe'
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.required_ruby_version = '>= 2.1.0'
|
22
|
+
spec.add_runtime_dependency 'acmesmith', '~> 2'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.11'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
27
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'acmesmith/challenge_responders/base'
|
2
|
+
require 'yaml'
|
3
|
+
require 'rest-client'
|
4
|
+
require 'resolv'
|
5
|
+
require 'json'
|
6
|
+
require 'nsone'
|
7
|
+
|
8
|
+
module Acmesmith
|
9
|
+
module ChallengeResponders
|
10
|
+
class Ns1 < Base
|
11
|
+
|
12
|
+
def support?(type)
|
13
|
+
type == 'dns-01'
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(config)
|
17
|
+
@config = config
|
18
|
+
@ttl = @config.has_key?(:ttl) ? @config[:ttl] : 3600
|
19
|
+
@timeout = 2
|
20
|
+
begin
|
21
|
+
token = @config.fetch(:token)
|
22
|
+
rescue
|
23
|
+
warn "ERROR :: Please verify that you add your NS1 account 'Token' config file."
|
24
|
+
exit 1
|
25
|
+
end
|
26
|
+
@ns1 = NSOne::Client.new(token)
|
27
|
+
end
|
28
|
+
|
29
|
+
def respond(domain, challenge)
|
30
|
+
@zone = find_zone(domain)
|
31
|
+
unless @zone
|
32
|
+
warn "ERROR :: Domain '#{domain}' is not configured in NS1."
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
@fqdn = canonicalize(domain, challenge)
|
36
|
+
|
37
|
+
create_rr(challenge)
|
38
|
+
wait_for_sync_by_api(challenge)
|
39
|
+
wait_for_sync_by_dns(challenge)
|
40
|
+
end
|
41
|
+
|
42
|
+
def cleanup(domain, challenge)
|
43
|
+
@zone = find_zone(domain)
|
44
|
+
unless @zone
|
45
|
+
warn "ERROR :: Domain '#{domain}' is not configured in NS1."
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
@fqdn = canonicalize(domain, challenge)
|
49
|
+
|
50
|
+
delete_rr(challenge)
|
51
|
+
wait_for_sync_by_api(challenge, false)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def get_rr(challenge)
|
57
|
+
begin
|
58
|
+
type = challenge.record_type
|
59
|
+
|
60
|
+
res = @ns1.record(@zone, @fqdn, type)
|
61
|
+
|
62
|
+
return res.has_key?("message") && res["message"] == "record not found" ? false : res
|
63
|
+
|
64
|
+
rescue => e
|
65
|
+
warn "ERROR :: Failed to get record: #{@fqdn}. error: #{e}"
|
66
|
+
exit 3
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_rr(challenge)
|
71
|
+
begin
|
72
|
+
data = {
|
73
|
+
"zone" => @zone,
|
74
|
+
"domain" => @fqdn,
|
75
|
+
"type" => challenge.record_type,
|
76
|
+
"answers" => [{"answer" => [challenge.record_content]}],
|
77
|
+
"ttl" => @ttl,
|
78
|
+
}
|
79
|
+
|
80
|
+
res = @ns1.create_record(@zone, @fqdn, challenge.record_type, data)
|
81
|
+
|
82
|
+
raise "ERROR :: Failed to create record: #{@fqdn} zone: #{@zone}. msg: #{res["message"]}" if res.has_key?("message")
|
83
|
+
|
84
|
+
res
|
85
|
+
|
86
|
+
rescue => e
|
87
|
+
warn "ERROR :: error on create -> #{e}"
|
88
|
+
exit 3
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def delete_rr(challenge)
|
93
|
+
begin
|
94
|
+
res = @ns1.delete_record(@zone, @fqdn, challenge.record_type)
|
95
|
+
|
96
|
+
raise "ERROR :: Failed to delete record: #{@fqdn} zone: #{@zone}." if res.has_key?("message")
|
97
|
+
|
98
|
+
res
|
99
|
+
rescue => e
|
100
|
+
warn "ERROR :: error on delete -> #{e}"
|
101
|
+
exit 3
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def wait_for_sync_by_api(challenge, for_create = true)
|
106
|
+
puts " * API Check :: Checking if record found using NS1 API --> record: #{@fqdn} expected value: #{challenge.record_content}"
|
107
|
+
|
108
|
+
record = get_rr(challenge)
|
109
|
+
|
110
|
+
if for_create
|
111
|
+
while !record
|
112
|
+
puts " * Record creation still in process. waiting 3 seconds."
|
113
|
+
sleep 3
|
114
|
+
record = get_rr(challenge)
|
115
|
+
end
|
116
|
+
|
117
|
+
puts " * Confirm new record creation using API!"
|
118
|
+
|
119
|
+
return true
|
120
|
+
else
|
121
|
+
while record
|
122
|
+
puts " * Record deletion still in process. waiting 3 seconds"
|
123
|
+
sleep 3
|
124
|
+
record = get_rr(challenge)
|
125
|
+
end
|
126
|
+
|
127
|
+
puts " * Confirm record deletion using API!"
|
128
|
+
|
129
|
+
return true
|
130
|
+
end # if for_create
|
131
|
+
end # def
|
132
|
+
|
133
|
+
def wait_for_sync_by_dns(challenge)
|
134
|
+
value = challenge.record_content
|
135
|
+
puts " * DNS CHeck :: Checking if record found using DNS query --> record: #{@fqdn} expected value: #{challenge.record_content}"
|
136
|
+
|
137
|
+
resolv = Resolv::DNS.new()
|
138
|
+
nameservers = resolv.getresources(@zone, Resolv::DNS::Resource::IN::NS).map {|ns| Resolv.getaddresses(ns.name.to_s).first}
|
139
|
+
Resolv::DNS.open(:nameserver => nameservers) do | dns |
|
140
|
+
dns.timeouts = @timeout
|
141
|
+
|
142
|
+
loop do
|
143
|
+
|
144
|
+
resolv_value = dns.getresources(@fqdn, Resolv::DNS::Resource::IN::TXT).map(&:data).first
|
145
|
+
|
146
|
+
if resolv_value == value
|
147
|
+
puts " * Success - Value found and it is as expected. value: #{resolv_value}"
|
148
|
+
sleep 1
|
149
|
+
break
|
150
|
+
else
|
151
|
+
puts " * Waiting - Value still does not match the expected result. current value: #{resolv_value}"
|
152
|
+
sleep 3
|
153
|
+
end
|
154
|
+
|
155
|
+
end # loop
|
156
|
+
end # Resolv::DNS
|
157
|
+
end # def
|
158
|
+
|
159
|
+
def canonicalize(domain, challenge)
|
160
|
+
"#{challenge.record_name}.#{domain}.".gsub(/\.{2,}/, '.')
|
161
|
+
end
|
162
|
+
|
163
|
+
def get_zones()
|
164
|
+
@zones ||= @ns1.zones.map {|z| z["zone"]}
|
165
|
+
end
|
166
|
+
|
167
|
+
def find_zone(domain)
|
168
|
+
get_zones.select {|z| domain[z] }.first
|
169
|
+
end
|
170
|
+
|
171
|
+
end # class
|
172
|
+
end # module
|
173
|
+
end # module
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acmesmith-ns1
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ben Kaplan
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-08-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: acmesmith
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.11'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.11'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.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.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
description: This gem is a plugin for acmesmith and implements an automated dns-01
|
70
|
+
challenge responder using NS1 API.
|
71
|
+
email:
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- acmesmith-ns1.gemspec
|
82
|
+
- lib/acmesmith-ns1/version.rb
|
83
|
+
- lib/acmesmith/challenge_responders/ns1.rb
|
84
|
+
homepage: https://github.com/benkap/acmesmith-ns1
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.1.0
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.7.6
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: acmesmith plugin implementing dns-01 using NS1 REST API
|
108
|
+
test_files: []
|