Commit 1c354e6c authored by Julien Muchembled's avatar Julien Muchembled

dist: use new entry_points['console_scripts'] way to ship scripts

The old distutils way is not compatible with zc.recipe.egg in develop mode,
because egg_info does not provide any information about such scripts.
parent 274d7bd2
......@@ -7,7 +7,7 @@ Standards-Version: 3.9.1
Package: re6stnet
Architecture: all
Depends: ${misc:Depends}, python (>= 2.6.6-3), python (<< 2.8), python-argparse, python-openssl (>= 0.13), openvpn (>= 2.3), babeld (= 1.6.0-nxd1), iproute2 | iproute, openssl
Depends: ${misc:Depends}, python (>= 2.6.6-3), python-pkg-resources, python-argparse, python-openssl (>= 0.13), openvpn (>= 2.3), babeld (= 1.6.0-nxd1), iproute2 | iproute, openssl
Recommends: ${python:Recommends}, logrotate
Suggests: ndisc6
Description: resilient, scalable, IPv6 network application
#!/usr/bin/python
import sqlite3, sys
import os; sys.path[0] = os.path.dirname(sys.path[0])
if 're6st' not in sys.modules:
import os; sys.path[0] = os.path.dirname(sys.path[0])
from re6st import utils, x509
from OpenSSL import crypto
......
......@@ -3,7 +3,8 @@
import argparse, httplib, select, socket, sqlite3, struct, sys, time, traceback
import xml.etree.cElementTree as ET
from collections import defaultdict
import os; sys.path[0] = os.path.dirname(sys.path[0])
if 're6st' not in sys.modules:
import os; sys.path[0] = os.path.dirname(sys.path[0])
from re6st import ctl, tunnel, utils
class iterRoutes(object):
......
#!/usr/bin/python
import argparse, atexit, binascii, errno, hashlib
import os, subprocess, sqlite3, sys, time
from OpenSSL import crypto
from re6st import registry, utils, x509
def create(path, text=None, mode=0666):
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, mode)
try:
os.write(fd, text)
finally:
os.close(fd)
def loadCert(pem):
return crypto.load_certificate(crypto.FILETYPE_PEM, pem)
def main():
parser = argparse.ArgumentParser(
description="Setup script for re6stnet.",
formatter_class=utils.HelpFormatter)
_ = parser.add_argument
_('--fingerprint', metavar='ALG:FINGERPRINT',
help="Check CA fingerprint to protect against MITM.")
_('--registry', required=True, metavar='URL',
help="HTTP URL of the server delivering certificates.")
_('--is-needed', action='store_true',
help="Exit immediately after asking the registry CA. Status code is"
" non-zero if we're already part of the network, which means"
" re6st is already running or we're behind a re6st router.")
_('--ca-only', action='store_true',
help='Only fetch CA from registry and exit.')
_('-d', '--dir',
help="Directory where the key and certificate will be stored.")
_('-r', '--req', nargs=2, action='append', metavar=('KEY', 'VALUE'),
help="The registry only sets the Common Name of your certificate,"
" which actually encodes your allocated prefix in the network."
" You can repeat this option to add any field you want to its"
" subject.")
_('--email',
help="Email address where your token is sent. Use -r option if you"
" want to show an email in your certificate.")
_('--token', help="The token you received.")
_('--anonymous', action='store_true',
help="Request an anonymous certificate. No email is required but the"
" registry may deliver a longer prefix.")
config = parser.parse_args()
if config.dir:
os.chdir(config.dir)
conf_path = 're6stnet.conf'
ca_path = 'ca.crt'
cert_path = 'cert.crt'
key_path = 'cert.key'
# Establish connection with server
s = registry.RegistryClient(config.registry)
# Get CA
ca = loadCert(s.getCa())
if config.fingerprint:
try:
alg, fingerprint = config.fingerprint.split(':', 1)
fingerprint = binascii.a2b_hex(fingerprint)
if hashlib.new(alg).digest_size != len(fingerprint):
raise ValueError("wrong size")
except StandardError, e:
parser.error("invalid fingerprint: %s" % e)
if x509.fingerprint(ca, alg).digest() != fingerprint:
sys.exit("CA fingerprint doesn't match")
else:
print "WARNING: it is strongly recommended to use --fingerprint option."
network = x509.networkFromCa(ca)
if config.is_needed:
route, err = subprocess.Popen(('ip', '-6', '-o', 'route', 'get',
utils.ipFromBin(network)),
stdout=subprocess.PIPE).communicate()
sys.exit(err or route and
utils.binFromIp(route.split()[8]).startswith(network))
create(ca_path, crypto.dump_certificate(crypto.FILETYPE_PEM, ca))
if config.ca_only:
sys.exit()
reserved = 'CN', 'serial'
req = crypto.X509Req()
try:
with open(cert_path) as f:
cert = loadCert(f.read())
components = dict(cert.get_subject().get_components())
for k in reserved:
components.pop(k, None)
except IOError, e:
if e.errno != errno.ENOENT:
raise
components = {}
if config.req:
components.update(config.req)
subj = req.get_subject()
for k, v in components.iteritems():
if k in reserved:
sys.exit(k + " field is reserved.")
if v:
setattr(subj, k, v)
cert_fd = token_advice = None
try:
token = config.token
if config.anonymous:
if not (token is config.email is None):
parser.error("--anonymous conflicts with --email/--token")
token = ''
elif not token:
if not config.email:
config.email = raw_input('Please enter your email address: ')
s.requestToken(config.email)
token_advice = "Use --token to retry without asking a new token\n"
while not token:
token = raw_input('Please enter your token: ')
try:
with open(key_path) as f:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
key = None
print "Reusing existing key."
except IOError, e:
if e.errno != errno.ENOENT:
raise
bits = ca.get_pubkey().bits()
print "Generating %s-bit key ..." % bits
pkey = crypto.PKey()
pkey.generate_key(crypto.TYPE_RSA, bits)
key = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
create(key_path, key, 0600)
req.set_pubkey(pkey)
req.sign(pkey, 'sha512')
req = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
# First make sure we can open certificate file for writing,
# to avoid using our token for nothing.
cert_fd = os.open(cert_path, os.O_CREAT | os.O_WRONLY, 0666)
print "Requesting certificate ..."
cert = s.requestCertificate(token, req)
if not cert:
token_advice = None
sys.exit("Error: invalid or expired token")
except:
if cert_fd is not None and not os.lseek(cert_fd, 0, os.SEEK_END):
os.remove(cert_path)
if token_advice:
atexit.register(sys.stdout.write, token_advice)
raise
os.write(cert_fd, cert)
os.ftruncate(cert_fd, len(cert))
os.close(cert_fd)
cert = loadCert(cert)
not_after = x509.notAfter(cert)
print("Setup complete. Certificate is valid until %s UTC"
" and will be automatically renewed after %s UTC.\n"
"Do not forget to backup to your private key (%s) or"
" you will lose your assigned subnet." % (
time.asctime(time.gmtime(not_after)),
time.asctime(time.gmtime(not_after - registry.RENEW_PERIOD)),
key_path))
if not os.path.lexists(conf_path):
create(conf_path, """\
registry %s
ca %s
cert %s
key %s
# increase re6stnet verbosity:
#verbose 3
# enable OpenVPN logging:
#ovpnlog
# increase OpenVPN verbosity:
#O--verb
#O3
""" % (config.registry, ca_path, cert_path, key_path))
print "Sample configuration file created."
cn = x509.subnetFromCert(cert)
subnet = network + utils.binFromSubnet(cn)
print "Your subnet: %s/%u (CN=%s)" \
% (utils.ipFromBin(subnet), len(subnet), cn)
if __name__ == "__main__":
main()
re6st/cli/conf.py
\ No newline at end of file
#!/usr/bin/python
import httplib, logging, socket
from BaseHTTPServer import BaseHTTPRequestHandler
from SocketServer import ThreadingTCPServer
from urlparse import parse_qsl
from re6st import ctl, registry, utils, version
# To generate server ca and key with serial for 2001:db8:42::/48
# openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 -days 3650 -out ca.crt
IPV6_V6ONLY = 26
SOL_IPV6 = 41
class RequestHandler(BaseHTTPRequestHandler):
if __import__("sys").version_info < (2, 7, 4):
def address_string(self):
# Workaround for http://bugs.python.org/issue6085
return self.client_address[0]
def do_GET(self):
try:
try:
path, query = self.path.split('?', 1)
except ValueError:
path = self.path
query = {}
else:
query = dict(parse_qsl(query, keep_blank_values=1,
strict_parsing=1))
_, path = path.split('/')
if not _:
return self.server.handle_request(self, path, query)
except Exception:
logging.info(self.requestline, exc_info=1)
self.send_error(httplib.BAD_REQUEST)
def log_error(*args):
pass
class HTTPServer4(ThreadingTCPServer):
allow_reuse_address = True
daemon_threads = True
class HTTPServer6(HTTPServer4):
address_family = socket.AF_INET6
def server_bind(self):
self.socket.setsockopt(SOL_IPV6, IPV6_V6ONLY, 1)
HTTPServer4.server_bind(self)
def main():
parser = utils.ArgParser(fromfile_prefix_chars='@',
description="re6stnet registry used to bootstrap nodes"
" and deliver certificates.")
_ = parser.add_argument
_('--port', type=int, default=80,
help="Port on which the server will listen.")
_('-4', dest='bind4', default='0.0.0.0',
help="Bind server to this IPv4.")
_('-6', dest='bind6', default='::',
help="Bind server to this IPv6.")
_('--db', default='/var/lib/re6stnet/registry.db',
help="Path to SQLite database file. It is automatically initialized"
" if the file does not exist.")
_('--dh',
help='File containing Diffie-Hellman parameters in .pem format')
_('--ca', required=True, help=parser._ca_help)
_('--key', required=True,
help="CA private key in .pem format.")
_('--mailhost', required=True,
help="SMTP host to send confirmation emails. For debugging"
" purpose, it can also be an absolute or existing path to"
" a mailbox file")
_('--prefix-length', default=16, type=int,
help="Default length of allocated prefixes.")
_('--anonymous-prefix-length', type=int,
help="Length of allocated anonymous prefixes."
" If 0 or unset, registration by email is required")
_('--ipv4', nargs=2, metavar=("IP/N", "PLEN"),
help="Enable ipv4. Each node is assigned a subnet of length PLEN"
" inside network IP/N.")
_('-l', '--logfile', default='/var/log/re6stnet/registry.log',
help="Path to logging file.")
_('-r', '--run', default='/var/run/re6stnet',
help="Path to re6stnet runtime directory:\n"
"- babeld.sock (option -R of babeld)\n")
_('-v', '--verbose', default=1, type=int,
help="Log level. 0 disables logging."
" Use SIGUSR1 to reopen log.")
_('--min-protocol', default=version.min_protocol, type=int,
help="Reject nodes that are too old. Current is %s." % version.protocol)
_ = parser.add_argument_group('routing').add_argument
_('--hello', type=int, default=15,
help="Hello interval in seconds, for both wired and wireless"
" connections. OpenVPN ping-exit option is set to 4 times the"
" hello interval. It takes between 3 and 4 times the"
" hello interval for Babel to re-establish connection with a"
" node for which the direct connection has been cut.")
_ = parser.add_argument_group('tunnelling').add_argument
_('--encrypt', action='store_true',
help='Specify that tunnels should be encrypted.')
_('--client-count', default=10, type=int,
help="Number of client tunnels to set up.")
_('--max-clients', type=int,
help="Maximum number of accepted clients per OpenVPN server. (default:"
" client-count * 2, which actually represents the average number"
" of tunnels to other peers)")
_('--tunnel-refresh', default=300, type=int,
help="Interval in seconds between two tunnel refresh: the worst"
" tunnel is closed if the number of client tunnels has reached"
" its maximum number (client-count).")
config = parser.parse_args()
if not version.min_protocol <= config.min_protocol <= version.protocol:
parser.error("--min-protocol: value must between %s and %s (included)"
% (version.min_protocol, version.protocol))
if config.ipv4:
ipv4, plen = config.ipv4
try:
ip, n = ipv4.split('/')
config.ipv4 = "%s/%s" % (socket.inet_ntoa(socket.inet_aton(ip)),
int(n)), int(plen)
except (socket.error, ValueError):
parser.error("invalid argument --ipv4")
utils.setupLog(config.verbose, config.logfile)
if config.max_clients is None:
config.max_clients = config.client_count * 2
server = registry.RegistryServer(config)
def requestHandler(request, client_address, _):
RequestHandler(request, client_address, server)
server_dict = {}
if config.bind4:
r = HTTPServer4((config.bind4, config.port), requestHandler)
server_dict[r.fileno()] = r._handle_request_noblock
if config.bind6:
r = HTTPServer6((config.bind6, config.port), requestHandler)
server_dict[r.fileno()] = r._handle_request_noblock
if server_dict:
while True:
args = server_dict.copy(), {}, []
server.select(*args)
utils.select(*args)
if __name__ == "__main__":
main()
re6st/cli/registry.py
\ No newline at end of file
import json, logging, os, sqlite3, socket, subprocess, time, zlib
from re6st.registry import RegistryClient
from .registry import RegistryClient
from . import utils, version, x509
class Cache(object):
......
#!/usr/bin/python
import argparse, atexit, binascii, errno, hashlib
import os, subprocess, sqlite3, sys, time
from OpenSSL import crypto
if 're6st' not in sys.modules:
sys.path[0] = os.path.dirname(os.path.dirname(sys.path[0]))
from re6st import registry, utils, x509
def create(path, text=None, mode=0666):
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, mode)
try:
os.write(fd, text)
finally:
os.close(fd)
def loadCert(pem):
return crypto.load_certificate(crypto.FILETYPE_PEM, pem)
def main():
parser = argparse.ArgumentParser(
description="Setup script for re6stnet.",
formatter_class=utils.HelpFormatter)
_ = parser.add_argument
_('--fingerprint', metavar='ALG:FINGERPRINT',
help="Check CA fingerprint to protect against MITM.")
_('--registry', required=True, metavar='URL',
help="HTTP URL of the server delivering certificates.")
_('--is-needed', action='store_true',
help="Exit immediately after asking the registry CA. Status code is"
" non-zero if we're already part of the network, which means"
" re6st is already running or we're behind a re6st router.")
_('--ca-only', action='store_true',
help='Only fetch CA from registry and exit.')
_('-d', '--dir',
help="Directory where the key and certificate will be stored.")
_('-r', '--req', nargs=2, action='append', metavar=('KEY', 'VALUE'),
help="The registry only sets the Common Name of your certificate,"
" which actually encodes your allocated prefix in the network."
" You can repeat this option to add any field you want to its"
" subject.")
_('--email',
help="Email address where your token is sent. Use -r option if you"
" want to show an email in your certificate.")
_('--token', help="The token you received.")
_('--anonymous', action='store_true',
help="Request an anonymous certificate. No email is required but the"
" registry may deliver a longer prefix.")
config = parser.parse_args()
if config.dir:
os.chdir(config.dir)
conf_path = 're6stnet.conf'
ca_path = 'ca.crt'
cert_path = 'cert.crt'
key_path = 'cert.key'
# Establish connection with server
s = registry.RegistryClient(config.registry)
# Get CA
ca = loadCert(s.getCa())
if config.fingerprint:
try:
alg, fingerprint = config.fingerprint.split(':', 1)
fingerprint = binascii.a2b_hex(fingerprint)
if hashlib.new(alg).digest_size != len(fingerprint):
raise ValueError("wrong size")
except StandardError, e:
parser.error("invalid fingerprint: %s" % e)
if x509.fingerprint(ca, alg).digest() != fingerprint:
sys.exit("CA fingerprint doesn't match")
else:
print "WARNING: it is strongly recommended to use --fingerprint option."
network = x509.networkFromCa(ca)
if config.is_needed:
route, err = subprocess.Popen(('ip', '-6', '-o', 'route', 'get',
utils.ipFromBin(network)),
stdout=subprocess.PIPE).communicate()
sys.exit(err or route and
utils.binFromIp(route.split()[8]).startswith(network))
create(ca_path, crypto.dump_certificate(crypto.FILETYPE_PEM, ca))
if config.ca_only:
sys.exit()
reserved = 'CN', 'serial'
req = crypto.X509Req()
try:
with open(cert_path) as f:
cert = loadCert(f.read())
components = dict(cert.get_subject().get_components())
for k in reserved:
components.pop(k, None)
except IOError, e:
if e.errno != errno.ENOENT:
raise
components = {}
if config.req:
components.update(config.req)
subj = req.get_subject()
for k, v in components.iteritems():
if k in reserved:
sys.exit(k + " field is reserved.")
if v:
setattr(subj, k, v)
cert_fd = token_advice = None
try:
token = config.token
if config.anonymous:
if not (token is config.email is None):
parser.error("--anonymous conflicts with --email/--token")
token = ''
elif not token:
if not config.email:
config.email = raw_input('Please enter your email address: ')
s.requestToken(config.email)
token_advice = "Use --token to retry without asking a new token\n"
while not token:
token = raw_input('Please enter your token: ')
try:
with open(key_path) as f:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
key = None
print "Reusing existing key."
except IOError, e:
if e.errno != errno.ENOENT:
raise
bits = ca.get_pubkey().bits()
print "Generating %s-bit key ..." % bits
pkey = crypto.PKey()
pkey.generate_key(crypto.TYPE_RSA, bits)
key = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
create(key_path, key, 0600)
req.set_pubkey(pkey)
req.sign(pkey, 'sha512')
req = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
# First make sure we can open certificate file for writing,
# to avoid using our token for nothing.
cert_fd = os.open(cert_path, os.O_CREAT | os.O_WRONLY, 0666)
print "Requesting certificate ..."
cert = s.requestCertificate(token, req)
if not cert:
token_advice = None
sys.exit("Error: invalid or expired token")
except:
if cert_fd is not None and not os.lseek(cert_fd, 0, os.SEEK_END):
os.remove(cert_path)
if token_advice:
atexit.register(sys.stdout.write, token_advice)
raise
os.write(cert_fd, cert)
os.ftruncate(cert_fd, len(cert))
os.close(cert_fd)
cert = loadCert(cert)
not_after = x509.notAfter(cert)
print("Setup complete. Certificate is valid until %s UTC"
" and will be automatically renewed after %s UTC.\n"
"Do not forget to backup to your private key (%s) or"
" you will lose your assigned subnet." % (
time.asctime(time.gmtime(not_after)),
time.asctime(time.gmtime(not_after - registry.RENEW_PERIOD)),
key_path))
if not os.path.lexists(conf_path):
create(conf_path, """\
registry %s
ca %s
cert %s
key %s
# increase re6stnet verbosity:
#verbose 3
# enable OpenVPN logging:
#ovpnlog
# increase OpenVPN verbosity:
#O--verb
#O3
""" % (config.registry, ca_path, cert_path, key_path))
print "Sample configuration file created."
cn = x509.subnetFromCert(cert)
subnet = network + utils.binFromSubnet(cn)
print "Your subnet: %s/%u (CN=%s)" \
% (utils.ipFromBin(subnet), len(subnet), cn)
if __name__ == "__main__":
main()
This diff is collapsed.
#!/usr/bin/python
import httplib, logging, os, socket, sys
from BaseHTTPServer import BaseHTTPRequestHandler
from SocketServer import ThreadingTCPServer
from urlparse import parse_qsl
if 're6st' not in sys.modules:
sys.path[0] = os.path.dirname(os.path.dirname(sys.path[0]))
from re6st import ctl, registry, utils, version
# To generate server ca and key with serial for 2001:db8:42::/48
# openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 -days 3650 -out ca.crt
IPV6_V6ONLY = 26
SOL_IPV6 = 41
class RequestHandler(BaseHTTPRequestHandler):
if __import__("sys").version_info < (2, 7, 4):
def address_string(self):
# Workaround for http://bugs.python.org/issue6085
return self.client_address[0]
def do_GET(self):
try:
try:
path, query = self.path.split('?', 1)
except ValueError:
path = self.path
query = {}
else:
query = dict(parse_qsl(query, keep_blank_values=1,
strict_parsing=1))
_, path = path.split('/')
if not _:
return self.server.handle_request(self, path, query)
except Exception:
logging.info(self.requestline, exc_info=1)
self.send_error(httplib.BAD_REQUEST)
def log_error(*args):
pass
class HTTPServer4(ThreadingTCPServer):
allow_reuse_address = True
daemon_threads = True
class HTTPServer6(HTTPServer4):
address_family = socket.AF_INET6
def server_bind(self):
self.socket.setsockopt(SOL_IPV6, IPV6_V6ONLY, 1)
HTTPServer4.server_bind(self)
def main():
parser = utils.ArgParser(fromfile_prefix_chars='@',
description="re6stnet registry used to bootstrap nodes"
" and deliver certificates.")
_ = parser.add_argument
_('--port', type=int, default=80,
help="Port on which the server will listen.")
_('-4', dest='bind4', default='0.0.0.0',
help="Bind server to this IPv4.")
_('-6', dest='bind6', default='::',
help="Bind server to this IPv6.")
_('--db', default='/var/lib/re6stnet/registry.db',
help="Path to SQLite database file. It is automatically initialized"
" if the file does not exist.")
_('--dh',
help='File containing Diffie-Hellman parameters in .pem format')
_('--ca', required=True, help=parser._ca_help)
_('--key', required=True,
help="CA private key in .pem format.")
_('--mailhost', required=True,
help="SMTP host to send confirmation emails. For debugging"
" purpose, it can also be an absolute or existing path to"
" a mailbox file")
_('--prefix-length', default=16, type=int,
help="Default length of allocated prefixes.")
_('--anonymous-prefix-length', type=int,
help="Length of allocated anonymous prefixes."
" If 0 or unset, registration by email is required")
_('--ipv4', nargs=2, metavar=("IP/N", "PLEN"),
help="Enable ipv4. Each node is assigned a subnet of length PLEN"
" inside network IP/N.")
_('-l', '--logfile', default='/var/log/re6stnet/registry.log',
help="Path to logging file.")
_('-r', '--run', default='/var/run/re6stnet',
help="Path to re6stnet runtime directory:\n"
"- babeld.sock (option -R of babeld)\n")
_('-v', '--verbose', default=1, type=int,
help="Log level. 0 disables logging."
" Use SIGUSR1 to reopen log.")
_('--min-protocol', default=version.min_protocol, type=int,
help="Reject nodes that are too old. Current is %s." % version.protocol)
_ = parser.add_argument_group('routing').add_argument
_('--hello', type=int, default=15,
help="Hello interval in seconds, for both wired and wireless"
" connections. OpenVPN ping-exit option is set to 4 times the"
" hello interval. It takes between 3 and 4 times the"
" hello interval for Babel to re-establish connection with a"
" node for which the direct connection has been cut.")
_ = parser.add_argument_group('tunnelling').add_argument
_('--encrypt', action='store_true',
help='Specify that tunnels should be encrypted.')
_('--client-count', default=10, type=int,
help="Number of client tunnels to set up.")
_('--max-clients', type=int,
help="Maximum number of accepted clients per OpenVPN server. (default:"
" client-count * 2, which actually represents the average number"
" of tunnels to other peers)")
_('--tunnel-refresh', default=300, type=int,