mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge pull request #372 from YunoHost/tsig-sha256
Uses hmac-sha512 for dyndns TSIG
This commit is contained in:
commit
be8ae067e6
4 changed files with 172 additions and 31 deletions
|
@ -213,6 +213,14 @@
|
|||
"mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space",
|
||||
"maindomain_change_failed": "Unable to change the main domain",
|
||||
"maindomain_changed": "The main domain has been changed",
|
||||
"migrate_tsig_end": "Migration to hmac-sha512 finished",
|
||||
"migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}",
|
||||
"migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512",
|
||||
"migrate_tsig_wait": "Let's wait 3min for the dyndns server to take the new key into account...",
|
||||
"migrate_tsig_wait_2": "2min...",
|
||||
"migrate_tsig_wait_3": "1min...",
|
||||
"migrate_tsig_wait_4": "30 secondes...",
|
||||
"migrate_tsig_not_needed": "You do not appear to use a dyndns domain, so no migration is needed !",
|
||||
"migrations_backward": "Migrating backward.",
|
||||
"migrations_bad_value_for_target": "Invalide number for target argument, available migrations numbers are 0 or {}",
|
||||
"migrations_cant_reach_migration_file": "Can't access migrations files at path %s",
|
||||
|
|
91
src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py
Normal file
91
src/yunohost/data_migrations/0002_migrate_to_tsig_sha256.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
import glob
|
||||
import os
|
||||
import requests
|
||||
import base64
|
||||
import time
|
||||
import json
|
||||
import errno
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
from yunohost.tools import Migration
|
||||
from yunohost.dyndns import _guess_current_dyndns_domain
|
||||
|
||||
logger = getActionLogger('yunohost.migration')
|
||||
|
||||
|
||||
class MyMigration(Migration):
|
||||
"Migrate Dyndns stuff from MD5 TSIG to SHA512 TSIG"
|
||||
|
||||
def backward(self):
|
||||
# Not possible because that's a non-reversible operation ?
|
||||
pass
|
||||
|
||||
def migrate(self, dyn_host="dyndns.yunohost.org", domain=None, private_key_path=None):
|
||||
|
||||
if domain is None or private_key_path is None:
|
||||
try:
|
||||
(domain, private_key_path) = _guess_current_dyndns_domain(dyn_host)
|
||||
assert "+157" in private_key_path
|
||||
except (MoulinetteError, AssertionError):
|
||||
logger.warning(m18n.n("migrate_tsig_not_needed"))
|
||||
return
|
||||
|
||||
logger.warning(m18n.n('migrate_tsig_start', domain=domain))
|
||||
public_key_path = private_key_path.rsplit(".private", 1)[0] + ".key"
|
||||
public_key_md5 = open(public_key_path).read().strip().split(' ')[-1]
|
||||
|
||||
os.system('cd /etc/yunohost/dyndns && '
|
||||
'dnssec-keygen -a hmac-sha512 -b 512 -r /dev/urandom -n USER %s' % domain)
|
||||
os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private')
|
||||
|
||||
# +165 means that this file store a hmac-sha512 key
|
||||
new_key_path = glob.glob('/etc/yunohost/dyndns/*+165*.key')[0]
|
||||
public_key_sha512 = open(new_key_path).read().strip().split(' ', 6)[-1]
|
||||
|
||||
try:
|
||||
r = requests.put('https://%s/migrate_key_to_sha512/' % (dyn_host),
|
||||
data={
|
||||
'public_key_md5': base64.b64encode(public_key_md5),
|
||||
'public_key_sha512': base64.b64encode(public_key_sha512),
|
||||
}, timeout=30)
|
||||
except requests.ConnectionError:
|
||||
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
|
||||
|
||||
if r.status_code != 201:
|
||||
try:
|
||||
error = json.loads(r.text)['error']
|
||||
except Exception:
|
||||
# failed to decode json
|
||||
error = r.text
|
||||
|
||||
import traceback
|
||||
from StringIO import StringIO
|
||||
stack = StringIO()
|
||||
traceback.print_stack(file=stack)
|
||||
logger.error(stack.getvalue())
|
||||
|
||||
# Migration didn't succeed, so we rollback and raise an exception
|
||||
os.system("mv /etc/yunohost/dyndns/*+165* /tmp")
|
||||
|
||||
raise MoulinetteError(m18n.n('migrate_tsig_failed', domain=domain,
|
||||
error_code=str(r.status_code), error=error))
|
||||
|
||||
# remove old certificates
|
||||
os.system("mv /etc/yunohost/dyndns/*+157* /tmp")
|
||||
|
||||
# sleep to wait for dyndns cache invalidation
|
||||
logger.warning(m18n.n('migrate_tsig_wait'))
|
||||
time.sleep(60)
|
||||
logger.warning(m18n.n('migrate_tsig_wait_2'))
|
||||
time.sleep(60)
|
||||
logger.warning(m18n.n('migrate_tsig_wait_3'))
|
||||
time.sleep(30)
|
||||
logger.warning(m18n.n('migrate_tsig_wait_4'))
|
||||
time.sleep(30)
|
||||
|
||||
logger.warning(m18n.n('migrate_tsig_end'))
|
||||
return
|
||||
|
|
@ -27,6 +27,7 @@ import os
|
|||
import re
|
||||
import json
|
||||
import glob
|
||||
import time
|
||||
import base64
|
||||
import errno
|
||||
import requests
|
||||
|
@ -46,6 +47,14 @@ OLD_IPV4_FILE = '/etc/yunohost/dyndns/old_ip'
|
|||
OLD_IPV6_FILE = '/etc/yunohost/dyndns/old_ipv6'
|
||||
DYNDNS_ZONE = '/etc/yunohost/dyndns/zone'
|
||||
|
||||
RE_DYNDNS_PRIVATE_KEY_MD5 = re.compile(
|
||||
r'.*/K(?P<domain>[^\s\+]+)\.\+157.+\.private$'
|
||||
)
|
||||
|
||||
RE_DYNDNS_PRIVATE_KEY_SHA512 = re.compile(
|
||||
r'.*/K(?P<domain>[^\s\+]+)\.\+165.+\.private$'
|
||||
)
|
||||
|
||||
|
||||
def _dyndns_provides(provider, domain):
|
||||
"""
|
||||
|
@ -129,21 +138,22 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None
|
|||
|
||||
if key is None:
|
||||
if len(glob.glob('/etc/yunohost/dyndns/*.key')) == 0:
|
||||
os.makedirs('/etc/yunohost/dyndns')
|
||||
if not os.path.exists('/etc/yunohost/dyndns'):
|
||||
os.makedirs('/etc/yunohost/dyndns')
|
||||
|
||||
logger.info(m18n.n('dyndns_key_generating'))
|
||||
|
||||
os.system('cd /etc/yunohost/dyndns && '
|
||||
'dnssec-keygen -a hmac-md5 -b 128 -r /dev/urandom -n USER %s' % domain)
|
||||
'dnssec-keygen -a hmac-sha512 -b 512 -r /dev/urandom -n USER %s' % domain)
|
||||
os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private')
|
||||
|
||||
key_file = glob.glob('/etc/yunohost/dyndns/*.key')[0]
|
||||
with open(key_file) as f:
|
||||
key = f.readline().strip().split(' ')[-1]
|
||||
key = f.readline().strip().split(' ', 6)[-1]
|
||||
|
||||
# Send subscription
|
||||
try:
|
||||
r = requests.post('https://%s/key/%s' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain})
|
||||
r = requests.post('https://%s/key/%s?key_algo=hmac-sha512' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}, timeout=30)
|
||||
except requests.ConnectionError:
|
||||
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
|
||||
if r.status_code != 201:
|
||||
|
@ -213,6 +223,19 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None,
|
|||
|
||||
key = keys[0]
|
||||
|
||||
# This mean that hmac-md5 is used
|
||||
# (Re?)Trigger the migration to sha256 and return immediately.
|
||||
# The actual update will be done in next run.
|
||||
if "+157" in key:
|
||||
from yunohost.tools import _get_migration_by_name
|
||||
migration = _get_migration_by_name("migrate_to_tsig_sha256")
|
||||
try:
|
||||
migration["module"].MyMigration().migrate(dyn_host, domain, key)
|
||||
except Exception as e:
|
||||
logger.error(m18n.n('migrations_migration_has_failed', exception=e, **migration), exc_info=1)
|
||||
|
||||
return
|
||||
|
||||
# Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me'
|
||||
host = domain.split('.')[1:]
|
||||
host = '.'.join(host)
|
||||
|
@ -313,15 +336,13 @@ def _guess_current_dyndns_domain(dyn_host):
|
|||
dynette...)
|
||||
"""
|
||||
|
||||
re_dyndns_private_key = re.compile(
|
||||
r'.*/K(?P<domain>[^\s\+]+)\.\+157.+\.private$'
|
||||
)
|
||||
|
||||
# Retrieve the first registered domain
|
||||
for path in glob.iglob('/etc/yunohost/dyndns/K*.private'):
|
||||
match = re_dyndns_private_key.match(path)
|
||||
match = RE_DYNDNS_PRIVATE_KEY_MD5.match(path)
|
||||
if not match:
|
||||
continue
|
||||
match = RE_DYNDNS_PRIVATE_KEY_SHA512.match(path)
|
||||
if not match:
|
||||
continue
|
||||
_domain = match.group('domain')
|
||||
|
||||
# Verify if domain is registered (i.e., if it's available, skip
|
||||
|
|
|
@ -774,30 +774,10 @@ def tools_migrations_migrate(target=None, skip=False):
|
|||
|
||||
# loading all migrations
|
||||
for migration in tools_migrations_list()["migrations"]:
|
||||
logger.debug(m18n.n('migrations_loading_migration',
|
||||
number=migration["number"],
|
||||
name=migration["name"],
|
||||
))
|
||||
|
||||
try:
|
||||
# this is python builtin method to import a module using a name, we
|
||||
# use that to import the migration as a python object so we'll be
|
||||
# able to run it in the next loop
|
||||
module = import_module("yunohost.data_migrations.{file_name}".format(**migration))
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('migrations_error_failed_to_load_migration',
|
||||
number=migration["number"],
|
||||
name=migration["name"],
|
||||
))
|
||||
break
|
||||
|
||||
migrations.append({
|
||||
"number": migration["number"],
|
||||
"name": migration["name"],
|
||||
"module": module,
|
||||
"module": _get_migration_module(migration),
|
||||
})
|
||||
|
||||
migrations = sorted(migrations, key=lambda x: x["number"])
|
||||
|
@ -934,6 +914,47 @@ def _get_migrations_list():
|
|||
return sorted(migrations)
|
||||
|
||||
|
||||
def _get_migration_by_name(migration_name, with_module=True):
|
||||
"""
|
||||
Low-level / "private" function to find a migration by its name
|
||||
"""
|
||||
|
||||
migrations = tools_migrations_list()["migrations"]
|
||||
|
||||
matches = [ m for m in migrations if m["name"] == migration_name ]
|
||||
|
||||
assert len(matches) == 1, "Unable to find migration with name %s" % migration_name
|
||||
|
||||
migration = matches[0]
|
||||
|
||||
if with_module:
|
||||
migration["module"] = _get_migration_module(migration)
|
||||
|
||||
return migration
|
||||
|
||||
|
||||
def _get_migration_module(migration):
|
||||
|
||||
logger.debug(m18n.n('migrations_loading_migration',
|
||||
number=migration["number"],
|
||||
name=migration["name"],
|
||||
))
|
||||
|
||||
try:
|
||||
# this is python builtin method to import a module using a name, we
|
||||
# use that to import the migration as a python object so we'll be
|
||||
# able to run it in the next loop
|
||||
return import_module("yunohost.data_migrations.{file_name}".format(**migration))
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('migrations_error_failed_to_load_migration',
|
||||
number=migration["number"],
|
||||
name=migration["name"],
|
||||
))
|
||||
|
||||
|
||||
class Migration(object):
|
||||
|
||||
def migrate(self):
|
||||
|
|
Loading…
Add table
Reference in a new issue