Add preliminary Python 2.x support

This commit is contained in:
Lauri Võsandi 2016-02-28 22:37:56 +02:00
parent 5eed7cb6d9
commit 4240d55fe4
11 changed files with 44 additions and 72 deletions

View File

@ -123,18 +123,19 @@ def certidude_app():
message = record.msg % record.args, message = record.msg % record.args,
severity = record.levelname.lower())) severity = record.levelname.lower()))
sql_handler = MySQLLogHandler(config.DATABASE_POOL) if config.DATABASE_POOL:
sql_handler = MySQLLogHandler(config.DATABASE_POOL)
push_handler = PushLogHandler() push_handler = PushLogHandler()
for facility in "api", "cli": for facility in "api", "cli":
logger = logging.getLogger(facility) logger = logging.getLogger(facility)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
logger.addHandler(sql_handler) if config.DATABASE_POOL:
logger.addHandler(sql_handler)
logger.addHandler(push_handler) logger.addHandler(push_handler)
logging.getLogger("cli").debug("Started Certidude at %s", logging.getLogger("cli").debug("Started Certidude at %s", config.FQDN)
socket.getaddrinfo(socket.gethostname(), 0, flags=socket.AI_CANONNAME)[0][3])
import atexit import atexit

View File

@ -22,7 +22,7 @@ class RequestListResource(object):
Submit certificate signing request (CSR) in PEM format Submit certificate signing request (CSR) in PEM format
""" """
# Parse remote IPv4/IPv6 address # Parse remote IPv4/IPv6 address
remote_addr = ipaddress.ip_network(req.env["REMOTE_ADDR"]) remote_addr = ipaddress.ip_network(req.env["REMOTE_ADDR"].decode("utf-8"))
# Check for CSR submission whitelist # Check for CSR submission whitelist
if config.REQUEST_SUBNETS: if config.REQUEST_SUBNETS:
@ -30,7 +30,7 @@ class RequestListResource(object):
if subnet.overlaps(remote_addr): if subnet.overlaps(remote_addr):
break break
else: else:
logger.warning("Attempted to submit signing request from non-whitelisted address %s", req.env["REMOTE_ADDR"]) logger.warning("Attempted to submit signing request from non-whitelisted address %s", remote_addr)
raise falcon.HTTPForbidden("Forbidden", "IP address %s not whitelisted" % remote_addr) raise falcon.HTTPForbidden("Forbidden", "IP address %s not whitelisted" % remote_addr)
if req.get_header("Content-Type") != "application/pkcs10": if req.get_header("Content-Type") != "application/pkcs10":

View File

@ -18,7 +18,7 @@ logger = logging.getLogger("api")
# address eg via LDAP, hence to keep things simple # address eg via LDAP, hence to keep things simple
# we simply use Kerberos to authenticate. # we simply use Kerberos to authenticate.
FQDN = socket.getaddrinfo(socket.gethostname(), 0, flags=socket.AI_CANONNAME)[0][3] FQDN = socket.getaddrinfo(socket.gethostname(), 0, socket.AF_INET, 0, 0, socket.AI_CANONNAME)[0][3]
if not os.getenv("KRB5_KTNAME"): if not os.getenv("KRB5_KTNAME"):
click.echo("Kerberos keytab not specified, set environment variable 'KRB5_KTNAME'", err=True) click.echo("Kerberos keytab not specified, set environment variable 'KRB5_KTNAME'", err=True)
@ -89,7 +89,7 @@ def authorize_admin(func):
def wrapped(self, req, resp, *args, **kwargs): def wrapped(self, req, resp, *args, **kwargs):
from certidude import config from certidude import config
# Parse remote IPv4/IPv6 address # Parse remote IPv4/IPv6 address
remote_addr = ipaddress.ip_network(req.env["REMOTE_ADDR"]) remote_addr = ipaddress.ip_network(req.env["REMOTE_ADDR"].decode("utf-8"))
# Check for administration subnet whitelist # Check for administration subnet whitelist
print("Comparing:", config.ADMIN_SUBNETS, "To:", remote_addr) print("Comparing:", config.ADMIN_SUBNETS, "To:", remote_addr)
@ -97,12 +97,12 @@ def authorize_admin(func):
if subnet.overlaps(remote_addr): if subnet.overlaps(remote_addr):
break break
else: else:
logger.info("Rejected access to administrative call %s by %s from %s, source address not whitelisted", req.env["PATH_INFO"], req.context["user"], req.env["REMOTE_ADDR"]) logger.info("Rejected access to administrative call %s by %s from %s, source address not whitelisted", req.env["PATH_INFO"], req.context["user"], remote_addr)
raise falcon.HTTPForbidden("Forbidden", "Remote address %s not whitelisted" % remote_addr) raise falcon.HTTPForbidden("Forbidden", "Remote address %s not whitelisted" % remote_addr)
# Check for username whitelist # Check for username whitelist
if req.context.get("user") not in config.ADMIN_USERS: if req.context.get("user") not in config.ADMIN_USERS:
logger.info("Rejected access to administrative call %s by %s from %s, user not whitelisted", req.env["PATH_INFO"], req.context["user"], req.env["REMOTE_ADDR"]) logger.info("Rejected access to administrative call %s by %s from %s, user not whitelisted", req.env["PATH_INFO"], req.context["user"], remote_addr)
raise falcon.HTTPForbidden("Forbidden", "User %s not whitelisted" % req.context.get("user")) raise falcon.HTTPForbidden("Forbidden", "User %s not whitelisted" % req.context.get("user"))
# Retain username, TODO: Better abstraction with username, e-mail, sn, gn? # Retain username, TODO: Better abstraction with username, e-mail, sn, gn?

