mirror of
https://github.com/laurivosandi/certidude
synced 2024-12-23 00:25:18 +00:00
Add preliminary Python 2.x support
This commit is contained in:
parent
5eed7cb6d9
commit
4240d55fe4
@ -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
|
||||||
|
|
||||||
|
@ -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":
|
||||||
|
@ -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?
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
@ -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,
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user