diff --git a/docs/faq.rst b/docs/faq.rst index 4354ab9e..a495918e 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -14,6 +14,22 @@ I am seeing Lemur's javascript load in my browser but not the CSS. :doc:`production/index` for example configurations. +Running 'lemur db upgrade' seems stuck. + Most likely, the upgrade is stuck because an existing query on the database is holding onto a lock that the + migration needs. + + To resolve, login to your lemur database and run: + + SELECT * FROM pg_locks l INNER JOIN pg_stat_activity s ON (l.pid = s.pid) WHERE waiting AND NOT granted; + + This will give you a list of queries that are currently waiting to be executed. From there attempt to idenity the PID + of the query blocking the migration. Once found execute: + + select pg_terminate_backend(); + + See ``_ for more. + + How do I -------- diff --git a/lemur/authorities/models.py b/lemur/authorities/models.py index 011acf2d..0dc5420b 100644 --- a/lemur/authorities/models.py +++ b/lemur/authorities/models.py @@ -14,7 +14,7 @@ from sqlalchemy import Column, Integer, String, Text, func, ForeignKey, DateTime from sqlalchemy.dialects.postgresql import JSON from lemur.database import db -from lemur.certificates.models import cert_get_cn, cert_get_not_after, cert_get_not_before +from lemur.certificates.models import get_cn, get_not_after, get_not_before class Authority(db.Model): @@ -44,9 +44,9 @@ class Authority(db.Model): self.owner = owner self.plugin_name = plugin_name cert = x509.load_pem_x509_certificate(str(body), default_backend()) - self.cn = cert_get_cn(cert) - self.not_before = cert_get_not_before(cert) - self.not_after = cert_get_not_after(cert) + self.cn = get_cn(cert) + self.not_before = get_not_before(cert) + self.not_after = get_not_after(cert) self.roles = roles self.description = description diff --git a/lemur/certificates/models.py b/lemur/certificates/models.py index 5fb6f6e1..2916b730 100644 --- a/lemur/certificates/models.py +++ b/lemur/certificates/models.py @@ -63,7 +63,11 @@ def create_name(issuer, not_before, not_after, subject, san): return temp.replace(" ", "-") -def cert_get_cn(cert): +def get_signing_algorithm(cert): + return cert.signature_hash_algorithm.name + + +def get_cn(cert): """ Attempts to get a sane common name from a given certificate. @@ -75,7 +79,7 @@ def cert_get_cn(cert): )[0].value.strip() -def cert_get_domains(cert): +def get_domains(cert): """ Attempts to get an domains listed in a certificate. If 'subjectAltName' extension is not available we simply @@ -96,7 +100,7 @@ def cert_get_domains(cert): return domains -def cert_get_serial(cert): +def get_serial(cert): """ Fetch the serial number from the certificate. @@ -106,7 +110,7 @@ def cert_get_serial(cert): return cert.serial -def cert_is_san(cert): +def is_san(cert): """ Determines if a given certificate is a SAN certificate. SAN certificates are simply certificates that cover multiple domains. @@ -114,18 +118,18 @@ def cert_is_san(cert): :param cert: :return: Bool """ - if len(cert_get_domains(cert)) > 1: + if len(get_domains(cert)) > 1: return True -def cert_is_wildcard(cert): +def is_wildcard(cert): """ Determines if certificate is a wildcard certificate. :param cert: :return: Bool """ - domains = cert_get_domains(cert) + domains = get_domains(cert) if len(domains) == 1 and domains[0][0:1] == "*": return True @@ -133,7 +137,7 @@ def cert_is_wildcard(cert): return True -def cert_get_bitstrength(cert): +def get_bitstrength(cert): """ Calculates a certificates public key bit length. @@ -143,7 +147,7 @@ def cert_get_bitstrength(cert): return cert.public_key().key_size -def cert_get_issuer(cert): +def get_issuer(cert): """ Gets a sane issuer from a given certificate. @@ -160,7 +164,7 @@ def cert_get_issuer(cert): current_app.logger.error("Unable to get issuer! {0}".format(e)) -def cert_get_not_before(cert): +def get_not_before(cert): """ Gets the naive datetime of the certificates 'not_before' field. This field denotes the first date in time which the given certificate @@ -172,7 +176,7 @@ def cert_get_not_before(cert): return cert.not_valid_before -def cert_get_not_after(cert): +def get_not_after(cert): """ Gets the naive datetime of the certificates 'not_after' field. This field denotes the last date in time which the given certificate @@ -224,6 +228,7 @@ class Certificate(db.Model): not_before = Column(DateTime) not_after = Column(DateTime) date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False) + signing_algorithm = Column(String(128)) user_id = Column(Integer, ForeignKey('users.id')) authority_id = Column(Integer, ForeignKey('authorities.id')) notifications = relationship("Notification", secondary=certificate_notification_associations, backref='certificate') @@ -237,16 +242,17 @@ class Certificate(db.Model): self.private_key = private_key self.chain = chain cert = x509.load_pem_x509_certificate(str(self.body), default_backend()) - self.bits = cert_get_bitstrength(cert) - self.issuer = cert_get_issuer(cert) - self.serial = cert_get_serial(cert) - self.cn = cert_get_cn(cert) - self.san = cert_is_san(cert) - self.not_before = cert_get_not_before(cert) - self.not_after = cert_get_not_after(cert) + self.signing_algorithm = get_signing_algorithm(cert) + self.bits = get_bitstrength(cert) + self.issuer = get_issuer(cert) + self.serial = get_serial(cert) + self.cn = get_cn(cert) + self.san = is_san(cert) + self.not_before = get_not_before(cert) + self.not_after = get_not_after(cert) self.name = create_name(self.issuer, self.not_before, self.not_after, self.cn, self.san) - for domain in cert_get_domains(cert): + for domain in get_domains(cert): self.domains.append(Domain(name=domain)) @property diff --git a/lemur/certificates/views.py b/lemur/certificates/views.py index 3595d2b3..8918b5a0 100644 --- a/lemur/certificates/views.py +++ b/lemur/certificates/views.py @@ -46,6 +46,7 @@ FIELDS = { 'notBefore': fields.DateTime(dt_format='iso8601', attribute='not_before'), 'notAfter': fields.DateTime(dt_format='iso8601', attribute='not_after'), 'cn': fields.String, + 'signingAlgorithm': fields.String(attribute='signing_algorithm'), 'status': fields.String, 'body': fields.String } @@ -400,6 +401,7 @@ class CertificatesUpload(AuthenticatedResource): "active": true, "notBefore": "2015-06-05T17:09:39", "notAfter": "2015-06-10T17:09:39", + "signingAlgorithm": "sha2" "cn": "example.com", "status": "unknown" } @@ -543,6 +545,7 @@ class Certificates(AuthenticatedResource): "active": true, "notBefore": "2015-06-05T17:09:39", "notAfter": "2015-06-10T17:09:39", + "signingAlgorithm": "sha2", "cn": "example.com", "status": "unknown" } @@ -677,6 +680,7 @@ class NotificationCertificatesList(AuthenticatedResource): "active": true, "notBefore": "2015-06-05T17:09:39", "notAfter": "2015-06-10T17:09:39", + "signingAlgorithm": "sha2", "cn": "example.com", "status": "unknown" } diff --git a/lemur/manage.py b/lemur/manage.py index d66d004c..7624bbca 100755 --- a/lemur/manage.py +++ b/lemur/manage.py @@ -716,6 +716,24 @@ def publish_verisign_units(): requests.post('http://localhost:8078/metrics', data=json.dumps(metric)) +@manager.command +def backfill_signing_algo(): + """ + Will attempt to backfill the signing_algorithm column + + :return: + """ + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + from lemur.certificates.models import get_signing_algorithm + for c in cert_service.get_all_certs(): + cert = x509.load_pem_x509_certificate(str(c.body), default_backend()) + c.signing_algorithm = get_signing_algorithm(cert) + c.signing_algorithm + database.update(c) + print c.signing_algorithm + + def main(): manager.add_command("start", LemurServer()) manager.add_command("runserver", Server(host='127.0.0.1')) diff --git a/lemur/migrations/versions/4bcfa2c36623_.py b/lemur/migrations/versions/4bcfa2c36623_.py new file mode 100644 index 00000000..b7917f8f --- /dev/null +++ b/lemur/migrations/versions/4bcfa2c36623_.py @@ -0,0 +1,26 @@ +"""Adding certificate signing algorithm + +Revision ID: 4bcfa2c36623 +Revises: 1ff763f5b80b +Create Date: 2015-10-06 10:03:47.993204 + +""" + +# revision identifiers, used by Alembic. +revision = '4bcfa2c36623' +down_revision = '1ff763f5b80b' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('certificates', sa.Column('signing_algorithm', sa.String(length=128), nullable=True)) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_column('certificates', 'signing_algorithm') + ### end Alembic commands ### diff --git a/lemur/static/app/angular/certificates/view/view.tpl.html b/lemur/static/app/angular/certificates/view/view.tpl.html index 26aa2657..9d0f726f 100644 --- a/lemur/static/app/angular/certificates/view/view.tpl.html +++ b/lemur/static/app/angular/certificates/view/view.tpl.html @@ -89,6 +89,10 @@ Bits {{ certificate.bits }} +
  • + Signing Algorithm + {{ certificate.signingAlgorithm }} +
  • Serial {{ certificate.serial }} diff --git a/lemur/static/app/angular/dashboard/dashboard.js b/lemur/static/app/angular/dashboard/dashboard.js index a9a05ce7..0923b6c4 100644 --- a/lemur/static/app/angular/dashboard/dashboard.js +++ b/lemur/static/app/angular/dashboard/dashboard.js @@ -79,6 +79,11 @@ angular.module('lemur') $scope.bits = data.items; }); + LemurRestangular.all('certificates').customGET('stats', {metric: 'signing_algorithm'}) + .then(function (data) { + $scope.algos = data.items; + }); + LemurRestangular.all('certificates').customGET('stats', {metric: 'not_after'}) .then(function (data) { $scope.expiring = {labels: data.items.labels, values: [data.items.values]}; diff --git a/lemur/static/app/angular/dashboard/dashboard.tpl.html b/lemur/static/app/angular/dashboard/dashboard.tpl.html index 22baec24..1b439df4 100644 --- a/lemur/static/app/angular/dashboard/dashboard.tpl.html +++ b/lemur/static/app/angular/dashboard/dashboard.tpl.html @@ -23,8 +23,7 @@

    Issuers

    - +
    @@ -34,8 +33,7 @@

    Bit Strength

    - +
    @@ -47,7 +45,18 @@
    + colours="colours"> +
    + + +
    +
    +
    +

    Signing Algorithms

    +
    +
    +