mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-31 01:19:11 +00:00 
			
		
		
		
	Add preliminary Python 2.x support
This commit is contained in:
		| @@ -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())) | ||||||
|  |  | ||||||
|  |     if config.DATABASE_POOL: | ||||||
|         sql_handler = MySQLLogHandler(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) | ||||||
|  |         if config.DATABASE_POOL: | ||||||
|             logger.addHandler(sql_handler) |             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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user