1
0
mirror of https://github.com/cmur2/dyndnsd.git synced 2025-08-08 08:33:56 +02:00

Compare commits

..

10 Commits

Author SHA1 Message Date
cn
25e70f484d release: 3.1.0.rc1 2020-08-18 22:26:56 +02:00
cn
617fbf538b docker: add image release on tag and periodic vulnerability scan 2020-08-18 22:22:41 +02:00
cn
5cce42f4c7 gem: fix solargraph warnings on CI 2020-08-17 12:10:35 +02:00
cn
093efc77ef gem: add editorconfig 2020-08-12 07:55:37 +02:00
depfu[bot]
2368099f7d gems: upgrade jaeger-client to version 1.0.0
Update jaeger-client to version 1.0.0 (#38)

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2020-08-11 16:08:28 +02:00
depfu[bot]
708cd13237 gems: update rubocop to version 0.89.0
Update rubocop to version 0.89.0 (#59)

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2020-08-07 08:36:49 +02:00
cn
a89a263250 docs: add example Dockerfile 2020-07-30 19:52:01 +02:00
cn
af102f23ec release: 3.0.0 2020-07-29 00:31:48 +02:00
depfu[bot]
950c985ad1 gems: update rubocop to version 0.88.0
Update rubocop to version 0.88.0 (#57)
Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
2020-07-29 00:04:44 +02:00
cn
b2d9b7745f gem: bump minimum required Ruby version to Ruby 2.5 2020-07-28 17:52:15 +02:00
19 changed files with 194 additions and 51 deletions

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

26
.github/workflows/cd.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
---
name: cd
on:
push:
tags:
- 'v*.*.*'
jobs:
release-dockerimage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Extract dyndnsd version from tag name
run: |
echo ::set-env name=DYNDNSD_VERSION::${GITHUB_REF#refs/*/v}
# https://github.com/marketplace/actions/build-and-push-docker-images
- name: Build and push Docker image for dyndnsd ${{ env.DYNDNSD_VERSION }}
uses: docker/build-push-action@v1
with:
username: cmur2
password: ${{ secrets.DOCKER_TOKEN }}
repository: cmur2/dyndnsd
path: docker
build_args: DYNDNSD_VERSION=${{ env.DYNDNSD_VERSION }}
tag_with_ref: true

42
.github/workflows/vulnscan.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
---
name: vulnscan
on:
schedule:
- cron: '7 4 * * 4' # weekly on thursday morning
jobs:
scan-released-dockerimages:
runs-on: ubuntu-latest
env:
TRIVY_LIGHT: 'true'
TRIVY_IGNORE_UNFIXED: 'true'
TRIVY_REMOVED_PKGS: 'true'
steps:
- name: Install Trivy
run: |
mkdir -p $GITHUB_WORKSPACE/bin
echo "::add-path::$GITHUB_WORKSPACE/bin"
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/master/contrib/install.sh | sh -s -- -b $GITHUB_WORKSPACE/bin
- name: Download Trivy DB
run: |
trivy image --download-db-only
- name: Scan vulnerabilities using Trivy
run: |
trivy --version
ALL_IMAGES="$(curl -s https://hub.docker.com/v2/repositories/cmur2/dyndnsd/tags?page_size=1000 | jq -r '.results[].name | "cmur2/dyndnsd:" + .' | grep -e 'cmur2/dyndnsd:v' | sort -r)"
EXIT_CODE=0
set -e
for major_version in $(seq 1 10); do
for image in $ALL_IMAGES; do
if [[ "$image" = cmur2/dyndnsd:v$major_version.* ]]; then
echo -n "\nScanning newest patch release $image of major v$major_version...\n"
if ! trivy image --skip-update --exit-code 1 "$image"; then
EXIT_CODE=1
fi
break
fi
done
done
exit "$EXIT_CODE"

View File

@@ -1,5 +1,6 @@
AllCops: AllCops:
TargetRubyVersion: '2.3' TargetRubyVersion: '2.5'
NewCops: enable
Layout/EmptyLineAfterGuardClause: Layout/EmptyLineAfterGuardClause:
Enabled: false Enabled: false

View File

@@ -5,8 +5,6 @@ rvm:
- 2.7 - 2.7
- 2.6 - 2.6
- 2.5 - 2.5
- 2.4
- 2.3
script: script:
- bundle exec rake travis - bundle exec rake travis

View File

@@ -1,5 +1,17 @@
# Changelog # Changelog
## 3.1.0
IMPROVEMENTS:
- Add officially maintained [Docker image for dyndnsd](https://hub.docker.com/r/cmur2/dyndnsd)
## 3.0.0 (July 29, 2020)
IMPROVEMENTS:
- Drop EOL Ruby 2.4 and lower support, now minimum version supported is Ruby 2.5
## 2.3.1 (July 27, 2020) ## 2.3.1 (July 27, 2020)
IMPROVEMENTS: IMPROVEMENTS:

View File

@@ -64,7 +64,42 @@ users:
Run dyndnsd.rb by: Run dyndnsd.rb by:
dyndnsd /path/to/config.yaml ```bash
dyndnsd /path/to/config.yml
```
### Docker image
There is an officially maintained [Docker image for dyndnsd](https://hub.docker.com/r/cmur2/dyndnsd) available at Dockerhub. The goal is to have a minimal secured image available (currently based on Alpine) that works well for the `zone_transfer_server` updater use case.
Users can make extensions by deriving from the official Docker image or building their own.
The Docker image consumes the same configuration file in YAML format as the gem, inside the container it needs to be mounted/available as `/etc/dyndnsd/config.yml`. the following YAML should be used as a base and extended with user's settings:
```yaml
host: "0.0.0.0"
port: 8080
# omit the logfile: option so logging to STDOUT will happen automatically
db: "/var/lib/db.json"
# User's settings for updater and permissions follow here!
```
more ports might be needed depending on if DNS zone transfer is needed
Run the Docker image exposing the DynDNS-API on host port 8080 via:
```bash
docker run -d --name dyndnsd \
-p 8080:8080 \
-v /host/path/to/dyndnsd/config.yml:/etc/dyndnsd/config.yml \
-v /host/path/to/dyndnsd/db.json:/var/lib/db.json \
cmur2/dyndnsd:vX.Y.Z
```
*Note*: You may need to expose more then just port 8080 e.g. if you use the `zone_transfer_server` which can be done by appending additional `-p 5353:5353` flags to the `docker run` command.
## Using dyndnsd.rb with any nameserver via DNS zone transfers (AXFR) ## Using dyndnsd.rb with any nameserver via DNS zone transfers (AXFR)
@@ -187,9 +222,11 @@ If you want to provide an additional IPv6 address as myip6 parameter, the myip p
Use a webserver as a proxy to handle SSL and/or multiple listen addresses and ports. DynDNS.com provides HTTP on port 80 and 8245 and HTTPS on port 443. Use a webserver as a proxy to handle SSL and/or multiple listen addresses and ports. DynDNS.com provides HTTP on port 80 and 8245 and HTTPS on port 443.
### Init scripts ### Startup
The [Debian 6 init.d script](init.d/debian-6-dyndnsd) assumes that dyndnsd.rb is installed into the system ruby (no RVM support) and the config.yaml is at /opt/dyndnsd/config.yaml. Modify to your needs. There is a [Dockerfile](docs/Dockerfile) that can be used to build a Docker image for running dyndnsd.rb.
The [Debian 6 init.d script](docs/debian-6-init-dyndnsd) assumes that dyndnsd.rb is installed into the system ruby (no RVM support) and the config.yaml is at /opt/dyndnsd/config.yaml. Modify to your needs.
### Monitoring ### Monitoring

View File

@@ -21,4 +21,4 @@ end
task default: [:rubocop, :spec, 'bundle:audit'] task default: [:rubocop, :spec, 'bundle:audit']
task travis: [:default, :'solargraph:tc'] task travis: [:default, :'solargraph:init', :'solargraph:tc']

15
docker/Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM alpine:3.12
EXPOSE 5353 8080
ARG DYNDNSD_VERSION=3.0.0
RUN apk --no-cache add openssl ca-certificates && \
apk --no-cache add ruby ruby-etc ruby-io-console ruby-json ruby-webrick && \
apk --no-cache add --virtual .build-deps ruby-dev build-base tzdata && \
gem install --no-document dyndnsd -v ${DYNDNSD_VERSION} && \
# set timezone to Berlin
cp /usr/share/zoneinfo/Europe/Berlin /etc/localtime && \
apk del .build-deps
ENTRYPOINT ["dyndnsd", "/etc/dyndnsd/config.yml"]

0
init.d/debian-6-dyndnsd → docs/debian-6-init-dyndnsd Normal file → Executable file
View File

View File

@@ -25,10 +25,10 @@ Gem::Specification.new do |s|
s.executables = ['dyndnsd'] s.executables = ['dyndnsd']
s.extra_rdoc_files = Dir['README.md', 'CHANGELOG.md', 'LICENSE'] s.extra_rdoc_files = Dir['README.md', 'CHANGELOG.md', 'LICENSE']
s.required_ruby_version = '>= 2.3' s.required_ruby_version = '>= 2.5'
s.add_runtime_dependency 'async-dns', '~> 1.2.0' s.add_runtime_dependency 'async-dns', '~> 1.2.0'
s.add_runtime_dependency 'jaeger-client', '~> 0.10.0' s.add_runtime_dependency 'jaeger-client', '~> 1.0.0'
s.add_runtime_dependency 'metriks' s.add_runtime_dependency 'metriks'
s.add_runtime_dependency 'opentracing', '~> 0.5.0' s.add_runtime_dependency 'opentracing', '~> 0.5.0'
s.add_runtime_dependency 'rack', '~> 2.0' s.add_runtime_dependency 'rack', '~> 2.0'
@@ -39,6 +39,6 @@ Gem::Specification.new do |s|
s.add_development_dependency 'rack-test' s.add_development_dependency 'rack-test'
s.add_development_dependency 'rake' s.add_development_dependency 'rake'
s.add_development_dependency 'rspec' s.add_development_dependency 'rspec'
s.add_development_dependency 'rubocop', '~> 0.81.0' s.add_development_dependency 'rubocop', '~> 0.89.0'
s.add_development_dependency 'solargraph' s.add_development_dependency 'solargraph'
end end

View File

@@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'date'
require 'etc' require 'etc'
require 'logger' require 'logger'
require 'ipaddr' require 'ipaddr'
@@ -80,7 +81,7 @@ module Dyndnsd
end end
# @param env [Hash{String => String}] # @param env [Hash{String => String}]
# @return [Array{Integer,Hash{String => String},Array{String}}] # @return [Array{Integer,Hash{String => String},Array<String>}]
def call(env) def call(env)
return [422, {'X-DynDNS-Response' => 'method_forbidden'}, []] if env['REQUEST_METHOD'] != 'GET' return [422, {'X-DynDNS-Response' => 'method_forbidden'}, []] if env['REQUEST_METHOD'] != 'GET'
return [422, {'X-DynDNS-Response' => 'not_found'}, []] if env['PATH_INFO'] != '/nic/update' return [422, {'X-DynDNS-Response' => 'not_found'}, []] if env['PATH_INFO'] != '/nic/update'
@@ -134,7 +135,7 @@ module Dyndnsd
private private
# @param params [Hash{String => String}] # @param params [Hash{String => String}]
# @return [Array{String}] # @return [Array<String>]
def extract_v4_and_v6_address(params) def extract_v4_and_v6_address(params)
return [] if !(params['myip']) return [] if !(params['myip'])
begin begin
@@ -148,7 +149,7 @@ module Dyndnsd
# @param env [Hash{String => String}] # @param env [Hash{String => String}]
# @param params [Hash{String => String}] # @param params [Hash{String => String}]
# @return [Array{String}] # @return [Array<String>]
def extract_myips(env, params) def extract_myips(env, params)
# require presence of myip parameter as valid IPAddr (v4) and valid myip6 # require presence of myip parameter as valid IPAddr (v4) and valid myip6
return extract_v4_and_v6_address(params) if params.key?('myip6') return extract_v4_and_v6_address(params) if params.key?('myip6')
@@ -164,8 +165,8 @@ module Dyndnsd
end end
# @param hostnames [String] # @param hostnames [String]
# @param myips [Array{String}] # @param myips [Array<String>]
# @return [Array{Symbol}] # @return [Array<Symbol>]
def process_changes(hostnames, myips) def process_changes(hostnames, myips)
changes = [] changes = []
Helper.span('process_changes') do |span| Helper.span('process_changes') do |span|
@@ -200,7 +201,7 @@ module Dyndnsd
end end
# @param env [Hash{String => String}] # @param env [Hash{String => String}]
# @return [Array{Integer,Hash{String => String},Array{String}}] # @return [Array{Integer,Hash{String => String},Array<String>}]
def handle_dyndns_request(env) def handle_dyndns_request(env)
params = Rack::Utils.parse_query(env['QUERY_STRING']) params = Rack::Utils.parse_query(env['QUERY_STRING'])
@@ -245,7 +246,7 @@ module Dyndnsd
if config['logfile'] if config['logfile']
Dyndnsd.logger = Logger.new(config['logfile']) Dyndnsd.logger = Logger.new(config['logfile'])
else else
Dyndnsd.logger = Logger.new(STDOUT) Dyndnsd.logger = Logger.new($stdout)
end end
Dyndnsd.logger.progname = 'dyndnsd' Dyndnsd.logger.progname = 'dyndnsd'

View File

@@ -27,7 +27,7 @@ module Dyndnsd
ips.each do |ip| ips.each do |ip|
ip = IPAddr.new(ip).native ip = IPAddr.new(ip).native
type = ip.ipv6? ? 'AAAA' : 'A' type = ip.ipv6? ? 'AAAA' : 'A'
name = hostname.chomp('.' + @domain) name = hostname.chomp(".#{@domain}")
out << "#{name} IN #{type} #{ip}" out << "#{name} IN #{type} #{ip}"
end end
end end

View File

@@ -9,7 +9,7 @@ module Dyndnsd
end end
# @param env [Hash{String => String}] # @param env [Hash{String => String}]
# @return [Array{Integer,Hash{String => String},Array{String}}] # @return [Array{Integer,Hash{String => String},Array<String>}]
def call(env) def call(env)
@app.call(env).tap do |status_code, headers, body| @app.call(env).tap do |status_code, headers, body|
if headers.key?('X-DynDNS-Response') if headers.key?('X-DynDNS-Response')
@@ -24,30 +24,32 @@ module Dyndnsd
# @param status_code [Integer] # @param status_code [Integer]
# @param headers [Hash{String => String}] # @param headers [Hash{String => String}]
# @param body [Array{String}] # @param body [Array<String>]
# @return [Array{Integer,Hash{String => String},Array{String}}] # @return [Array{Integer,Hash{String => String},Array<String>}]
def decorate_dyndnsd_response(status_code, headers, body) def decorate_dyndnsd_response(status_code, headers, body)
if status_code == 200 case status_code
when 200
[200, {'Content-Type' => 'text/plain'}, [get_success_body(body[0], body[1])]] [200, {'Content-Type' => 'text/plain'}, [get_success_body(body[0], body[1])]]
elsif status_code == 422 when 422
error_response_map[headers['X-DynDNS-Response']] error_response_map[headers['X-DynDNS-Response']]
end end
end end
# @param status_code [Integer] # @param status_code [Integer]
# @param headers [Hash{String => String}] # @param headers [Hash{String => String}]
# @param _body [Array{String}] # @param _body [Array<String>]
# @return [Array{Integer,Hash{String => String},Array{String}}] # @return [Array{Integer,Hash{String => String},Array<String>}]
def decorate_other_response(status_code, headers, _body) def decorate_other_response(status_code, headers, _body)
if status_code == 400 case status_code
when 400
[status_code, headers, ['Bad Request']] [status_code, headers, ['Bad Request']]
elsif status_code == 401 when 401
[status_code, headers, ['badauth']] [status_code, headers, ['badauth']]
end end
end end
# @param changes [Array{Symbol}] # @param changes [Array<Symbol>]
# @param myips [Array{String}] # @param myips [Array<String>]
# @return [String] # @return [String]
def get_success_body(changes, myips) def get_success_body(changes, myips)
changes.map { |change| "#{change} #{myips.join(' ')}" }.join("\n") changes.map { |change| "#{change} #{myips.join(' ')}" }.join("\n")

View File

@@ -9,7 +9,7 @@ module Dyndnsd
end end
# @param env [Hash{String => String}] # @param env [Hash{String => String}]
# @return [Array{Integer,Hash{String => String},Array{String}}] # @return [Array{Integer,Hash{String => String},Array<String>}]
def call(env) def call(env)
@app.call(env).tap do |status_code, headers, body| @app.call(env).tap do |status_code, headers, body|
if headers.key?('X-DynDNS-Response') if headers.key?('X-DynDNS-Response')
@@ -24,30 +24,32 @@ module Dyndnsd
# @param status_code [Integer] # @param status_code [Integer]
# @param headers [Hash{String => String}] # @param headers [Hash{String => String}]
# @param body [Array{String}] # @param body [Array<String>]
# @return [Array{Integer,Hash{String => String},Array{String}}] # @return [Array{Integer,Hash{String => String},Array<String>}]
def decorate_dyndnsd_response(status_code, headers, body) def decorate_dyndnsd_response(status_code, headers, body)
if status_code == 200 case status_code
when 200
[200, {'Content-Type' => 'text/plain'}, [get_success_body(body[0], body[1])]] [200, {'Content-Type' => 'text/plain'}, [get_success_body(body[0], body[1])]]
elsif status_code == 422 when 422
error_response_map[headers['X-DynDNS-Response']] error_response_map[headers['X-DynDNS-Response']]
end end
end end
# @param status_code [Integer] # @param status_code [Integer]
# @param headers [Hash{String => String}] # @param headers [Hash{String => String}]
# @param _body [Array{String}] # @param _body [Array<String>]
# @return [Array{Integer,Hash{String => String},Array{String}}] # @return [Array{Integer,Hash{String => String},Array<String>}]
def decorate_other_response(status_code, headers, _body) def decorate_other_response(status_code, headers, _body)
if status_code == 400 case status_code
when 400
[status_code, headers, ['Bad Request']] [status_code, headers, ['Bad Request']]
elsif status_code == 401 when 401
[status_code, headers, ['Unauthorized']] [status_code, headers, ['Unauthorized']]
end end
end end
# @param changes [Array{Symbol}] # @param changes [Array<Symbol>]
# @param myips [Array{String}] # @param myips [Array<String>]
# @return [String] # @return [String]
def get_success_body(changes, myips) def get_success_body(changes, myips)
changes.map { |change| change == :good ? "Changed to #{myips.join(' ')}" : "No change needed for #{myips.join(' ')}" }.join("\n") changes.map { |change| change == :good ? "Changed to #{myips.join(' ')}" : "No change needed for #{myips.join(' ')}" }.join("\n")

View File

@@ -28,7 +28,6 @@ module Dyndnsd
sleep @interval sleep @interval
Thread.new do Thread.new do
begin
write write
rescue StandardError => e rescue StandardError => e
@on_error[e] rescue nil @on_error[e] rescue nil
@@ -36,7 +35,6 @@ module Dyndnsd
end end
end end
end end
end
# @return [void] # @return [void]
def stop def stop
@@ -96,8 +94,8 @@ module Dyndnsd
# @param file [String] # @param file [String]
# @param base_name [String] # @param base_name [String]
# @param metric [Object] # @param metric [Object]
# @param keys [Array{Symbol}] # @param keys [Array<Symbol>]
# @param snapshot_keys [Array{Symbol}] # @param snapshot_keys [Array<Symbol>]
# @return [void] # @return [void]
def write_metric(file, base_name, metric, keys, snapshot_keys = []) def write_metric(file, base_name, metric, keys, snapshot_keys = [])
time = Time.now.to_i time = Time.now.to_i

View File

@@ -85,7 +85,7 @@ module Dyndnsd
# converts into suitable parameter form for Async::DNS::Resolver or Async::DNS::Server # converts into suitable parameter form for Async::DNS::Resolver or Async::DNS::Server
# #
# @param endpoint_list [Array{String}] # @param endpoint_list [Array<String>]
# @return [Array{Array{Object}}] # @return [Array{Array{Object}}]
def self.parse_endpoints(endpoint_list) def self.parse_endpoints(endpoint_list)
endpoint_list.map { |addr_string| addr_string.split('@') } endpoint_list.map { |addr_string| addr_string.split('@') }
@@ -139,7 +139,7 @@ module Dyndnsd
# @param name [String] # @param name [String]
# @param resource_class [Resolv::DNS::Resource] # @param resource_class [Resolv::DNS::Resource]
# @param transaction [Async::DNS::Transaction] # Since solargraph cannot parse this: param transaction [Async::DNS::Transaction]
# @return [void] # @return [void]
def process(name, resource_class, transaction) def process(name, resource_class, transaction)
if name != @domain || resource_class != Resolv::DNS::Resource::Generic::Type252_Class1 if name != @domain || resource_class != Resolv::DNS::Resource::Generic::Type252_Class1

View File

@@ -1,5 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
module Dyndnsd module Dyndnsd
VERSION = '2.3.1' VERSION = '3.1.0.rc1'
end end

View File

@@ -6,7 +6,7 @@ describe Dyndnsd::Daemon do
include Rack::Test::Methods include Rack::Test::Methods
def app def app
Dyndnsd.logger = Logger.new(STDOUT) Dyndnsd.logger = Logger.new($stdout)
Dyndnsd.logger.level = Logger::UNKNOWN Dyndnsd.logger.level = Logger::UNKNOWN
config = { config = {