View File

@ -3,7 +3,7 @@ import click
import os import os
import re import re
import socket import socket
import urllib.request import requests
from OpenSSL import crypto from OpenSSL import crypto
from certidude import config, push from certidude import config, push
from certidude.wrappers import Certificate, Request from certidude.wrappers import Certificate, Request
@ -24,12 +24,9 @@ def publish_certificate(func):
if config.PUSH_PUBLISH: if config.PUSH_PUBLISH:
url = config.PUSH_PUBLISH % csr.fingerprint() url = config.PUSH_PUBLISH % csr.fingerprint()
notification = urllib.request.Request(url, cert.dump().encode("ascii")) click.echo("Publishing certificate at %s ..." % url)
notification.add_header("User-Agent", "Certidude API") requests.post(url, data=cert.dump(),
notification.add_header("Content-Type", "application/x-x509-user-cert") headers={"User-Agent": "Certidude API", "Content-Type": "application/x-x509-user-cert"})
click.echo("Publishing certificate at %s, waiting for response..." % url)
response = urllib.request.urlopen(notification)
response.read()
push.publish("request-signed", csr.common_name) push.publish("request-signed", csr.common_name)
return cert return cert
return wrapped return wrapped
@ -146,22 +143,8 @@ def delete_request(common_name):
push.publish("request-deleted", request_sha1sum) push.publish("request-deleted", request_sha1sum)
# Write empty certificate to long-polling URL # Write empty certificate to long-polling URL
url = config.PUSH_PUBLISH % request_sha1sum requests.delete(config.PUSH_PUBLISH % request_sha1sum,
click.echo("POST-ing empty certificate at %s, waiting for response..." % url) headers={"User-Agent": "Certidude API"})
publisher = urllib.request.Request(url, b"-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n")
publisher.add_header("User-Agent", "Certidude API")
try:
response = urllib.request.urlopen(publisher)
body = response.read()
except urllib.error.HTTPError as err:
if err.code == 404:
print("No subscribers on the channel")
else:
raise
else:
print("Push server returned:", response.code, body)
@publish_certificate @publish_certificate
def sign(req, overwrite=False, delete=True): def sign(req, overwrite=False, delete=True):

View File

