Compare commits

..

No commits in common. "dev" and "master" have entirely different histories.
dev ... master

6 changed files with 108 additions and 380 deletions

View File

@ -10,4 +10,4 @@ DEPENDENCIES
xmpp4r xmpp4r
BUNDLED WITH BUNDLED WITH
2.1.4 1.17.2

View File

@ -1,15 +0,0 @@
{ pkgs, helpers, ... }:
with pkgs.lib;
let
gems = pkgs.bundlerEnv {
name = "gems-for-backplane-dns-client";
gemdir = ./.;
};
in helpers.lib.writeRubyApplication {
name = "backplane-dns-client";
inherit pkgs;
ruby = gems.wrappedRuby;
text = readFile ./dns-client.rb;
}

View File

@ -1,70 +1,70 @@
# frozen_string_literal: true
require 'ipaddr' require "ipaddr"
require 'socket' require "socket"
require 'optparse' require "optparse"
require 'json' require "json"
require 'securerandom' require "securerandom"
require 'xmpp4r'
options = { require "xmpp4r"
sshfp: []
}
# rubocop:disable Metrics/BlockLength puts ARGV
options = {}
OptionParser.new do |opts| OptionParser.new do |opts|
opts.banner = 'usage: ${$0} [opts]' opts.banner = "usage: ${$0} [opts]"
opts.on('-i', '--interface=INTERFACE', opts.on("-i", "--interface=INTERFACE",
'Publicly-accessible interface') do |interface| "Publicly-accessible interface") do |interface|
options[:interface] = interface options[:interface] = interface
end end
opts.on('-d', '--domain=DOMAIN', opts.on("-d", "--domain=DOMAIN",
'Domain on which we wish to set the new ip') do |domain| "Domain on which we wish to set the new ip") do |domain|
options[:domain] = domain options[:domain] = domain
end end
opts.on('-s', '--server=SERVER', opts.on("-s", "--server=SERVER",
'Backplane DNS XMPP server') do |server| "Backplane DNS XMPP server") do |server|
options[:server] = server options[:server] = server
end end
opts.on('-p', '--password-file=/path/to/file', opts.on("-p", "--password-file=/path/to/file",
'File containing password for XMPP server') do |pw_file| "File containing password for XMPP server") do |pw_file|
options[:pw_file] = pw_file options[:pw_file] = pw_file
end end
opts.on('-4', '--ipv4', opts.on("-4", "--ipv4",
'Check for a public IPv4 and register with the backplane.') do "Check for a public IPv4 and register with the backplane.") do
options[:ipv4] = true options[:ipv4] = true
end end
opts.on('-6', '--ipv6', opts.on("-6", "--ipv6",
'Check for a public IPv6 and register with the backplane.') do "Check for a public IPv6 and register with the backplane.") do
options[:ipv6] = true options[:ipv6] = true
end end
opts.on('-f', '--ssh-fp=SSHFP', 'SSH fingerprint to register with he backplane.') do |sshfp| opts.on("-f", "--sshfp=FILE",
options[:sshfp] << sshfp "Register host SSH key fingerprints with the backplane.") do |file|
options[:sshfp] = [] if not options[:sshfp]
options[:sshfp] = options[:sshfp] + [file]
end end
end.parse! end.parse!
# rubocop:enable Metrics/BlockLength
raise 'domain is required' unless options[:domain]
raise 'server is required' unless options[:server]
raise 'password file is required' unless options[:pw_file]
raise 'at least one of -4 or -6 required' unless options[:ipv4] || options[:ipv6]
password = options[:pw_file]
raise "file does not exist or is not readable: #{password}" unless File::readable?(password)
def error(msg) def error(msg)
puts msg puts msg
raise msg throw msg
end end
# XMPP client for Fudo Backplane error("domain is required") if not options[:domain]
error("server is required") if not options[:server]
error("password file is required") if not options[:pw_file]
error("at least one of -4 or -6 required") if not (options[:ipv4] or options[:ipv6])
if not File::readable?(options[:pw_file])
error("file does not exist or is not readable")
end
password = File::open(options[:pw_file]) { |f| f.gets.strip }
class XMPPClient class XMPPClient
def initialize(domain, hostname, server, password) def initialize(domain, hostname, server, password)
@jid = "host-#{hostname}@#{server}" @jid = "host-#{hostname}@#{server}"
@ -80,7 +80,7 @@ class XMPPClient
disconnect if connected? disconnect if connected?
@client = Jabber::Client::new(@jid) @client = Jabber::Client::new(@jid)
@client.connect # will use SRV records @client.connect # will use SRV records
error('failed to initialize TLS connection') unless @client.is_tls? error("failed to initialize TLS connection") if not @client.is_tls?
@client.auth(@password) @client.auth(@password)
register_response_callback register_response_callback
end end
@ -94,7 +94,7 @@ class XMPPClient
if @client.respond_to?(:is_connected?) && @client.is_connected? if @client.respond_to?(:is_connected?) && @client.is_connected?
begin begin
@client.close @client.close
rescue Errno::EPIPE, IOError rescue Errno::EPIPE, IOError => e
nil nil
end end
end end
@ -110,7 +110,7 @@ class XMPPClient
@client.send(msg) @client.send(msg)
response = receive_response(msg_id) response = receive_response(msg_id)
puts "response: #{response}" puts "response: #{response}"
response && response['status'] == 'OK' response and response["status"] == "OK"
end end
def send_ip(ip) def send_ip(ip)
@ -125,7 +125,7 @@ class XMPPClient
{ {
version: 1, version: 1,
service: :dns, service: :dns,
msgid: msg_id, msgid: msg_id,
payload: req payload: req
} }
end end
@ -133,16 +133,16 @@ class XMPPClient
def ip_payload(ip) def ip_payload(ip)
{ {
request: ip.ipv4? ? :change_ipv4 : :change_ipv6, request: ip.ipv4? ? :change_ipv4 : :change_ipv6,
domain: @domain, domain: @domain,
ip: ip.to_s ip: ip.to_s
} }
end end
def sshfp_payload(fingerprint) def sshfp_payload(fp)
{ {
request: :change_sshfp, request: :change_sshfp,
domain: @domain, domain: @domain,
sshfp: fingerprint sshfp: fp
} }
end end
@ -158,36 +158,35 @@ class XMPPClient
def receive_response(msg_id) def receive_response(msg_id)
msg = @responses.pop msg = @responses.pop
return msg if msg && msg['msgid'] == msg_id.to_s return msg if (msg and (msg["msgid"] == msg_id.to_s))
raise "failed to receive message: #{msg}" raise "failed to receive message: #{msg}"
end end
end end
RESERVED_V4_NETWORKS = [ RESERVED_V4_NETWORKS = [
'0.0.0.0/8', "0.0.0.0/8",
'10.0.0.0/8', "10.0.0.0/8",
'100.64.0.0/10', "100.64.0.0/10",
'127.0.0.0/8', "127.0.0.0/8",
'169.254.0.0/16', "169.254.0.0/16",
'172.16.0.0/12', "172.16.0.0/12",
'192.0.0.0/24', "192.0.0.0/24",
'192.0.2.0/24', "192.0.2.0/24",
'192.88.99.0/24', "192.88.99.0/24",
'192.168.0.0/16', "192.168.0.0/16",
'198.18.0.0/15', "198.18.0.0/15",
'198.51.100.0/24', "198.51.100.0/24",
'203.0.113.0/24', "203.0.113.0/24",
'224.0.0.0/4', "224.0.0.0/4",
'240.0.0.0/4', "240.0.0.0/4",
'255.255.255.255/32' "255.255.255.255/32"
].map { |ip| IPAddr.new(ip) } ].map { |ip| IPAddr.new(ip) }
def public_ip?(ip) def public_ip?(ip)
if ip.ipv4? if (ip.ipv4?)
RESERVED_V4_NETWORKS.none? { |network| network.include? ip } not RESERVED_V4_NETWORKS.any? { |network| network.include? ip }
elsif ip.ipv6? elsif (ip.ipv6?)
!(ip.link_local? || ip.loopback? || ip.private?) not (ip.link_local? or ip.loopback? or ip.private?)
else else
false false
end end
@ -197,28 +196,38 @@ def to_ipaddr(addrinfo)
if addrinfo.ipv4? if addrinfo.ipv4?
IPAddr.new addrinfo.ip_address IPAddr.new addrinfo.ip_address
else else
IPAddr.new(addrinfo.ip_address.split('%')[0]) IPAddr.new(addrinfo.ip_address.split("%")[0])
end end
end end
def local_addresses def local_addresses
ips = Socket::ip_address_list.map do |addrinfo| Socket::ip_address_list.map do |addrinfo|
to_ipaddr(addrinfo) to_ipaddr(addrinfo)
end end.select { |ip| public_ip?(ip) }
ips.select { |ip| public_ip?(ip) }
end end
def interface_addresses(interface) def interface_addresses(interface)
ifaddrs = Socket::getifaddrs.select do |ifaddr| Socket::getifaddrs.select do |ifaddr|
ifaddr.name == interface && ifaddr.name == interface
ifaddr.addr.ip? && end.select do |ifaddr|
ifaddr.flag & Socket::IFF_MULTICAST != 0 ifaddr.addr.ip? and (ifaddr.flags & Socket::IFF_MULTICAST != 0)
end.map do |ifaddr|
to_ipaddr(ifaddr.addr)
end.filter do |ip|
public_ip? ip
end end
ifaddrs.map { |ifaddr| to_ipaddr(ifaddr.addr) }.filter(:public_ip?) end
def host_sshfp(keys)
keys.flat_map { |keyfile|
`ssh-keygen -r hostname -f #{keyfile}`.split("\n")
}.map { |fp|
fp.match(/[0-9] [0-9] [a-fA-F0-9]{32,64}$/)[0]
}.compact
end end
def hostname def hostname
Socket.gethostname.split('.').first Socket.gethostname.split(".").first
end end
client = XMPPClient::new(options[:domain], client = XMPPClient::new(options[:domain],
@ -238,13 +247,13 @@ begin
end end
if options[:ipv4] if options[:ipv4]
ipv4 = addrs.find(:ipv4?) ipv4 = addrs.find { |ip| ip.ipv4? }
if ipv4 if ipv4
puts "#{options[:server]}: #{hostname}.#{options[:domain]} IN A => #{ipv4}" puts "#{options[:server]}: #{hostname}.#{options[:domain]} IN A => #{ipv4.to_s}"
if client.send_ip(ipv4) if client.send_ip(ipv4)
puts 'OK' puts "OK"
else else
puts 'ERROR' puts "ERROR"
success = false success = false
end end
else else
@ -253,13 +262,13 @@ begin
end end
if options[:ipv6] if options[:ipv6]
ipv6 = addrs.find(:ipv6?) ipv6 = addrs.find { |ip| ip.ipv6? }
if ipv6 if ipv6
puts "#{options[:server]}: #{hostname}.#{options[:domain]} IN AAAA => #{ipv6}" puts "#{options[:server]}: #{hostname}.#{options[:domain]} IN AAAA => #{ipv6.to_s}"
if client.send_ip(ipv6) if client.send_ip(ipv6)
puts 'OK' puts "OK"
else else
puts 'ERROR' puts "ERROR"
success = false success = false
end end
else else
@ -267,14 +276,18 @@ begin
end end
end end
unless options[:sshfp].empty? if options[:sshfp]
fps = options[:sshfp] fps = host_sshfp(options[:sshfp])
puts "#{options[:server]}: #{hostname}.#{options[:domain]} IN SSHFP => #{fps}" if not fps.empty?
if client.send_sshfp(fps) puts "#{options[:server]}: #{hostname}.#{options[:domain]} IN SSHFP => #{fps}"
puts 'OK' if client.send_sshfp(fps)
puts "OK"
else
puts "ERROR"
success = false
end
else else
puts 'ERROR' puts "#{options[:server]}: no valid sshfps found"
success = false
end end
end end
ensure ensure

View File

@ -1,236 +0,0 @@
{
"nodes": {
"clj-nix": {
"inputs": {
"devshell": "devshell",
"flake-utils": "flake-utils_2",
"nixpkgs": [
"helpers",
"nixpkgs"
]
},
"locked": {
"lastModified": 1663870497,
"narHash": "sha256-gnoyYWvZl64WBqR3tf9bKHAznEtBCHmwx7taHghH9Lw=",
"owner": "jlesquembre",
"repo": "clj-nix",
"rev": "23d9daacc80e634df078c4c6e34d592e1593d84c",
"type": "github"
},
"original": {
"owner": "jlesquembre",
"repo": "clj-nix",
"type": "github"
}
},
"clj2nix": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": "nixpkgs",
"utils": "utils"
},
"locked": {
"lastModified": 1654804151,
"narHash": "sha256-D/fRFmem9MoSWAmeK8VE6EMtfBRF3xPpEXp9AotW+K0=",
"owner": "hlolli",
"repo": "clj2nix",
"rev": "a321028a6670fc6329272a4d1ac0054f0b25d920",
"type": "github"
},
"original": {
"owner": "hlolli",
"repo": "clj2nix",
"type": "github"
}
},
"devshell": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"helpers",
"clj-nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1658746384,
"narHash": "sha256-CCJcoMOcXyZFrV1ag4XMTpAPjLWb4Anbv+ktXFI1ry0=",
"owner": "numtide",
"repo": "devshell",
"rev": "0ffc7937bb5e8141af03d462b468bd071eb18e1b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1627913399,
"narHash": "sha256-hY8g6H2KFL8ownSiFeMOjwPC8P0ueXpCVEbxgda3pko=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "12c64ca55c1014cdc1b16ed5a804aa8576601ff2",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"locked": {
"lastModified": 1642700792,
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"locked": {
"lastModified": 1656928814,
"narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"helpers": {
"inputs": {
"clj-nix": "clj-nix",
"clj2nix": "clj2nix",
"nixpkgs": "nixpkgs_2",
"utils": "utils_2"
},
"locked": {
"lastModified": 1674932271,
"narHash": "sha256-ArHZiRPpPMJyGSa65VKwTUG6aNICtMTf26pjOuK4snQ=",
"ref": "refs/heads/master",
"rev": "1923e4cf4ef116782c9ea29416150bdb3d532b0b",
"revCount": 17,
"type": "git",
"url": "https://git.fudo.org/fudo-public/nix-helpers.git"
},
"original": {
"type": "git",
"url": "https://git.fudo.org/fudo-public/nix-helpers.git"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1637881340,
"narHash": "sha256-/meU5CTm8GnaETZrJa0UqBQvk9T/jKp1+MLIQQ7FTTo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d460f48ddb884f7270b7f7bfcbf8a7b91140caa5",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1672353432,
"narHash": "sha256-oZfgp/44/o2tWiylV30cR+DLyWTJ+5dhsdWZVpzs3e4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "913a47cd064cc06440ea84e5e0452039a85781f0",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-22.11",
"type": "indirect"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1672580127,
"narHash": "sha256-3lW3xZslREhJogoOkjeZtlBtvFMyxHku7I/9IVehhT8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0874168639713f547c05947c76124f78441ea46c",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-22.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"helpers": "helpers",
"nixpkgs": "nixpkgs_3",
"utils": "utils_3"
}
},
"utils": {
"locked": {
"lastModified": 1637014545,
"narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"utils_2": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"utils_3": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@ -1,34 +0,0 @@
{
description = "Fudo Backplane Client.";
inputs = {
nixpkgs.url = "nixpkgs/nixos-22.05";
helpers.url = "git+https://git.fudo.org/fudo-public/nix-helpers.git";
utils.url = "github:numtide/flake-utils";
};
outputs = { nixpkgs, helpers, utils, ... }:
utils.lib.eachDefaultSystem (system:
let pkgs = nixpkgs.legacyPackages."${system}";
in {
packages = rec {
default = backplaneDnsClient;
backplaneDnsClient =
import ./dns-client.nix { inherit pkgs helpers; };
};
devShells = rec {
default = update;
update = let
update-deps = pkgs.writeShellApplication {
name = "update-deps";
runtimeInputs = with pkgs; [ bundler bundix ];
text = ''
bundle lock
bundix
'';
};
in pkgs.mkShell { buildInputs = with pkgs; [ update-deps ]; };
};
});
}