From eb9b14f8fe7576e29483e7987a4bdf1ac6cd6862 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 6 Sep 2017 05:29:31 +0200 Subject: [PATCH 01/21] [enh] store key algo --- dynette.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dynette.rb b/dynette.rb index 02a6600..d2d62c6 100755 --- a/dynette.rb +++ b/dynette.rb @@ -27,6 +27,13 @@ class Entry property :id, Serial property :public_key, String + + # for historical reasons, dnssec algo was md5, so we assume that every + # entry is using md5 while we provide automatic upgrade code inside + # yunohost to move to sha256 instead (and register new domains using sh256) + # it would be good to depreciate md5 in the futur but that migh be complicated + property :key_algo, String, :default => "hmac-md5" + property :subdomain, String property :current_ip, String property :created_at, DateTime From 3c9eb4469c3cadb318a1b67a93844ac7192eea7f Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 6 Sep 2017 10:40:56 +0200 Subject: [PATCH 02/21] [enh] use db key_algo entry to generate bind files in cron --- dynette.cron.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynette.cron.py b/dynette.cron.py index d8daea5..cdf542d 100755 --- a/dynette.cron.py +++ b/dynette.cron.py @@ -95,7 +95,7 @@ for url in subs_urls: for entry in result: lines.extend([ 'key '+ entry['subdomain'] +'. {', - ' algorithm hmac-md5;', + ' algorithm ' + entry['key_algo'] + ';', ' secret "'+ entry['public_key'] +'";', '};', ]) From 5836772ef7e9ff1a6647848655b6af59231ea546 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 18 Sep 2017 19:47:27 +0200 Subject: [PATCH 03/21] [mod] we can actually uses sha512 here --- dynette.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynette.rb b/dynette.rb index d2d62c6..6b10d0b 100755 --- a/dynette.rb +++ b/dynette.rb @@ -30,7 +30,7 @@ class Entry # for historical reasons, dnssec algo was md5, so we assume that every # entry is using md5 while we provide automatic upgrade code inside - # yunohost to move to sha256 instead (and register new domains using sh256) + # yunohost to move to sha512 instead (and register new domains using sh512) # it would be good to depreciate md5 in the futur but that migh be complicated property :key_algo, String, :default => "hmac-md5" From d94ecdc0587dd87f2f3d9b11af134d0c6ba29c6d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 18 Sep 2017 19:48:07 +0200 Subject: [PATCH 04/21] [enh] allows to register a key with hmac-sha512 algo --- dynette.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/dynette.rb b/dynette.rb index 6b10d0b..74bf6c9 100755 --- a/dynette.rb +++ b/dynette.rb @@ -114,10 +114,14 @@ end before path do if params.has_key?("public_key") public_key = Base64.decode64(params[:public_key].encode('ascii-8bit')) - unless public_key.length == 24 + # might be 88 + unless public_key.length == 24 or public_key.length == 89 halt 400, { :error => "Key is invalid: #{public_key.to_s.encode('UTF-8', {:invalid => :replace, :undef => :replace, :replace => '?'})}" }.to_json end end + if params.has_key?("key_algo") and not ["hmac-md5", "hmac-sha512"].include? params[:key_algo] + halt 400, { :error => "key_algo value is invalid: #{public_key}, it should be either 'hmac-sha512' or 'hmac-md5' (but you should **really** use 'hmac-sha512')" }.to_json + end if params.has_key?("subdomain") unless params[:subdomain].match /^([a-z0-9]{1}([a-z0-9\-]*[a-z0-9])*)(\.[a-z0-9]{1}([a-z0-9\-]*[a-z0-9])*)*(\.[a-z]{1}([a-z0-9\-]*[a-z0-9])*)$/ halt 400, { :error => "Subdomain is invalid: #{params[:subdomain]}" }.to_json @@ -183,8 +187,14 @@ post '/key/:public_key' do recovery_password = "" end + if params.has_key?("key_algo") + key_algo = params[:key_algo] + else # default until we'll one day kill it + key_algo = "hmac-md5" + end + # Process - entry = Entry.new(:public_key => params[:public_key], :subdomain => params[:subdomain], :current_ip => request.ip, :created_at => Time.now, :recovery_password => recovery_password) + entry = Entry.new(:public_key => params[:public_key], :subdomain => params[:subdomain], :current_ip => request.ip, :created_at => Time.now, :recovery_password => recovery_password, :key_algo => key_algo) entry.ips << Ip.create(:ip_addr => request.ip) if entry.save From 8626a2dca09ce5da30563e42b70065b67af110a6 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 18 Sep 2017 22:26:56 +0200 Subject: [PATCH 05/21] [fix] actually it's 45 chars since it's base64 encoded --- dynette.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynette.rb b/dynette.rb index 74bf6c9..13bce36 100755 --- a/dynette.rb +++ b/dynette.rb @@ -115,7 +115,7 @@ end if params.has_key?("public_key") public_key = Base64.decode64(params[:public_key].encode('ascii-8bit')) # might be 88 - unless public_key.length == 24 or public_key.length == 89 + unless public_key.length == 24 or public_key.length == 45 halt 400, { :error => "Key is invalid: #{public_key.to_s.encode('UTF-8', {:invalid => :replace, :undef => :replace, :replace => '?'})}" }.to_json end end From fbbf73fae11e76e6f9b29548f224f66574c4061d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 18 Sep 2017 22:43:37 +0200 Subject: [PATCH 06/21] [fix] it's actually 32 --- dynette.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynette.rb b/dynette.rb index 13bce36..5068e90 100755 --- a/dynette.rb +++ b/dynette.rb @@ -115,7 +115,7 @@ end if params.has_key?("public_key") public_key = Base64.decode64(params[:public_key].encode('ascii-8bit')) # might be 88 - unless public_key.length == 24 or public_key.length == 45 + unless public_key.length == 24 or public_key.length == 32 halt 400, { :error => "Key is invalid: #{public_key.to_s.encode('UTF-8', {:invalid => :replace, :undef => :replace, :replace => '?'})}" }.to_json end end From 1fe2fc535593e5fbbdab085a746fa72103192243 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 19 Sep 2017 16:54:50 +0200 Subject: [PATCH 07/21] [mod] typo in comment --- dynette.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynette.rb b/dynette.rb index 5068e90..6027f34 100755 --- a/dynette.rb +++ b/dynette.rb @@ -30,7 +30,7 @@ class Entry # for historical reasons, dnssec algo was md5, so we assume that every # entry is using md5 while we provide automatic upgrade code inside - # yunohost to move to sha512 instead (and register new domains using sh512) + # yunohost to move to sha512 instead (and register new domains using sha512) # it would be good to depreciate md5 in the futur but that migh be complicated property :key_algo, String, :default => "hmac-md5" From 464fd1ffc211463a138b1351b419b8b323ce202b Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Tue, 19 Sep 2017 19:18:07 +0200 Subject: [PATCH 08/21] [enh] first version of migration key algo api entry point --- dynette.rb | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/dynette.rb b/dynette.rb index 6027f34..851555e 100755 --- a/dynette.rb +++ b/dynette.rb @@ -204,6 +204,39 @@ post '/key/:public_key' do end end +# Migrate a key from hmac-md5 to hmac-sha512 because it's 2017 +put '/migrate_key_to_sha512/:public_key' do + # TODO check parameters + params[:public_key_md5] = Base64.decode64(params[:public_key_md5].encode('ascii-8bit')) + params[:public_key_sha512] = Base64.decode64(params[:public_key_sha512].encode('ascii-8bit')) + + # TODO signing handling + + # TODO check entry exists + entry = Entry.first(:public_key => params[:public_key_md5], + :key_algo => "hmac-md5") + + unless request.ip == entry.current_ip + entry.ips << Ip.create(:ip_addr => request.ip) + end + entry.current_ip = request.ip + + entry.public_key = params[:public_key_sha512] + entry.key_algo = "hmac-sha512" + + unless entry.save + halt 412, { :error => "A problem occured during key algo migration" }.to_json + end + + # need to regenerate bind9 stuff + `python ./dynette.cron.py` + # flush this idiotic bind cache because he doesn't know how to do that + # himself + `rndc flush` + + halt 200, { :public_key => entry.public_key, :subdomain => entry.subdomain, :current_ip => entry.current_ip }.to_json +end + # Update a sub-domain put '/key/:public_key' do params[:public_key] = Base64.decode64(params[:public_key].encode('ascii-8bit')) From dfcfbf472b3874fbfed5c3ffe13d5fa315ed9e26 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 20 Sep 2017 05:53:33 +0200 Subject: [PATCH 09/21] [mod] apparently yunohost standard is to return 201 --- dynette.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynette.rb b/dynette.rb index 851555e..a966979 100755 --- a/dynette.rb +++ b/dynette.rb @@ -234,7 +234,7 @@ put '/migrate_key_to_sha512/:public_key' do # himself `rndc flush` - halt 200, { :public_key => entry.public_key, :subdomain => entry.subdomain, :current_ip => entry.current_ip }.to_json + halt 201, { :public_key => entry.public_key, :subdomain => entry.subdomain, :current_ip => entry.current_ip }.to_json end # Update a sub-domain From d4964421ec39c076612b732948634b2dd5637ebf Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 20 Sep 2017 19:19:22 +0200 Subject: [PATCH 10/21] [fix] we don't expect this argument --- dynette.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynette.rb b/dynette.rb index a966979..77ba659 100755 --- a/dynette.rb +++ b/dynette.rb @@ -205,7 +205,7 @@ post '/key/:public_key' do end # Migrate a key from hmac-md5 to hmac-sha512 because it's 2017 -put '/migrate_key_to_sha512/:public_key' do +put '/migrate_key_to_sha512/' do # TODO check parameters params[:public_key_md5] = Base64.decode64(params[:public_key_md5].encode('ascii-8bit')) params[:public_key_sha512] = Base64.decode64(params[:public_key_sha512].encode('ascii-8bit')) From 48fe53d437b6a5a3414b65174ec5fd752ec4b644 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 21 Sep 2017 04:02:56 +0200 Subject: [PATCH 11/21] [mod] I'm ashamed --- dynette.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dynette.rb b/dynette.rb index 77ba659..08d1dab 100755 --- a/dynette.rb +++ b/dynette.rb @@ -229,10 +229,11 @@ put '/migrate_key_to_sha512/' do end # need to regenerate bind9 stuff - `python ./dynette.cron.py` + # yes this is awful + `python /root/dynette/dynette.cron.py` # flush this idiotic bind cache because he doesn't know how to do that # himself - `rndc flush` + `/usr/sbin/rndc flush` halt 201, { :public_key => entry.public_key, :subdomain => entry.subdomain, :current_ip => entry.current_ip }.to_json end From 0c40a0275ec90fdc1f5ca50854ad251063dfe8a6 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 21 Sep 2017 05:58:49 +0200 Subject: [PATCH 12/21] [fix] ask cron to flush bind cache on key migration situation --- dynette.cron.py | 6 ++++++ dynette.rb | 13 +++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/dynette.cron.py b/dynette.cron.py index cdf542d..77dfe74 100755 --- a/dynette.cron.py +++ b/dynette.cron.py @@ -119,3 +119,9 @@ else: os.system('/usr/sbin/rndc reload') print("An error occured ! Please check daemon.log and your conf.bad") exit(1) + +# mein got this is so awful +if os.path.exists('/tmp/dynette_flush_bind_cache'): + os.system('/usr/sbin/rndc flush') + os.system('/usr/sbin/rndc reload') + os.system('rm /tmp/dynette_flush_bind_cache') diff --git a/dynette.rb b/dynette.rb index 08d1dab..0529e64 100755 --- a/dynette.rb +++ b/dynette.rb @@ -228,12 +228,13 @@ put '/migrate_key_to_sha512/' do halt 412, { :error => "A problem occured during key algo migration" }.to_json end - # need to regenerate bind9 stuff - # yes this is awful - `python /root/dynette/dynette.cron.py` - # flush this idiotic bind cache because he doesn't know how to do that - # himself - `/usr/sbin/rndc flush` + # I don't have any other way of communicating with this dynette.cron.py + # this is awful + File.open("/tmp/dynette_flush_bind_cache", "w").close + + # assume that the dynette.cron.py runs every minute like on prod and add a + # bit of security margin. I hate that. + sleep(90) halt 201, { :public_key => entry.public_key, :subdomain => entry.subdomain, :current_ip => entry.current_ip }.to_json end From 8bfe653531c6e845ebb1c1334a769651589f3499 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 21 Sep 2017 06:24:29 +0200 Subject: [PATCH 13/21] [fix] hmac-sha512 keys are bigger than 50 chars --- dynette.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dynette.rb b/dynette.rb index 0529e64..efb71c4 100755 --- a/dynette.rb +++ b/dynette.rb @@ -26,7 +26,8 @@ class Entry include BCrypt property :id, Serial - property :public_key, String + # we need at least 90 chars for hmac-sha512 keys + property :public_key, String, :length => 100 # for historical reasons, dnssec algo was md5, so we assume that every # entry is using md5 while we provide automatic upgrade code inside From 8446a552f2b0923479e0c42d0ab9722bcfc80b2d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Thu, 21 Sep 2017 06:39:35 +0200 Subject: [PATCH 14/21] [fix] 90 secondes is not enough, let's be sure that this works --- dynette.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynette.rb b/dynette.rb index efb71c4..4218c08 100755 --- a/dynette.rb +++ b/dynette.rb @@ -235,7 +235,7 @@ put '/migrate_key_to_sha512/' do # assume that the dynette.cron.py runs every minute like on prod and add a # bit of security margin. I hate that. - sleep(90) + sleep(180) halt 201, { :public_key => entry.public_key, :subdomain => entry.subdomain, :current_ip => entry.current_ip }.to_json end From 368fe5d206ad11650517d2e400ee0a80b81b0ff1 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 9 Oct 2017 09:42:58 +0200 Subject: [PATCH 15/21] [enh] hammer it --- dynette.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dynette.rb b/dynette.rb index 4218c08..1167e85 100755 --- a/dynette.rb +++ b/dynette.rb @@ -232,6 +232,8 @@ put '/migrate_key_to_sha512/' do # I don't have any other way of communicating with this dynette.cron.py # this is awful File.open("/tmp/dynette_flush_bind_cache", "w").close + # let's try flusing here, hope that could help ... (this design is so awful) + `/usr/sbin/rndc flush` # assume that the dynette.cron.py runs every minute like on prod and add a # bit of security margin. I hate that. From b8487cd3a93d972aa12d2b087c3ebe509ed8f2ac Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 9 Oct 2017 12:09:49 +0200 Subject: [PATCH 16/21] [mod] I'm going to wait in YunoHost code, not on the server --- dynette.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dynette.rb b/dynette.rb index 1167e85..55c4e77 100755 --- a/dynette.rb +++ b/dynette.rb @@ -235,10 +235,6 @@ put '/migrate_key_to_sha512/' do # let's try flusing here, hope that could help ... (this design is so awful) `/usr/sbin/rndc flush` - # assume that the dynette.cron.py runs every minute like on prod and add a - # bit of security margin. I hate that. - sleep(180) - halt 201, { :public_key => entry.public_key, :subdomain => entry.subdomain, :current_ip => entry.current_ip }.to_json end From e25a8cb7380402b82a4aeebb1b58b1458979d6f7 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Mon, 9 Oct 2017 12:34:55 +0200 Subject: [PATCH 17/21] [doc] it's tsig, not dnssec --- dynette.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynette.rb b/dynette.rb index 55c4e77..02f694f 100755 --- a/dynette.rb +++ b/dynette.rb @@ -29,7 +29,7 @@ class Entry # we need at least 90 chars for hmac-sha512 keys property :public_key, String, :length => 100 - # for historical reasons, dnssec algo was md5, so we assume that every + # for historical reasons, tsig algo was md5, so we assume that every # entry is using md5 while we provide automatic upgrade code inside # yunohost to move to sha512 instead (and register new domains using sha512) # it would be good to depreciate md5 in the futur but that migh be complicated From dfdb70f4cedc782dbad0dc90371d145eb3721245 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 3 Jan 2018 11:01:38 +0100 Subject: [PATCH 18/21] [mod] remove useless comment --- dynette.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/dynette.rb b/dynette.rb index 02f694f..f914e5c 100755 --- a/dynette.rb +++ b/dynette.rb @@ -115,7 +115,6 @@ end before path do if params.has_key?("public_key") public_key = Base64.decode64(params[:public_key].encode('ascii-8bit')) - # might be 88 unless public_key.length == 24 or public_key.length == 32 halt 400, { :error => "Key is invalid: #{public_key.to_s.encode('UTF-8', {:invalid => :replace, :undef => :replace, :replace => '?'})}" }.to_json end From 7662757275c5c1b6358a91b906dbb4453a77cbad Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 3 Jan 2018 11:01:54 +0100 Subject: [PATCH 19/21] [mod] actually this won't increase security at all because tsig is stupid --- dynette.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/dynette.rb b/dynette.rb index f914e5c..5aa8740 100755 --- a/dynette.rb +++ b/dynette.rb @@ -210,8 +210,6 @@ put '/migrate_key_to_sha512/' do params[:public_key_md5] = Base64.decode64(params[:public_key_md5].encode('ascii-8bit')) params[:public_key_sha512] = Base64.decode64(params[:public_key_sha512].encode('ascii-8bit')) - # TODO signing handling - # TODO check entry exists entry = Entry.first(:public_key => params[:public_key_md5], :key_algo => "hmac-md5") From 41e2e480bb247f8d065af7bcc9eaddc543320bf9 Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 3 Jan 2018 11:23:18 +0100 Subject: [PATCH 20/21] [enh] add better error management --- dynette.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dynette.rb b/dynette.rb index 5aa8740..419d676 100755 --- a/dynette.rb +++ b/dynette.rb @@ -222,6 +222,17 @@ put '/migrate_key_to_sha512/' do entry.public_key = params[:public_key_sha512] entry.key_algo = "hmac-sha512" + # we probably want to remove this once this algo is stable to avoid having + # an entry point in our api where some could possibly hammer our db to try + # to find if a key is registered or not + if entry == nil + if Entry.first(:public_key => params[:public_key_sha512]) + halt 400, { :error => "This domain has already been migrated to hmac-sha512." }.to_json + else + halt 404, { :error => "There is not domain registered with this key." }.to_json + end + end + unless entry.save halt 412, { :error => "A problem occured during key algo migration" }.to_json end From dae4f16d111e96dec3dc3bd4d36c1164db748a3d Mon Sep 17 00:00:00 2001 From: Laurent Peuch Date: Wed, 3 Jan 2018 20:51:12 +0100 Subject: [PATCH 21/21] [fix] sha512 key size is 89 --- dynette.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynette.rb b/dynette.rb index 419d676..b4703d5 100755 --- a/dynette.rb +++ b/dynette.rb @@ -115,7 +115,7 @@ end before path do if params.has_key?("public_key") public_key = Base64.decode64(params[:public_key].encode('ascii-8bit')) - unless public_key.length == 24 or public_key.length == 32 + unless public_key.length == 24 or public_key.length == 89 halt 400, { :error => "Key is invalid: #{public_key.to_s.encode('UTF-8', {:invalid => :replace, :undef => :replace, :replace => '?'})}" }.to_json end end