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.
@@ -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
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.vscode/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem 'rest-client'
4
+ gem 'ns-1'
5
+ gem 'json'
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.
@@ -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).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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,3 @@
1
+ module AcmesmithNS1
2
+ VERSION = '0.1.0'
3
+ 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: []