mythic-beasts 0.2.0 → 1.0.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 +4 -4
- data/CHANGELOG.md +23 -2
- data/LICENSE +1 -1
- data/README.md +59 -4
- data/lib/mythic_beasts/dns/email_auth.rb +262 -0
- data/lib/mythic_beasts/dns.rb +36 -0
- data/lib/mythic_beasts/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d494ca87fec6261eef7a30bb03ca7fdab2d34c1b589de1d5cc19caaf543dfb77
|
|
4
|
+
data.tar.gz: ed5f8c6e915764a76e71d502e7210b850d78ce672699d34b04a33610605c1bef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 583a0776da3cdff5fabd1ba06f2b3eab45ca6c458564c546323e8f077f4e42eb356304e9d43c0ce601914cd33d1b4366f46ba99cdb1e995f0ec80f348e2a1ac0
|
|
7
|
+
data.tar.gz: ac6d3815a942466222b0d536e82c7d378b1fc020a58a9892b0ba6053e2434353f62b2822b149e0cb17f69efa2802440243fac450a7074c4892cf2af1b2926757
|
data/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [0.
|
|
10
|
+
## [1.0.0] - 2026-03-12
|
|
11
|
+
|
|
12
|
+
### Added in 1.0.0
|
|
13
|
+
|
|
14
|
+
- Email authentication verification (`verify_email_auth`, `verify_spf`, `verify_dmarc`, `verify_dkim`)
|
|
15
|
+
- Validates SPF records: syntax, lookup count (RFC 7208 limit of 10), multiple record detection
|
|
16
|
+
- Validates DMARC records: version tag, policy tag, aggregate report configuration
|
|
17
|
+
- Validates DKIM records: version tag, public key presence and base64 encoding
|
|
18
|
+
- Returns structured `Result` and `CheckResult` objects for programmatic use
|
|
19
|
+
- New example scripts:
|
|
20
|
+
- `examples/list_dns.rb` - List all DNS records for a zone
|
|
21
|
+
- `examples/sync_dns.rb` - Generic DNS sync from YAML with dry run/apply
|
|
22
|
+
- `examples/sync_from_octodns.rb` - OctoDNS zone file sync with conflict detection
|
|
23
|
+
- `examples/verify_email_auth.rb` - CLI for email authentication verification
|
|
24
|
+
- README documentation for email authentication verification and DNS examples
|
|
25
|
+
|
|
26
|
+
## [0.2.0] - 2025-11-23
|
|
11
27
|
|
|
12
28
|
### Added in 0.2.0
|
|
13
29
|
|
|
@@ -19,7 +35,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
35
|
- Convenience method `create_simple` for quick proxy setup
|
|
20
36
|
- Full CRUD operations on proxy endpoints by domain/hostname/address
|
|
21
37
|
- DNS record verification after proxy configuration
|
|
22
|
-
- DNS management integration in manage-vps tool
|
|
38
|
+
- DNS management integration in `manage-vps` tool
|
|
39
|
+
- Comprehensive test coverage improvements:
|
|
40
|
+
- Added tests for VPS newer API endpoints: `servers()`, `server()`, `reboot()`, `update()`, `unprovision()`, `iso_images()`, `create_with_identifier()`
|
|
41
|
+
- Added test for Client PATCH method
|
|
42
|
+
- Added test for Proxy client initialization
|
|
43
|
+
- Test suite now contains 73 examples with 100% pass rate
|
|
23
44
|
|
|
24
45
|
### Changed in 0.2.0
|
|
25
46
|
|
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -84,6 +84,34 @@ MythicBeasts.client.dns.delete_records(
|
|
|
84
84
|
MythicBeasts.client.dns.dynamic_update('home.example.com')
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
### Email Authentication Verification
|
|
88
|
+
|
|
89
|
+
Verify SPF, DKIM, and DMARC records are correctly configured for a domain:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
dns = MythicBeasts.client.dns
|
|
93
|
+
|
|
94
|
+
# Verify all email auth records at once
|
|
95
|
+
result = dns.verify_email_auth('example.com', dkim_selectors: ['google', 'mailchimp'])
|
|
96
|
+
|
|
97
|
+
result.valid? # => true if no errors across all checks
|
|
98
|
+
result.errors # => ["No DKIM record found for selector 'mailchimp'"]
|
|
99
|
+
result.warnings # => ["DMARC record missing 'rua' tag (no aggregate reports)"]
|
|
100
|
+
|
|
101
|
+
# Access individual check results
|
|
102
|
+
result.spf.record # => "v=spf1 include:_spf.google.com ~all"
|
|
103
|
+
result.spf.details[:lookup_count] # => 1
|
|
104
|
+
result.dmarc.details[:policy] # => "reject"
|
|
105
|
+
result.dkim['google'].valid? # => true
|
|
106
|
+
|
|
107
|
+
# Or verify individual record types
|
|
108
|
+
spf = dns.verify_spf('example.com')
|
|
109
|
+
dmarc = dns.verify_dmarc('example.com')
|
|
110
|
+
dkim = dns.verify_dkim('example.com', 'google')
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
See `examples/verify_email_auth.rb` for a complete CLI script.
|
|
114
|
+
|
|
87
115
|
### VPS Management
|
|
88
116
|
|
|
89
117
|
#### Interactive CLI Tool (Recommended)
|
|
@@ -100,7 +128,12 @@ export MYTHIC_BEASTS_API_SECRET="your_secret"
|
|
|
100
128
|
mythic-beasts-provision
|
|
101
129
|
```
|
|
102
130
|
|
|
103
|
-
The
|
|
131
|
+
The gem ships two interactive CLI tools:
|
|
132
|
+
|
|
133
|
+
- `mythic-beasts-provision` - provision a new VPS with guided setup
|
|
134
|
+
- `mythic-beasts-manage-vps` - manage existing servers (DNS, proxy, controls)
|
|
135
|
+
|
|
136
|
+
The provisioning CLI guides you through selecting options by number:
|
|
104
137
|
|
|
105
138
|
- Selecting product (VPS size)
|
|
106
139
|
- Choosing datacenter/zone
|
|
@@ -210,7 +243,17 @@ Available error classes:
|
|
|
210
243
|
|
|
211
244
|
See the `examples/` directory for complete working examples:
|
|
212
245
|
|
|
246
|
+
**DNS:**
|
|
247
|
+
|
|
248
|
+
- `examples/list_dns.rb` - List all DNS records for a zone
|
|
249
|
+
- `examples/sync_dns.rb` - Sync DNS records from a YAML file (with dry run/apply)
|
|
250
|
+
- `examples/sync_from_octodns.rb` - Sync DNS from OctoDNS zone files (with conflict detection)
|
|
251
|
+
- `examples/verify_email_auth.rb` - Verify SPF, DKIM, and DMARC records
|
|
252
|
+
|
|
253
|
+
**VPS:**
|
|
254
|
+
|
|
213
255
|
- `examples/list_zones_and_types.rb` - List available zones and VPS types
|
|
256
|
+
- `examples/list_vps_options.rb` - List all VPS configuration options
|
|
214
257
|
- `examples/provision_vps.rb` - Provision a new VPS server
|
|
215
258
|
- `examples/ipv6_only_vps.rb` - Provision an IPv6-only VPS with proxy setup guide
|
|
216
259
|
|
|
@@ -219,7 +262,8 @@ Run examples with:
|
|
|
219
262
|
```bash
|
|
220
263
|
export MYTHIC_BEASTS_API_KEY=your_key
|
|
221
264
|
export MYTHIC_BEASTS_API_SECRET=your_secret
|
|
222
|
-
ruby examples/
|
|
265
|
+
ruby examples/list_dns.rb example.com
|
|
266
|
+
ruby examples/verify_email_auth.rb example.com google,mailchimp
|
|
223
267
|
```
|
|
224
268
|
|
|
225
269
|
## Development
|
|
@@ -253,7 +297,18 @@ bundle exec bundler-audit check --update # Run security audit
|
|
|
253
297
|
|
|
254
298
|
## Contributing
|
|
255
299
|
|
|
256
|
-
Bug reports and pull requests are welcome on GitHub at
|
|
300
|
+
Bug reports and pull requests are welcome on GitHub at
|
|
301
|
+
[tastybamboo/mythic-beasts](https://github.com/tastybamboo/mythic-beasts).
|
|
302
|
+
|
|
303
|
+
1. Fork the repository
|
|
304
|
+
2. Create your feature branch (`git checkout -b my-feature`)
|
|
305
|
+
3. Run the test suite (`bundle exec rspec`)
|
|
306
|
+
4. Ensure linting passes (`bundle exec standardrb`)
|
|
307
|
+
5. Commit your changes
|
|
308
|
+
6. Push to the branch and open a Pull Request
|
|
309
|
+
|
|
310
|
+
This project is intended to be a safe, welcoming space for collaboration.
|
|
311
|
+
Contributors are expected to adhere to the [code of conduct](CODE_OF_CONDUCT.md).
|
|
257
312
|
|
|
258
313
|
## Author
|
|
259
314
|
|
|
@@ -266,7 +321,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/tastyb
|
|
|
266
321
|
|
|
267
322
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
268
323
|
|
|
269
|
-
Copyright © 2025, Otaina Limited
|
|
324
|
+
Copyright © 2025-2026, James Inman, Otaina Limited
|
|
270
325
|
|
|
271
326
|
## Resources
|
|
272
327
|
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MythicBeasts
|
|
4
|
+
class DNS
|
|
5
|
+
# Validates SPF, DKIM, and DMARC DNS records for a domain
|
|
6
|
+
#
|
|
7
|
+
# Uses the Mythic Beasts DNS API to fetch TXT records and validate
|
|
8
|
+
# email authentication configuration. Returns structured results
|
|
9
|
+
# rather than printing output, so callers can format as needed.
|
|
10
|
+
#
|
|
11
|
+
# @example Verify all email auth records
|
|
12
|
+
# result = client.dns.verify_email_auth("example.com", dkim_selectors: ["google", "mailchimp"])
|
|
13
|
+
# result.valid? # => true/false
|
|
14
|
+
# result.errors # => ["No SPF record found"]
|
|
15
|
+
# result.spf.record # => "v=spf1 include:_spf.google.com ~all"
|
|
16
|
+
#
|
|
17
|
+
# @see https://www.mythic-beasts.com/support/api/dnsv2
|
|
18
|
+
class EmailAuth
|
|
19
|
+
# Aggregated result of all email authentication checks
|
|
20
|
+
Result = Struct.new(:spf, :dmarc, :dkim, :errors, :warnings, keyword_init: true) do
|
|
21
|
+
# @return [Boolean] true if no errors were found across all checks
|
|
22
|
+
def valid?
|
|
23
|
+
errors.empty?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Result of an individual check (SPF, DMARC, or DKIM)
|
|
28
|
+
CheckResult = Struct.new(:record, :valid, :errors, :warnings, :details, keyword_init: true) do
|
|
29
|
+
# @return [Boolean] true if this individual check passed
|
|
30
|
+
def valid?
|
|
31
|
+
valid
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
SPF_LOOKUP_MECHANISMS = %w[include a mx ptr exists redirect].freeze
|
|
36
|
+
VALID_DMARC_POLICIES = %w[none quarantine reject].freeze
|
|
37
|
+
|
|
38
|
+
# @param dns [MythicBeasts::DNS] the DNS client instance
|
|
39
|
+
# @param zone [String] the domain/zone to verify
|
|
40
|
+
def initialize(dns, zone)
|
|
41
|
+
@dns = dns
|
|
42
|
+
@zone = zone
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Run all email authentication checks
|
|
46
|
+
#
|
|
47
|
+
# @param dkim_selectors [Array<String>] DKIM selectors to check (e.g., ["google", "mailchimp"])
|
|
48
|
+
# @return [Result] aggregated result with per-check details
|
|
49
|
+
def verify(dkim_selectors: [])
|
|
50
|
+
spf_result = verify_spf
|
|
51
|
+
dmarc_result = verify_dmarc
|
|
52
|
+
dkim_results = dkim_selectors.map { |selector| [selector, verify_dkim(selector)] }.to_h
|
|
53
|
+
|
|
54
|
+
all_errors = spf_result.errors + dmarc_result.errors +
|
|
55
|
+
dkim_results.values.flat_map(&:errors)
|
|
56
|
+
all_warnings = spf_result.warnings + dmarc_result.warnings +
|
|
57
|
+
dkim_results.values.flat_map(&:warnings)
|
|
58
|
+
|
|
59
|
+
Result.new(
|
|
60
|
+
spf: spf_result,
|
|
61
|
+
dmarc: dmarc_result,
|
|
62
|
+
dkim: dkim_results,
|
|
63
|
+
errors: all_errors,
|
|
64
|
+
warnings: all_warnings
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Verify SPF record for the zone
|
|
69
|
+
#
|
|
70
|
+
# Fetches TXT records at the zone root and filters for v=spf1 records.
|
|
71
|
+
# Validates against RFC 7208: single record, valid syntax, <=10 DNS lookups.
|
|
72
|
+
#
|
|
73
|
+
# @return [CheckResult] with :lookup_count in details
|
|
74
|
+
def verify_spf
|
|
75
|
+
errors = []
|
|
76
|
+
warnings = []
|
|
77
|
+
|
|
78
|
+
txt_records = fetch_txt_records("@")
|
|
79
|
+
spf_records = txt_records.select { |r| r.start_with?("v=spf1") }
|
|
80
|
+
|
|
81
|
+
if spf_records.empty?
|
|
82
|
+
return CheckResult.new(record: nil, valid: false,
|
|
83
|
+
errors: ["No SPF record found"], warnings: [], details: {})
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if spf_records.length > 1
|
|
87
|
+
return CheckResult.new(record: spf_records, valid: false,
|
|
88
|
+
errors: ["Multiple SPF records found (RFC 7208 violation)"],
|
|
89
|
+
warnings: [], details: {})
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
spf = spf_records.first
|
|
93
|
+
|
|
94
|
+
# Validate syntax
|
|
95
|
+
if spf.match?(/\ball\b/) && !spf.match?(/[~\-+?]all\s*$/)
|
|
96
|
+
warnings << "SPF 'all' mechanism should be at the end"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
if spf.include?(";")
|
|
100
|
+
warnings << "SPF record contains semicolons (check if intentional)"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Count DNS lookups
|
|
104
|
+
lookup_count = count_spf_lookups(spf)
|
|
105
|
+
if lookup_count > 10
|
|
106
|
+
errors << "SPF has #{lookup_count} DNS lookups (max 10 allowed per RFC 7208)"
|
|
107
|
+
elsif lookup_count > 8
|
|
108
|
+
warnings << "SPF has #{lookup_count} DNS lookups (approaching limit of 10)"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
CheckResult.new(
|
|
112
|
+
record: spf, valid: errors.empty?, errors: errors, warnings: warnings,
|
|
113
|
+
details: {lookup_count: lookup_count}
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Verify DMARC record for the zone
|
|
118
|
+
#
|
|
119
|
+
# Fetches TXT records at _dmarc.{zone} and validates syntax,
|
|
120
|
+
# required p= tag, and recommended rua= tag.
|
|
121
|
+
#
|
|
122
|
+
# @return [CheckResult] with :policy and :tags in details
|
|
123
|
+
def verify_dmarc
|
|
124
|
+
errors = []
|
|
125
|
+
warnings = []
|
|
126
|
+
|
|
127
|
+
txt_records = fetch_txt_records("_dmarc")
|
|
128
|
+
|
|
129
|
+
if txt_records.empty?
|
|
130
|
+
return CheckResult.new(record: nil, valid: true,
|
|
131
|
+
errors: [],
|
|
132
|
+
warnings: ["No DMARC record found (recommended for email authentication)"],
|
|
133
|
+
details: {})
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
if txt_records.length > 1
|
|
137
|
+
return CheckResult.new(record: txt_records, valid: false,
|
|
138
|
+
errors: ["Multiple DMARC records found"], warnings: [], details: {})
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
dmarc = txt_records.first
|
|
142
|
+
|
|
143
|
+
unless dmarc.start_with?("v=DMARC1")
|
|
144
|
+
return CheckResult.new(record: dmarc, valid: false,
|
|
145
|
+
errors: ["DMARC record doesn't start with 'v=DMARC1'"],
|
|
146
|
+
warnings: [], details: {})
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
tags = parse_dmarc_tags(dmarc)
|
|
150
|
+
|
|
151
|
+
if tags["p"]
|
|
152
|
+
unless VALID_DMARC_POLICIES.include?(tags["p"])
|
|
153
|
+
errors << "Invalid DMARC policy: #{tags["p"]}"
|
|
154
|
+
end
|
|
155
|
+
else
|
|
156
|
+
errors << "DMARC record missing required 'p' tag"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
unless tags["rua"]
|
|
160
|
+
warnings << "DMARC record missing 'rua' tag (no aggregate reports)"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
CheckResult.new(
|
|
164
|
+
record: dmarc, valid: errors.empty?, errors: errors, warnings: warnings,
|
|
165
|
+
details: {policy: tags["p"], tags: tags}
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Verify DKIM record for a specific selector
|
|
170
|
+
#
|
|
171
|
+
# Fetches TXT records at {selector}._domainkey.{zone} and validates
|
|
172
|
+
# the presence of v=DKIM1 and a valid base64 public key.
|
|
173
|
+
#
|
|
174
|
+
# @param selector [String] the DKIM selector (e.g., "google", "mailchimp")
|
|
175
|
+
# @return [CheckResult]
|
|
176
|
+
def verify_dkim(selector)
|
|
177
|
+
errors = []
|
|
178
|
+
warnings = []
|
|
179
|
+
|
|
180
|
+
txt_records = fetch_txt_records("#{selector}._domainkey")
|
|
181
|
+
|
|
182
|
+
if txt_records.empty?
|
|
183
|
+
return CheckResult.new(record: nil, valid: false,
|
|
184
|
+
errors: ["No DKIM record found for selector '#{selector}'"],
|
|
185
|
+
warnings: [], details: {selector: selector})
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
dkim = txt_records.join
|
|
189
|
+
|
|
190
|
+
unless dkim.match?(/v=DKIM1/i)
|
|
191
|
+
return CheckResult.new(record: dkim, valid: false,
|
|
192
|
+
errors: ["DKIM record doesn't contain 'v=DKIM1'"],
|
|
193
|
+
warnings: warnings, details: {selector: selector})
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
key_match = dkim.match(/p=([A-Za-z0-9+\/=]+)/)
|
|
197
|
+
unless key_match
|
|
198
|
+
return CheckResult.new(record: dkim, valid: false,
|
|
199
|
+
errors: ["DKIM record missing or invalid public key (p= tag)"],
|
|
200
|
+
warnings: warnings, details: {selector: selector})
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Validate base64 encoding (without requiring the base64 gem)
|
|
204
|
+
key = key_match[1]
|
|
205
|
+
unless valid_base64?(key)
|
|
206
|
+
errors << "DKIM public key is not valid base64"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
CheckResult.new(
|
|
210
|
+
record: dkim, valid: errors.empty?, errors: errors, warnings: warnings,
|
|
211
|
+
details: {selector: selector, key_length: key.length}
|
|
212
|
+
)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
private
|
|
216
|
+
|
|
217
|
+
# Fetch TXT record data from the DNS API
|
|
218
|
+
#
|
|
219
|
+
# @param host [String] the hostname within the zone
|
|
220
|
+
# @return [Array<String>] array of TXT record data values
|
|
221
|
+
def fetch_txt_records(host)
|
|
222
|
+
response = @dns.get_record(@zone, host, "TXT")
|
|
223
|
+
records = response["records"] || []
|
|
224
|
+
records.map { |r| r["data"] }
|
|
225
|
+
rescue MythicBeasts::NotFoundError
|
|
226
|
+
[]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Count DNS lookup mechanisms in an SPF record
|
|
230
|
+
#
|
|
231
|
+
# @param spf [String] the SPF record text
|
|
232
|
+
# @return [Integer] number of DNS-querying mechanisms
|
|
233
|
+
def count_spf_lookups(spf)
|
|
234
|
+
SPF_LOOKUP_MECHANISMS.sum { |mechanism| spf.scan(/\b#{mechanism}[:\s]/i).length }
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Validate strict base64 encoding without requiring the base64 gem
|
|
238
|
+
#
|
|
239
|
+
# @param str [String] the string to validate
|
|
240
|
+
# @return [Boolean] true if valid base64
|
|
241
|
+
def valid_base64?(str)
|
|
242
|
+
return false if str.empty?
|
|
243
|
+
return false unless str.match?(/\A[A-Za-z0-9+\/]*={0,2}\z/)
|
|
244
|
+
str.length % 4 == 0
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Parse DMARC tag-value pairs
|
|
248
|
+
#
|
|
249
|
+
# @param dmarc [String] the DMARC record text
|
|
250
|
+
# @return [Hash<String, String>] tag => value pairs
|
|
251
|
+
def parse_dmarc_tags(dmarc)
|
|
252
|
+
tags = {}
|
|
253
|
+
dmarc.gsub(/v=DMARC1;?\s*/, "").split(";").each do |tag|
|
|
254
|
+
next if tag.strip.empty?
|
|
255
|
+
key, value = tag.split("=", 2).map(&:strip)
|
|
256
|
+
tags[key] = value if key && value
|
|
257
|
+
end
|
|
258
|
+
tags
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
data/lib/mythic_beasts/dns.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require_relative "dns/email_auth"
|
|
2
|
+
|
|
1
3
|
module MythicBeasts
|
|
2
4
|
class DNS
|
|
3
5
|
attr_reader :client
|
|
@@ -72,5 +74,39 @@ module MythicBeasts
|
|
|
72
74
|
def create_txt_record(zone, host, text, ttl: 300)
|
|
73
75
|
create_records(zone, [{host: host, ttl: ttl, type: "TXT", data: text}])
|
|
74
76
|
end
|
|
77
|
+
|
|
78
|
+
# Verify all email authentication records (SPF, DKIM, DMARC) for a zone
|
|
79
|
+
#
|
|
80
|
+
# @param zone [String] the domain to verify
|
|
81
|
+
# @param dkim_selectors [Array<String>] DKIM selectors to check
|
|
82
|
+
# @return [MythicBeasts::DNS::EmailAuth::Result]
|
|
83
|
+
def verify_email_auth(zone, dkim_selectors: [])
|
|
84
|
+
EmailAuth.new(self, zone).verify(dkim_selectors: dkim_selectors)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Verify SPF record for a zone
|
|
88
|
+
#
|
|
89
|
+
# @param zone [String] the domain to verify
|
|
90
|
+
# @return [MythicBeasts::DNS::EmailAuth::CheckResult]
|
|
91
|
+
def verify_spf(zone)
|
|
92
|
+
EmailAuth.new(self, zone).verify_spf
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Verify DMARC record for a zone
|
|
96
|
+
#
|
|
97
|
+
# @param zone [String] the domain to verify
|
|
98
|
+
# @return [MythicBeasts::DNS::EmailAuth::CheckResult]
|
|
99
|
+
def verify_dmarc(zone)
|
|
100
|
+
EmailAuth.new(self, zone).verify_dmarc
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Verify DKIM record for a specific selector
|
|
104
|
+
#
|
|
105
|
+
# @param zone [String] the domain to verify
|
|
106
|
+
# @param selector [String] the DKIM selector
|
|
107
|
+
# @return [MythicBeasts::DNS::EmailAuth::CheckResult]
|
|
108
|
+
def verify_dkim(zone, selector)
|
|
109
|
+
EmailAuth.new(self, zone).verify_dkim(selector)
|
|
110
|
+
end
|
|
75
111
|
end
|
|
76
112
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mythic-beasts
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Inman
|
|
@@ -9,6 +9,20 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: dotenv
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '3.1'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: faraday
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -164,6 +178,7 @@ files:
|
|
|
164
178
|
- lib/mythic_beasts/auth.rb
|
|
165
179
|
- lib/mythic_beasts/client.rb
|
|
166
180
|
- lib/mythic_beasts/dns.rb
|
|
181
|
+
- lib/mythic_beasts/dns/email_auth.rb
|
|
167
182
|
- lib/mythic_beasts/errors.rb
|
|
168
183
|
- lib/mythic_beasts/proxy.rb
|
|
169
184
|
- lib/mythic_beasts/version.rb
|
|
@@ -189,7 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
189
204
|
- !ruby/object:Gem::Version
|
|
190
205
|
version: '0'
|
|
191
206
|
requirements: []
|
|
192
|
-
rubygems_version:
|
|
207
|
+
rubygems_version: 4.0.7
|
|
193
208
|
specification_version: 4
|
|
194
209
|
summary: Ruby client for Mythic Beasts API
|
|
195
210
|
test_files: []
|