chiark / gitweb /
ensure mtu is in the ipif substitution set
[hippotat.git] / hippotatlib / __init__.py
index 19a6aef3c26bab94ba00585d51c9a0d2c55c5134..de939c86b09621dbddb7c5f1445ad1bf4ed4b717 100644 (file)
@@ -50,6 +50,9 @@ from functools import partial
 
 import collections
 import time
+import hmac
+import hashlib
+import base64
 import codecs
 import traceback
 
@@ -139,6 +142,7 @@ port = 80
 vroutes = ''
 ifname_client = hippo%%d
 ifname_server = shippo%%d
+max_clock_skew = 300
 
 #[server] or [<client>] overrides
 ipif = userv root ipif %(local)s,%(peer)s,%(mtu)s,slip,%(ifname)s %(rnets)s
@@ -157,7 +161,7 @@ vvnetwork = 172.24.230.192
 
 
 # [<client-ip4-or-ipv6-address>]
-# password = <password>    # used by both, must match
+# secret = <secret>    # used by both, must match
 
 [LIMIT]
 max_batch_down = 262144
@@ -367,6 +371,34 @@ def crash_on_critical(event):
   if event.get('log_level') >= LogLevel.critical:
     crash(twisted.logger.formatEvent(event))
 
+#---------- authentication tokens ----------
+
+_authtoken_digest = hashlib.sha256
+
+def _authtoken_time():
+  return int(time.time())
+
+def _authtoken_hmac(secret, hextime):
+  return hmac.new(secret, hextime, _authtoken_digest).digest()
+
+def authtoken_make(secret):
+  hextime = ('%x' % _authtoken_time()).encode('ascii')
+  mac = _authtoken_hmac(secret, hextime)
+  return hextime + b' ' + base64.b64encode(mac)
+
+def authtoken_check(secret, token, maxskew):
+  (hextime, theirmac64) = token.split(b' ')
+  now = _authtoken_time()
+  then = int(hextime, 16)
+  skew = then - now;
+  if (abs(skew) > maxskew):
+    raise ValueError('too much clock skew (client %ds ahead)' % skew)
+  theirmac = base64.b64decode(theirmac64)
+  ourmac = _authtoken_hmac(secret, hextime)
+  if not hmac.compare_digest(theirmac, ourmac):
+    raise ValueError('invalid token (wrong secret?)')
+  pass
+
 #---------- config processing ----------
 
 def _cfg_process_putatives():
@@ -390,12 +422,17 @@ def _cfg_process_putatives():
         server_pat + r' ' + '(?:' + client_pat + '|LIMIT)')
 
   for cs in cfg.sections():
+    def dbg(m):
+      log_debug_config('putatives: section [%s] %s' % (cs, m))
+
     def log_ignore(why):
+      dbg('X ignore: %s' % (why))
       print('warning: ignoring config section [%s] (%s)' % (cs, why),
             file=sys.stderr)
 
     if cs == 'LIMIT' or cs == 'COMMON':
       # plan A "[LIMIT]" or "[COMMON]"
+      dbg('A ignore')
       continue
 
     try:
@@ -405,6 +442,7 @@ def _cfg_process_putatives():
 
       if server_re.fullmatch(cs):
         # plan C "[<servername>]"
+        dbg('C <server>')
         putative(servers, cs, cs)
         continue
 
@@ -414,24 +452,30 @@ def _cfg_process_putatives():
 
         if pcs == 'LIMIT':
           # plan E "[<servername> LIMIT]"
+          dbg('E <server> LIMIT')
           continue
 
         try:
           # plan D "[<servername> <client>]" part 2
-          ci = ipaddr(pc)
+          ci = ipaddr(pcs)
         except AddressValueError:
-          # plan F "[<some thing we do not understand>]"
+          # plan F branch 1 "[<some thing we do not understand>]"
           log_ignore('bad-addr')
           continue
 
         else: # no AddressValueError
-          # plan D "[<servername> <client]" part 3
+          # plan D "[<servername> <client>]" part 3
+          dbg('D <server> <client>')
           putative(clients, ci, pcs)
           putative(servers, pss, pss)
           continue
+      else:
+        # plan F branch 2 "[<some thing we do not understand>]"
+        log_ignore('nomatch '+ repr(serverclient_re))
 
     else: # no AddressValueError
       # plan B "[<client>" part 2
+      dbg('B <client>')
       putative(clients, ci, cs)
       continue
 
@@ -509,7 +553,7 @@ def cfg_process_client_limited(cc,ss,sections,key):
   cc.__dict__[key] = min(val,lim)
 
 def cfg_process_client_common(cc,ss,cs,ci):
-  # returns sections to search in, iff password is defined, otherwise None
+  # returns sections to search in, iff secret is defined, otherwise None
   cc.ci = ci
 
   sections = ['%s %s' % (ss,cs),
@@ -517,11 +561,11 @@ def cfg_process_client_common(cc,ss,cs,ci):
               ss,
               'COMMON']
 
-  try: pwsection = cfg_search_section('password', sections)
+  try: pwsection = cfg_search_section('secret', sections)
   except NoOptionError: return None
     
-  pw = cfg1get(pwsection, 'password')
-  cc.password = pw.encode('utf-8')
+  pw = cfg1get(pwsection, 'secret')
+  cc.secret = pw.encode('utf-8')
 
   cfg_process_client_limited(cc,ss,sections,'target_requests_outstanding')
   cfg_process_client_limited(cc,ss,sections,'http_timeout')
@@ -533,6 +577,9 @@ def cfg_process_ipif(c, sections, varmap):
     try: v = getattr(c, s)
     except AttributeError: continue
     setattr(c, d, v)
+  for d in ('mtu',):
+    v = cfg_search(cfg.get, d, sections)
+    setattr(c, d, v)
 
   #print('CFGIPIF',repr((varmap, sections, c.__dict__)),file=sys.stderr)
 
@@ -595,7 +642,7 @@ def common_startup(process_cfg):
 
   def read_defconfig():
     readconfig('/etc/hippotat/config.d', False)
-    readconfig('/etc/hippotat/passwords.d', False)
+    readconfig('/etc/hippotat/secrets.d', False)
     readconfig('/etc/hippotat/master.cfg',   False)
 
   def oc_defconfig(od,os, value, op):