@ -23,7 +23,8 @@ from jinja2 import Environment, PackageLoader
from time import sleep from time import sleep
from setproctitle import setproctitle from setproctitle import setproctitle
from OpenSSL import crypto from OpenSSL import crypto
from future.standard_library import install_aliases
install_aliases()
env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True) env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True)
@ -44,7 +45,7 @@ assert hasattr(crypto.X509Req(), "get_extensions"), "You're running too old vers
# Parse command-line argument defaults from environment # Parse command-line argument defaults from environment
HOSTNAME = socket.gethostname() HOSTNAME = socket.gethostname()
FQDN = socket.getaddrinfo(HOSTNAME, 0, flags=socket.AI_CANONNAME)[0][3] FQDN = socket.getaddrinfo(HOSTNAME, 0, socket.AF_INET, 0, 0, socket.AI_CANONNAME)[0][3]
USERNAME = os.environ.get("USER") USERNAME = os.environ.get("USER")
NOW = datetime.utcnow().replace(tzinfo=None) NOW = datetime.utcnow().replace(tzinfo=None)
FIRST_NAME = None FIRST_NAME = None
@ -66,9 +67,8 @@ if os.getuid() >= 1000:
@click.option("-f", "--fork", default=False, is_flag=True, help="Fork to background") @click.option("-f", "--fork", default=False, is_flag=True, help="Fork to background")
def certidude_request_spawn(fork): def certidude_request_spawn(fork):
from certidude.helpers import certidude_request_certificate from certidude.helpers import certidude_request_certificate
from configparser import ConfigParser
clients = ConfigParser() clients = configparser.ConfigParser()
clients.readfp(open("/etc/certidude/client.conf")) clients.readfp(open("/etc/certidude/client.conf"))
services = ConfigParser() services = ConfigParser()

View File

@ -1,14 +1,18 @@
import click import click
import codecs
import configparser import configparser
import ipaddress import ipaddress
import os import os
import socket import socket
import string import string
from random import choice from random import choice
from urllib.parse import urlparse
FQDN = socket.getaddrinfo(socket.gethostname(), 0, socket.AF_INET, 0, 0, socket.AI_CANONNAME)[0][3]
cp = configparser.ConfigParser() cp = configparser.ConfigParser()
cp.read("/etc/certidude/server.conf") cp.readfp(codecs.open("/etc/certidude/server.conf", "r", "utf8"))
ADMIN_USERS = set([j for j in cp.get("authorization", "admin_users").split(" ") if j]) ADMIN_USERS = set([j for j in cp.get("authorization", "admin_users").split(" ") if j])
ADMIN_SUBNETS = set([ipaddress.ip_network(j) for j in cp.get("authorization", "admin_subnets").split(" ") if j]) ADMIN_SUBNETS = set([ipaddress.ip_network(j) for j in cp.get("authorization", "admin_subnets").split(" ") if j])
@ -43,14 +47,16 @@ try:
PUSH_LONG_POLL = cp.get("push", "long_poll") PUSH_LONG_POLL = cp.get("push", "long_poll")
PUSH_PUBLISH = cp.get("push", "publish") PUSH_PUBLISH = cp.get("push", "publish")
except configparser.NoOptionError: except configparser.NoOptionError:
PUSH_SERVER = cp.get("push", "server") PUSH_SERVER = cp.get("push", "server") or "http://localhost"
PUSH_EVENT_SOURCE = PUSH_SERVER + "/ev/%s" PUSH_EVENT_SOURCE = PUSH_SERVER + "/ev/%s"
PUSH_LONG_POLL = PUSH_SERVER + "/lp/%s" PUSH_LONG_POLL = PUSH_SERVER + "/lp/%s"
PUSH_PUBLISH = PUSH_SERVER + "/pub?id=%s" PUSH_PUBLISH = PUSH_SERVER + "/pub?id=%s"
from urllib.parse import urlparse o = urlparse(cp.get("authority", "database") if cp.has_option("authority", "database") else "")
o = urlparse(cp.get("authority", "database"))
if o.scheme == "mysql": if not o.scheme:
DATABASE_POOL = None
elif o.scheme == "mysql":
import mysql.connector import mysql.connector
DATABASE_POOL = mysql.connector.pooling.MySQLConnectionPool( DATABASE_POOL = mysql.connector.pooling.MySQLConnectionPool(
pool_size = 32, pool_size = 32,

View File

@ -1,3 +1,4 @@
# encoding: utf-8
import falcon import falcon
import ipaddress import ipaddress
@ -41,8 +42,6 @@ class MyEncoder(json.JSONEncoder):
return obj.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" return obj.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
if isinstance(obj, date): if isinstance(obj, date):
return obj.strftime("%Y-%m-%d") return obj.strftime("%Y-%m-%d")
if isinstance(obj, map):
return tuple(obj)
if isinstance(obj, types.GeneratorType): if isinstance(obj, types.GeneratorType):
return tuple(obj) return tuple(obj)
if isinstance(obj, Request): if isinstance(obj, Request):

View File

@ -2,7 +2,6 @@
import click import click
import os import os
import requests import requests
import urllib.request
from certidude import errors from certidude import errors
from certidude.wrappers import Certificate, Request from certidude.wrappers import Certificate, Request
from OpenSSL import crypto from OpenSSL import crypto
@ -123,12 +122,11 @@ def certidude_request_certificate(url, key_path, request_path, certificate_path,
return return
if submission.status_code == requests.codes.conflict: if submission.status_code == requests.codes.conflict:
raise errors.DuplicateCommonNameError("Different signing request with same CN is already present on server, server refuses to overwrite") raise errors.DuplicateCommonNameError("Different signing request with same CN is already present on server, server refuses to overwrite")
else: elif submission.status_code == requests.codes.gone:
submission.raise_for_status()
if submission.text == '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n':
# Should the client retry or disable request submission? # Should the client retry or disable request submission?
raise ValueError("Server refused to sign the request") # TODO: Raise proper exception raise ValueError("Server refused to sign the request") # TODO: Raise proper exception
else:
submission.raise_for_status()
try: try:
cert = crypto.load_certificate(crypto.FILETYPE_PEM, submission.text) cert = crypto.load_certificate(crypto.FILETYPE_PEM, submission.text)

View File

@ -1,7 +1,7 @@
import click import click
import json import json
import urllib.request import requests
from certidude import config from certidude import config
@ -13,25 +13,9 @@ def publish(event_type, event_data):
from certidude.decorators import MyEncoder from certidude.decorators import MyEncoder
event_data = json.dumps(event_data, cls=MyEncoder) event_data = json.dumps(event_data, cls=MyEncoder)
url = config.PUSH_PUBLISH % config.PUSH_TOKEN notification = requests.post(
click.echo("Posting event %s %s at %s, waiting for response..." % (repr(event_type), repr(event_data), repr(url))) config.PUSH_PUBLISH % config.PUSH_TOKEN,
notification = urllib.request.Request( data=event_data,
url, headers={"X-EventSource-Event": event_type, "User-Agent": "Certidude API"})
event_data.encode("utf-8"),
{"X-EventSource-Event":event_type.encode("ascii")})
notification.add_header("User-Agent", "Certidude API")
try:
response = urllib.request.urlopen(notification)
body = response.read()
except urllib.error.HTTPError as err:
if err.code == 404:
print("No subscribers on the channel")
else:
print("Failed to submit event, %s" % err)
else:
print("Push server returned:", response.code, body)
response.close()

View File

@ -199,7 +199,7 @@ class Request(CertificateBase):
self.path = NotImplemented self.path = NotImplemented
self.created = NotImplemented self.created = NotImplemented
if isinstance(mixed, io.TextIOWrapper): if isinstance(mixed, file):
self.path = mixed.name self.path = mixed.name
_, _, _, _, _, _, _, _, mtime, _ = os.stat(self.path) _, _, _, _, _, _, _, _, mtime, _ = os.stat(self.path)
self.created = datetime.fromtimestamp(mtime) self.created = datetime.fromtimestamp(mtime)
@ -248,7 +248,7 @@ class Certificate(CertificateBase):
self.path = NotImplemented self.path = NotImplemented
self.changed = NotImplemented self.changed = NotImplemented
if isinstance(mixed, io.TextIOWrapper): if isinstance(mixed, file):
self.path = mixed.name self.path = mixed.name
_, _, _, _, _, _, _, _, mtime, _ = os.stat(self.path) _, _, _, _, _, _, _, _, mtime, _ = os.stat(self.path)
self.changed = datetime.fromtimestamp(mtime) self.changed = datetime.fromtimestamp(mtime)

View File

@ -2,6 +2,7 @@ cffi==1.2.1
click==5.1 click==5.1
cryptography==1.0 cryptography==1.0
falcon==0.3.0 falcon==0.3.0
future=0.15.2
humanize==0.5.1 humanize==0.5.1
idna==2.0 idna==2.0
ipsecparse==0.1.0 ipsecparse==0.1.0