class Endpoint(db.Model): __tablename__ = 'endpoints' id = Column(Integer, primary_key=True) owner = Column(String(128)) name = Column(String(128)) dnsname = Column(String(256)) type = Column(String(128)) active = Column(Boolean, default=True) port = Column(Integer) policy_id = Column(Integer, ForeignKey('policy.id')) policy = relationship('Policy', backref='endpoint') certificate_id = Column(Integer, ForeignKey('certificates.id')) source_id = Column(Integer, ForeignKey('sources.id')) sensitive = Column(Boolean, default=False) source = relationship('Source', back_populates='endpoints') last_updated = Column(DateTime, PassiveDefault(func.now()), onupdate=func.now(), nullable=False) date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False) @property def issues(self): issues = [] for cipher in self.policy.ciphers: if cipher.deprecated: issues.append({ 'name': 'deprecated cipher', 'value': '{0} has been deprecated consider removing it.'.format( cipher.name) }) if self.certificate.expired: issues.append({ 'name': 'expired certificate', 'value': 'There is an expired certificate attached to this endpoint consider replacing it.' }) if self.certificate.revoked: issues.append({ 'name': 'revoked', 'value': 'There is a revoked certificate attached to this endpoint consider replacing it.' }) return issues def __repr__(self): return "Endpoint(name={name})".format(name=self.name)
class Log(db.Model): __tablename__ = 'logs' id = Column(Integer, primary_key=True) certificate_id = Column(Integer, ForeignKey('certificates.id')) log_type = Column(Enum('key_view', 'create_cert', 'update_cert', 'revoke_cert', 'delete_cert', name='log_type'), nullable=False) logged_at = Column(ArrowType(), PassiveDefault(func.now()), nullable=False) user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
class Authority(db.Model): __tablename__ = "authorities" id = Column(Integer, primary_key=True) owner = Column(String(128), nullable=False) name = Column(String(128), unique=True) body = Column(Text()) chain = Column(Text()) active = Column(Boolean, default=True) plugin_name = Column(String(64)) description = Column(Text) options = Column(JSON) date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False) roles = relationship( "Role", secondary=roles_authorities, passive_deletes=True, backref=db.backref("authority"), lazy="dynamic", ) user_id = Column(Integer, ForeignKey("users.id")) authority_certificate = relationship( "Certificate", backref="root_authority", uselist=False, foreign_keys="Certificate.root_authority_id", ) certificates = relationship("Certificate", backref="authority", foreign_keys="Certificate.authority_id") authority_pending_certificate = relationship( "PendingCertificate", backref="root_authority", uselist=False, foreign_keys="PendingCertificate.root_authority_id", ) pending_certificates = relationship( "PendingCertificate", backref="authority", foreign_keys="PendingCertificate.authority_id", ) def __init__(self, **kwargs): self.owner = kwargs["owner"] self.roles = kwargs.get("roles", []) self.name = kwargs.get("name") self.description = kwargs.get("description") self.authority_certificate = kwargs["authority_certificate"] self.plugin_name = kwargs["plugin"]["slug"] self.options = kwargs.get("options") @property def plugin(self): return plugins.get(self.plugin_name) def __repr__(self): return "Authority(name={name})".format(name=self.name)
class Authority(db.Model): __tablename__ = 'authorities' id = Column(Integer, primary_key=True) owner = Column(String(128), nullable=False) name = Column(String(128), unique=True) body = Column(Text()) chain = Column(Text()) active = Column(Boolean, default=True) plugin_name = Column(String(64)) description = Column(Text) options = Column(JSON) date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False) roles = relationship('Role', secondary=roles_authorities, passive_deletes=True, backref=db.backref('authority'), lazy='dynamic') user_id = Column(Integer, ForeignKey('users.id')) authority_certificate = relationship( "Certificate", backref='root_authority', uselist=False, foreign_keys='Certificate.root_authority_id') certificates = relationship("Certificate", backref='authority', foreign_keys='Certificate.authority_id') authority_pending_certificate = relationship( "PendingCertificate", backref='root_authority', uselist=False, foreign_keys='PendingCertificate.root_authority_id') pending_certificates = relationship( 'PendingCertificate', backref='authority', foreign_keys='PendingCertificate.authority_id') def __init__(self, **kwargs): self.owner = kwargs['owner'] self.roles = kwargs.get('roles', []) self.name = kwargs.get('name') self.description = kwargs.get('description') self.authority_certificate = kwargs['authority_certificate'] self.plugin_name = kwargs['plugin']['slug'] self.options = kwargs.get('options') @property def plugin(self): return plugins.get(self.plugin_name) def __repr__(self): return "Authority(name={name})".format(name=self.name)
class Authority(db.Model): __tablename__ = 'authorities' id = Column(Integer, primary_key=True) owner = Column(String(128)) name = Column(String(128), unique=True) body = Column(Text()) chain = Column(Text()) bits = Column(Integer()) cn = Column(String(128)) not_before = Column(DateTime) not_after = Column(DateTime) active = Column(Boolean, default=True) date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False) plugin_name = Column(String(64)) description = Column(Text) options = Column(JSON) roles = relationship('Role', backref=db.backref('authority'), lazy='dynamic') user_id = Column(Integer, ForeignKey('users.id')) certificates = relationship("Certificate", backref='authority') def __init__(self, name, owner, plugin_name, body, roles=None, chain=None, description=None): self.name = name self.body = body self.chain = chain self.owner = owner self.plugin_name = plugin_name cert = x509.load_pem_x509_certificate(str(body), default_backend()) 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 def as_dict(self): return {c.name: getattr(self, c.name) for c in self.__table__.columns} def serialize(self): blob = self.as_dict() return blob
class Log(db.Model): __tablename__ = "logs" id = Column(Integer, primary_key=True) certificate_id = Column(Integer, ForeignKey("certificates.id")) log_type = Column( Enum( "key_view", "create_cert", "update_cert", "revoke_cert", "delete_cert", name="log_type", ), nullable=False, ) logged_at = Column(ArrowType(), PassiveDefault(func.now()), nullable=False) user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
class Certificate(db.Model): __tablename__ = 'certificates' id = Column(Integer, primary_key=True) external_id = Column(String(128)) owner = Column(String(128), nullable=False) name = Column(String(256), unique=True) description = Column(String(1024)) notify = Column(Boolean, default=True) body = Column(Text(), nullable=False) chain = Column(Text()) private_key = Column(Vault) issuer = Column(String(128)) serial = Column(String(128)) cn = Column(String(128)) deleted = Column(Boolean, index=True) not_before = Column(ArrowType) not_after = Column(ArrowType) date_created = Column(ArrowType, PassiveDefault(func.now()), nullable=False) signing_algorithm = Column(String(128)) status = Column(String(128)) bits = Column(Integer()) san = Column(String(1024)) # TODO this should be migrated to boolean rotation = Column(Boolean, default=False) user_id = Column(Integer, ForeignKey('users.id')) authority_id = Column(Integer, ForeignKey('authorities.id', ondelete="CASCADE")) root_authority_id = Column(Integer, ForeignKey('authorities.id', ondelete="CASCADE")) rotation_policy_id = Column(Integer, ForeignKey('rotation_policies.id')) notifications = relationship('Notification', secondary=certificate_notification_associations, backref='certificate') destinations = relationship('Destination', secondary=certificate_destination_associations, backref='certificate') sources = relationship('Source', secondary=certificate_source_associations, backref='certificate') domains = relationship('Domain', secondary=certificate_associations, backref='certificate') roles = relationship('Role', secondary=roles_certificates, backref='certificate') replaces = relationship('Certificate', secondary=certificate_replacement_associations, primaryjoin=id == certificate_replacement_associations.c.certificate_id, # noqa secondaryjoin=id == certificate_replacement_associations.c.replaced_certificate_id, # noqa backref='replaced') logs = relationship('Log', backref='certificate') endpoints = relationship('Endpoint', backref='certificate') rotation_policy = relationship("RotationPolicy") sensitive_fields = ('private_key',) def __init__(self, **kwargs): cert = lemur.common.utils.parse_certificate(kwargs['body']) self.issuer = defaults.issuer(cert) self.cn = defaults.common_name(cert) self.san = defaults.san(cert) self.not_before = defaults.not_before(cert) self.not_after = defaults.not_after(cert) self.serial = defaults.serial(cert) # when destinations are appended they require a valid name. if kwargs.get('name'): self.name = get_or_increase_name(defaults.text_to_slug(kwargs['name']), self.serial) else: self.name = get_or_increase_name( defaults.certificate_name(self.cn, self.issuer, self.not_before, self.not_after, self.san), self.serial) self.owner = kwargs['owner'] self.body = kwargs['body'].strip() if kwargs.get('private_key'): self.private_key = kwargs['private_key'].strip() if kwargs.get('chain'): self.chain = kwargs['chain'].strip() self.notify = kwargs.get('notify', True) self.destinations = kwargs.get('destinations', []) self.notifications = kwargs.get('notifications', []) self.description = kwargs.get('description') self.roles = list(set(kwargs.get('roles', []))) self.replaces = kwargs.get('replaces', []) self.rotation = kwargs.get('rotation') self.rotation_policy = kwargs.get('rotation_policy') self.signing_algorithm = defaults.signing_algorithm(cert) self.bits = defaults.bitstrength(cert) self.external_id = kwargs.get('external_id') for domain in defaults.domains(cert): self.domains.append(Domain(name=domain)) @property def active(self): return self.notify @property def organization(self): cert = lemur.common.utils.parse_certificate(self.body) return defaults.organization(cert) @property def organizational_unit(self): cert = lemur.common.utils.parse_certificate(self.body) return defaults.organizational_unit(cert) @property def country(self): cert = lemur.common.utils.parse_certificate(self.body) return defaults.country(cert) @property def state(self): cert = lemur.common.utils.parse_certificate(self.body) return defaults.state(cert) @property def location(self): cert = lemur.common.utils.parse_certificate(self.body) return defaults.location(cert) @property def key_type(self): cert = lemur.common.utils.parse_certificate(self.body) if isinstance(cert.public_key(), rsa.RSAPublicKey): return 'RSA{key_size}'.format(key_size=cert.public_key().key_size) @property def validity_remaining(self): return abs(self.not_after - arrow.utcnow()) @property def validity_range(self): return self.not_after - self.not_before @property def subject(self): cert = lemur.common.utils.parse_certificate(self.body) return cert.subject @property def public_key(self): cert = lemur.common.utils.parse_certificate(self.body) return cert.public_key() @hybrid_property def expired(self): if self.not_after <= arrow.utcnow(): return True @expired.expression def expired(cls): return case( [ (cls.not_after <= arrow.utcnow(), True) ], else_=False ) @hybrid_property def revoked(self): if 'revoked' == self.status: return True @revoked.expression def revoked(cls): return case( [ (cls.status == 'revoked', True) ], else_=False ) @hybrid_property def in_rotation_window(self): """ Determines if a certificate is available for rotation based on the rotation policy associated. :return: """ now = arrow.utcnow() end = now + timedelta(days=self.rotation_policy.days) if self.not_after <= end: return True @in_rotation_window.expression def in_rotation_window(cls): """ Determines if a certificate is available for rotation based on the rotation policy associated. :return: """ return case( [ (extract('day', cls.not_after - func.now()) <= RotationPolicy.days, True) ], else_=False ) @property def extensions(self): # setup default values return_extensions = { 'sub_alt_names': {'names': []} } try: cert = lemur.common.utils.parse_certificate(self.body) for extension in cert.extensions: value = extension.value if isinstance(value, x509.BasicConstraints): return_extensions['basic_constraints'] = value elif isinstance(value, x509.SubjectAlternativeName): return_extensions['sub_alt_names']['names'] = value elif isinstance(value, x509.ExtendedKeyUsage): return_extensions['extended_key_usage'] = value elif isinstance(value, x509.KeyUsage): return_extensions['key_usage'] = value elif isinstance(value, x509.SubjectKeyIdentifier): return_extensions['subject_key_identifier'] = {'include_ski': True} elif isinstance(value, x509.AuthorityInformationAccess): return_extensions['certificate_info_access'] = {'include_aia': True} elif isinstance(value, x509.AuthorityKeyIdentifier): aki = { 'use_key_identifier': False, 'use_authority_cert': False } if value.key_identifier: aki['use_key_identifier'] = True if value.authority_cert_issuer: aki['use_authority_cert'] = True return_extensions['authority_key_identifier'] = aki # TODO: Don't support CRLDistributionPoints yet https://github.com/Netflix/lemur/issues/662 elif isinstance(value, x509.CRLDistributionPoints): current_app.logger.warning('CRLDistributionPoints not yet supported for clone operation.') # TODO: Not supporting custom OIDs yet. https://github.com/Netflix/lemur/issues/665 else: current_app.logger.warning('Custom OIDs not yet supported for clone operation.') except InvalidCodepoint as e: sentry.captureException() current_app.logger.warning('Unable to parse extensions due to underscore in dns name') except ValueError as e: sentry.captureException() current_app.logger.warning('Unable to parse') current_app.logger.exception(e) return return_extensions def __repr__(self): return "Certificate(name={name})".format(name=self.name)
class PendingCertificate(db.Model): __tablename__ = 'pending_certs' id = Column(Integer, primary_key=True) external_id = Column(String(128)) owner = Column(String(128), nullable=False) name = Column(String(256), unique=True) description = Column(String(1024)) notify = Column(Boolean, default=True) number_attempts = Column(Integer) rename = Column(Boolean, default=True) resolved = Column(Boolean, default=False) resolved_cert_id = Column(Integer, nullable=True) cn = Column(String(128)) csr = Column(Text(), nullable=False) chain = Column(Text()) private_key = Column(Vault, nullable=True) date_created = Column(ArrowType, PassiveDefault(func.now()), nullable=False) dns_provider_id = Column(Integer, ForeignKey('dns_providers.id', ondelete="CASCADE")) status = Column(Text(), nullable=True) last_updated = Column(ArrowType, PassiveDefault(func.now()), onupdate=func.now(), nullable=False) rotation = Column(Boolean, default=False) user_id = Column(Integer, ForeignKey('users.id')) authority_id = Column(Integer, ForeignKey('authorities.id', ondelete="CASCADE")) root_authority_id = Column(Integer, ForeignKey('authorities.id', ondelete="CASCADE")) rotation_policy_id = Column(Integer, ForeignKey('rotation_policies.id')) notifications = relationship('Notification', secondary=pending_cert_notification_associations, backref='pending_cert', passive_deletes=True) destinations = relationship('Destination', secondary=pending_cert_destination_associations, backref='pending_cert', passive_deletes=True) sources = relationship('Source', secondary=pending_cert_source_associations, backref='pending_cert', passive_deletes=True) roles = relationship('Role', secondary=pending_cert_role_associations, backref='pending_cert', passive_deletes=True) replaces = relationship('Certificate', secondary=pending_cert_replacement_associations, backref='pending_cert', passive_deletes=True) options = Column(JSONType) rotation_policy = relationship("RotationPolicy") sensitive_fields = ('private_key',) def __init__(self, **kwargs): self.csr = kwargs.get('csr') self.private_key = kwargs.get('private_key', "") if self.private_key: # If the request does not send private key, the key exists but the value is None self.private_key = self.private_key.strip() self.external_id = kwargs.get('external_id') # when destinations are appended they require a valid name. if kwargs.get('name'): self.name = get_or_increase_name(defaults.text_to_slug(kwargs['name']), 0) self.rename = False else: # TODO: Fix auto-generated name, it should be renamed on creation self.name = get_or_increase_name( defaults.certificate_name(kwargs['common_name'], kwargs['authority'].name, dt.now(), dt.now(), False), self.external_id) self.rename = True self.cn = defaults.common_name(utils.parse_csr(self.csr)) self.owner = kwargs['owner'] self.number_attempts = 0 if kwargs.get('chain'): self.chain = kwargs['chain'].strip() self.notify = kwargs.get('notify', True) self.destinations = kwargs.get('destinations', []) self.notifications = kwargs.get('notifications', []) self.description = kwargs.get('description') self.roles = list(set(kwargs.get('roles', []))) self.replaces = kwargs.get('replaces', []) self.rotation = kwargs.get('rotation') self.rotation_policy = kwargs.get('rotation_policy') try: self.dns_provider_id = kwargs.get('dns_provider').id except (AttributeError, KeyError, TypeError, Exception): pass
def reflecttable(self, connection, table, include_columns): preparer = self.identifier_preparer if table.schema is None: pragma = "PRAGMA " else: pragma = "PRAGMA %s." % preparer.quote_identifier(table.schema) qtable = preparer.format_table(table, False) c = connection.execute("%stable_info(%s)" % (pragma, qtable)) found_table = False while True: row = c.fetchone() if row is None: break found_table = True (name, type_, nullable, has_default, primary_key) = (row[1], row[2].upper(), not row[3], row[4] is not None, row[5]) name = re.sub(r'^\"|\"$', '', name) if include_columns and name not in include_columns: continue match = re.match(r'(\w+)(\(.*?\))?', type_) if match: coltype = match.group(1) args = match.group(2) else: coltype = "VARCHAR" args = '' try: coltype = ischema_names[coltype] except KeyError: util.warn("Did not recognize type '%s' of column '%s'" % (coltype, name)) coltype = sqltypes.NullType if args is not None: args = re.findall(r'(\d+)', args) coltype = coltype(*[int(a) for a in args]) colargs = [] if has_default: colargs.append(PassiveDefault('?')) table.append_column( schema.Column(name, coltype, primary_key=primary_key, nullable=nullable, *colargs)) if not found_table: raise exceptions.NoSuchTableError(table.name) c = connection.execute("%sforeign_key_list(%s)" % (pragma, qtable)) fks = {} while True: row = c.fetchone() if row is None: break (constraint_name, tablename, localcol, remotecol) = (row[0], row[2], row[3], row[4]) tablename = re.sub(r'^\"|\"$', '', tablename) localcol = re.sub(r'^\"|\"$', '', localcol) remotecol = re.sub(r'^\"|\"$', '', remotecol) try: fk = fks[constraint_name] except KeyError: fk = ([], []) fks[constraint_name] = fk # look up the table based on the given table's engine, not 'self', # since it could be a ProxyEngine remotetable = schema.Table(tablename, table.metadata, autoload=True, autoload_with=connection) constrained_column = table.c[localcol].name refspec = ".".join([tablename, remotecol]) if constrained_column not in fk[0]: fk[0].append(constrained_column) if refspec not in fk[1]: fk[1].append(refspec) for name, value in fks.iteritems(): table.append_constraint( schema.ForeignKeyConstraint(value[0], value[1])) # check for UNIQUE indexes c = connection.execute("%sindex_list(%s)" % (pragma, qtable)) unique_indexes = [] while True: row = c.fetchone() if row is None: break if (row[2] == 1): unique_indexes.append(row[1]) # loop thru unique indexes for one that includes the primary key for idx in unique_indexes: c = connection.execute("%sindex_info(%s)" % (pragma, idx)) cols = [] while True: row = c.fetchone() if row is None: break cols.append(row[2])
class Certificate(db.Model): __tablename__ = 'certificates' id = Column(Integer, primary_key=True) owner = Column(String(128), nullable=False) name = Column(String(128), unique=True) description = Column(String(1024)) notify = Column(Boolean, default=True) body = Column(Text(), nullable=False) chain = Column(Text()) private_key = Column(Vault) issuer = Column(String(128)) serial = Column(String(128)) cn = Column(String(128)) deleted = Column(Boolean, index=True) not_before = Column(ArrowType) not_after = Column(ArrowType) date_created = Column(ArrowType, PassiveDefault(func.now()), nullable=False) signing_algorithm = Column(String(128)) status = Column(String(128)) bits = Column(Integer()) san = Column(String(1024)) # TODO this should be migrated to boolean user_id = Column(Integer, ForeignKey('users.id')) authority_id = Column(Integer, ForeignKey('authorities.id', ondelete="CASCADE")) root_authority_id = Column( Integer, ForeignKey('authorities.id', ondelete="CASCADE")) notifications = relationship( 'Notification', secondary=certificate_notification_associations, backref='certificate') destinations = relationship('Destination', secondary=certificate_destination_associations, backref='certificate') sources = relationship('Source', secondary=certificate_source_associations, backref='certificate') domains = relationship('Domain', secondary=certificate_associations, backref='certificate') roles = relationship('Role', secondary=roles_certificates, backref='certificate') replaces = relationship( 'Certificate', secondary=certificate_replacement_associations, primaryjoin=id == certificate_replacement_associations.c.certificate_id, # noqa secondaryjoin=id == certificate_replacement_associations.c.replaced_certificate_id, # noqa backref='replaced') logs = relationship('Log', backref='certificate') endpoints = relationship('Endpoint', backref='certificate') def __init__(self, **kwargs): cert = lemur.common.utils.parse_certificate(kwargs['body']) self.issuer = defaults.issuer(cert) self.cn = defaults.common_name(cert) self.san = defaults.san(cert) self.not_before = defaults.not_before(cert) self.not_after = defaults.not_after(cert) # when destinations are appended they require a valid name. if kwargs.get('name'): self.name = get_or_increase_name(kwargs['name']) else: self.name = get_or_increase_name( defaults.certificate_name(self.cn, self.issuer, self.not_before, self.not_after, self.san)) self.owner = kwargs['owner'] self.body = kwargs['body'].strip() if kwargs.get('private_key'): self.private_key = kwargs['private_key'].strip() if kwargs.get('chain'): self.chain = kwargs['chain'].strip() self.notify = kwargs.get('notify', True) self.destinations = kwargs.get('destinations', []) self.notifications = kwargs.get('notifications', []) self.description = kwargs.get('description') self.roles = list(set(kwargs.get('roles', []))) self.replaces = kwargs.get('replacements', []) self.signing_algorithm = defaults.signing_algorithm(cert) self.bits = defaults.bitstrength(cert) self.serial = defaults.serial(cert) for domain in defaults.domains(cert): self.domains.append(Domain(name=domain)) @property def active(self): return self.notify @property def organization(self): cert = lemur.common.utils.parse_certificate(self.body) return defaults.organization(cert) @property def organizational_unit(self): cert = lemur.common.utils.parse_certificate(self.body) return defaults.organizational_unit(cert) @property def country(self): cert = lemur.common.utils.parse_certificate(self.body) return defaults.country(cert) @property def state(self): cert = lemur.common.utils.parse_certificate(self.body) return defaults.state(cert) @property def location(self): cert = lemur.common.utils.parse_certificate(self.body) return defaults.location(cert) @property def key_type(self): cert = lemur.common.utils.parse_certificate(self.body) if isinstance(cert.public_key(), rsa.RSAPublicKey): return 'RSA{key_size}'.format(key_size=cert.public_key().key_size) @hybrid_property def expired(self): if self.not_after <= arrow.utcnow(): return True @expired.expression def expired(cls): return case([(cls.now_after <= arrow.utcnow(), True)], else_=False) @hybrid_property def revoked(self): if 'revoked' == self.status: return True @revoked.expression def revoked(cls): return case([(cls.status == 'revoked', True)], else_=False) @property def extensions(self): # TODO pull the OU, O, CN, etc + other extensions. names = [{ 'name_type': 'DNSName', 'value': x.name } for x in self.domains] extensions = {'sub_alt_names': {'names': names}} return extensions def get_arn(self, account_number): """ Generate a valid AWS IAM arn :rtype : str :param account_number: :return: """ return "arn:aws:iam::{}:server-certificate/{}".format( account_number, self.name) def __repr__(self): return "Certificate(name={name})".format(name=self.name)
class Certificate(db.Model): __tablename__ = 'certificates' id = Column(Integer, primary_key=True) owner = Column(String(128), nullable=False) name = Column(String(128), unique=True) description = Column(String(1024)) active = Column(Boolean, default=True) body = Column(Text(), nullable=False) chain = Column(Text()) private_key = Column(Vault) issuer = Column(String(128)) serial = Column(String(128)) cn = Column(String(128)) deleted = Column(Boolean, index=True) not_before = Column(DateTime) not_after = Column(DateTime) date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False) signing_algorithm = Column(String(128)) status = Column(String(128)) bits = Column(Integer()) san = Column(String(1024)) # TODO this should be migrated to boolean user_id = Column(Integer, ForeignKey('users.id')) authority_id = Column(Integer, ForeignKey('authorities.id', ondelete="CASCADE")) root_authority_id = Column( Integer, ForeignKey('authorities.id', ondelete="CASCADE")) notifications = relationship( "Notification", secondary=certificate_notification_associations, backref='certificate') destinations = relationship("Destination", secondary=certificate_destination_associations, backref='certificate') sources = relationship("Source", secondary=certificate_source_associations, backref='certificate') domains = relationship("Domain", secondary=certificate_associations, backref="certificate") roles = relationship("Role", secondary=roles_certificates, backref="certificate") replaces = relationship( "Certificate", secondary=certificate_replacement_associations, primaryjoin=id == certificate_replacement_associations.c.certificate_id, # noqa secondaryjoin=id == certificate_replacement_associations.c.replaced_certificate_id, # noqa backref='replaced') endpoints = relationship("Endpoint", backref='certificate') def __init__(self, **kwargs): cert = lemur.common.utils.parse_certificate(kwargs['body']) self.issuer = defaults.issuer(cert) self.cn = defaults.common_name(cert) self.san = defaults.san(cert) self.not_before = defaults.not_before(cert) self.not_after = defaults.not_after(cert) # when destinations are appended they require a valid name. if kwargs.get('name'): self.name = get_or_increase_name(kwargs['name']) else: self.name = get_or_increase_name( defaults.certificate_name(self.cn, self.issuer, self.not_before, self.not_after, self.san)) self.owner = kwargs['owner'] self.body = kwargs['body'].strip() if kwargs.get('private_key'): self.private_key = kwargs['private_key'].strip() if kwargs.get('chain'): self.chain = kwargs['chain'].strip() self.destinations = kwargs.get('destinations', []) self.notifications = kwargs.get('notifications', []) self.description = kwargs.get('description') self.roles = list(set(kwargs.get('roles', []))) self.replaces = kwargs.get('replacements', []) self.signing_algorithm = defaults.signing_algorithm(cert) self.bits = defaults.bitstrength(cert) self.serial = defaults.serial(cert) for domain in defaults.domains(cert): self.domains.append(Domain(name=domain)) @hybrid_property def expired(self): if self.not_after <= datetime.datetime.now(): return True @expired.expression def expired(cls): return case([(cls.now_after <= datetime.datetime.now(), True)], else_=False) @hybrid_property def revoked(self): if 'revoked' == self.status: return True @revoked.expression def revoked(cls): return case([(cls.status == 'revoked', True)], else_=False) def get_arn(self, account_number): """ Generate a valid AWS IAM arn :rtype : str :param account_number: :return: """ return "arn:aws:iam::{}:server-certificate/{}".format( account_number, self.name)
class Certificate(db.Model): __tablename__ = "certificates" __table_args__ = ( Index( "ix_certificates_cn", "cn", postgresql_ops={"cn": "gin_trgm_ops"}, postgresql_using="gin", ), Index( "ix_certificates_name", "name", postgresql_ops={"name": "gin_trgm_ops"}, postgresql_using="gin", ), ) id = Column(Integer, primary_key=True) ix = Index("ix_certificates_id_desc", id.desc(), postgresql_using="btree", unique=True) external_id = Column(String(128)) owner = Column(String(128), nullable=False) name = Column(String(256), unique=True) description = Column(String(1024)) notify = Column(Boolean, default=True) body = Column(Text(), nullable=False) chain = Column(Text()) csr = Column(Text()) private_key = Column(Vault) issuer = Column(String(128)) serial = Column(String(128)) cn = Column(String(128)) deleted = Column(Boolean, index=True, default=False) dns_provider_id = Column(Integer(), ForeignKey("dns_providers.id", ondelete="CASCADE"), nullable=True) not_before = Column(ArrowType) not_after = Column(ArrowType) date_created = Column(ArrowType, PassiveDefault(func.now()), nullable=False) signing_algorithm = Column(String(128)) status = Column(String(128)) bits = Column(Integer()) san = Column(String(1024)) # TODO this should be migrated to boolean rotation = Column(Boolean, default=False) user_id = Column(Integer, ForeignKey("users.id")) authority_id = Column(Integer, ForeignKey("authorities.id", ondelete="CASCADE")) root_authority_id = Column( Integer, ForeignKey("authorities.id", ondelete="CASCADE")) rotation_policy_id = Column(Integer, ForeignKey("rotation_policies.id")) notifications = relationship( "Notification", secondary=certificate_notification_associations, backref="certificate", ) destinations = relationship( "Destination", secondary=certificate_destination_associations, backref="certificate", ) sources = relationship("Source", secondary=certificate_source_associations, backref="certificate") domains = relationship("Domain", secondary=certificate_associations, backref="certificate") roles = relationship("Role", secondary=roles_certificates, backref="certificate") replaces = relationship( "Certificate", secondary=certificate_replacement_associations, primaryjoin=id == certificate_replacement_associations.c.certificate_id, # noqa secondaryjoin=id == certificate_replacement_associations.c.replaced_certificate_id, # noqa backref="replaced", ) replaced_by_pending = relationship( "PendingCertificate", secondary=pending_cert_replacement_associations, backref="pending_replace", viewonly=True, ) logs = relationship("Log", backref="certificate") endpoints = relationship("Endpoint", backref="certificate") rotation_policy = relationship("RotationPolicy") sensitive_fields = ("private_key", ) def __init__(self, **kwargs): self.body = kwargs["body"].strip() cert = self.parsed_cert self.issuer = defaults.issuer(cert) self.cn = defaults.common_name(cert) self.san = defaults.san(cert) self.not_before = defaults.not_before(cert) self.not_after = defaults.not_after(cert) self.serial = defaults.serial(cert) # when destinations are appended they require a valid name. if kwargs.get("name"): self.name = get_or_increase_name( defaults.text_to_slug(kwargs["name"]), self.serial) else: self.name = get_or_increase_name( defaults.certificate_name(self.cn, self.issuer, self.not_before, self.not_after, self.san), self.serial, ) self.owner = kwargs["owner"] if kwargs.get("private_key"): self.private_key = kwargs["private_key"].strip() if kwargs.get("chain"): self.chain = kwargs["chain"].strip() if kwargs.get("csr"): self.csr = kwargs["csr"].strip() self.notify = kwargs.get("notify", True) self.destinations = kwargs.get("destinations", []) self.notifications = kwargs.get("notifications", []) self.description = kwargs.get("description") self.roles = list(set(kwargs.get("roles", []))) self.replaces = kwargs.get("replaces", []) self.rotation = kwargs.get("rotation") self.rotation_policy = kwargs.get("rotation_policy") self.signing_algorithm = defaults.signing_algorithm(cert) self.bits = defaults.bitstrength(cert) self.external_id = kwargs.get("external_id") self.authority_id = kwargs.get("authority_id") self.dns_provider_id = kwargs.get("dns_provider_id") for domain in defaults.domains(cert): self.domains.append(Domain(name=domain)) # Check integrity before saving anything into the database. # For user-facing API calls, validation should also be done in schema validators. self.check_integrity() def check_integrity(self): """ Integrity checks: Does the cert have a valid chain and matching private key? """ if self.private_key: validators.verify_private_key_match( utils.parse_private_key(self.private_key), self.parsed_cert, error_class=AssertionError, ) if self.chain: chain = [self.parsed_cert] + utils.parse_cert_chain(self.chain) validators.verify_cert_chain(chain, error_class=AssertionError) @cached_property def parsed_cert(self): assert self.body, "Certificate body not set" return utils.parse_certificate(self.body) @property def active(self): return self.notify @property def organization(self): return defaults.organization(self.parsed_cert) @property def organizational_unit(self): return defaults.organizational_unit(self.parsed_cert) @property def country(self): return defaults.country(self.parsed_cert) @property def state(self): return defaults.state(self.parsed_cert) @property def location(self): return defaults.location(self.parsed_cert) @property def distinguished_name(self): return self.parsed_cert.subject.rfc4514_string() @property def key_type(self): if isinstance(self.parsed_cert.public_key(), rsa.RSAPublicKey): return "RSA{key_size}".format( key_size=self.parsed_cert.public_key().key_size) @property def validity_remaining(self): return abs(self.not_after - arrow.utcnow()) @property def validity_range(self): return self.not_after - self.not_before @property def subject(self): return self.parsed_cert.subject @property def public_key(self): return self.parsed_cert.public_key() @hybrid_property def expired(self): if self.not_after <= arrow.utcnow(): return True @expired.expression def expired(cls): return case([(cls.not_after <= arrow.utcnow(), True)], else_=False) @hybrid_property def revoked(self): if "revoked" == self.status: return True @revoked.expression def revoked(cls): return case([(cls.status == "revoked", True)], else_=False) @hybrid_property def in_rotation_window(self): """ Determines if a certificate is available for rotation based on the rotation policy associated. :return: """ now = arrow.utcnow() end = now + timedelta(days=self.rotation_policy.days) if self.not_after <= end: return True @in_rotation_window.expression def in_rotation_window(cls): """ Determines if a certificate is available for rotation based on the rotation policy associated. :return: """ return case( [(extract("day", cls.not_after - func.now()) <= RotationPolicy.days, True)], else_=False, ) @property def extensions(self): # setup default values return_extensions = {"sub_alt_names": {"names": []}} try: for extension in self.parsed_cert.extensions: value = extension.value if isinstance(value, x509.BasicConstraints): return_extensions["basic_constraints"] = value elif isinstance(value, x509.SubjectAlternativeName): return_extensions["sub_alt_names"]["names"] = value elif isinstance(value, x509.ExtendedKeyUsage): return_extensions["extended_key_usage"] = value elif isinstance(value, x509.KeyUsage): return_extensions["key_usage"] = value elif isinstance(value, x509.SubjectKeyIdentifier): return_extensions["subject_key_identifier"] = { "include_ski": True } elif isinstance(value, x509.AuthorityInformationAccess): return_extensions["certificate_info_access"] = { "include_aia": True } elif isinstance(value, x509.AuthorityKeyIdentifier): aki = { "use_key_identifier": False, "use_authority_cert": False } if value.key_identifier: aki["use_key_identifier"] = True if value.authority_cert_issuer: aki["use_authority_cert"] = True return_extensions["authority_key_identifier"] = aki elif isinstance(value, x509.CRLDistributionPoints): return_extensions["crl_distribution_points"] = { "include_crl_dp": value } # TODO: Not supporting custom OIDs yet. https://github.com/Netflix/lemur/issues/665 else: current_app.logger.warning( "Custom OIDs not yet supported for clone operation.") except InvalidCodepoint as e: sentry.captureException() current_app.logger.warning( "Unable to parse extensions due to underscore in dns name") except ValueError as e: sentry.captureException() current_app.logger.warning("Unable to parse") current_app.logger.exception(e) return return_extensions def __repr__(self): return "Certificate(name={name})".format(name=self.name)
class Authority(db.Model): __tablename__ = "authorities" id = Column(Integer, primary_key=True) owner = Column(String(128), nullable=False) name = Column(String(128), unique=True) body = Column(Text()) chain = Column(Text()) active = Column(Boolean, default=True) plugin_name = Column(String(64)) description = Column(Text) options = Column(JSON) date_created = Column(DateTime, PassiveDefault(func.now()), nullable=False) roles = relationship( "Role", secondary=roles_authorities, passive_deletes=True, backref=db.backref("authority"), lazy="dynamic", ) user_id = Column(Integer, ForeignKey("users.id")) authority_certificate = relationship( "Certificate", backref="root_authority", uselist=False, foreign_keys="Certificate.root_authority_id", ) certificates = relationship( "Certificate", backref="authority", foreign_keys="Certificate.authority_id" ) authority_pending_certificate = relationship( "PendingCertificate", backref="root_authority", uselist=False, foreign_keys="PendingCertificate.root_authority_id", ) pending_certificates = relationship( "PendingCertificate", backref="authority", foreign_keys="PendingCertificate.authority_id", ) def __init__(self, **kwargs): self.owner = kwargs["owner"] self.roles = kwargs.get("roles", []) self.name = kwargs.get("name") self.description = kwargs.get("description") self.authority_certificate = kwargs["authority_certificate"] self.plugin_name = kwargs["plugin"]["slug"] self.options = kwargs.get("options") @property def plugin(self): return plugins.get(self.plugin_name) @property def is_cab_compliant(self): """ Parse the options to find whether authority is CAB Forum Compliant, i.e., adhering to the CA/Browser Forum Baseline Requirements. Returns None if option is not available """ if not self.options: return None for option in json.loads(self.options): if "name" in option and option["name"] == 'cab_compliant': return option["value"] return None @property def max_issuance_days(self): if self.is_cab_compliant: return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397) @property def default_validity_days(self): if self.is_cab_compliant: return current_app.config.get("PUBLIC_CA_MAX_VALIDITY_DAYS", 397) return current_app.config.get("DEFAULT_VALIDITY_DAYS", 365) # 1 year default def __repr__(self): return "Authority(name={name})".format(name=self.name)
def reflecttable(self, connection, table): c = connection.execute("PRAGMA table_info(" + table.name + ")", {}) found_table = False while True: row = c.fetchone() if row is None: break #print "row! " + repr(row) found_table = True (name, type, nullable, has_default, primary_key) = (row[1], row[2].upper(), not row[3], row[4] is not None, row[5]) name = re.sub(r'^\"|\"$', '', name) match = re.match(r'(\w+)(\(.*?\))?', type) if match: coltype = match.group(1) args = match.group(2) else: coltype = "VARCHAR" args = '' #print "coltype: " + repr(coltype) + " args: " + repr(args) coltype = pragma_names.get(coltype, SLString) if args is not None: args = re.findall(r'(\d+)', args) #print "args! " +repr(args) coltype = coltype(*[int(a) for a in args]) colargs = [] if has_default: colargs.append(PassiveDefault('?')) table.append_column( schema.Column(name, coltype, primary_key=primary_key, nullable=nullable, *colargs)) if not found_table: raise exceptions.NoSuchTableError(table.name) c = connection.execute("PRAGMA foreign_key_list(" + table.name + ")", {}) fks = {} while True: row = c.fetchone() if row is None: break (constraint_name, tablename, localcol, remotecol) = (row[0], row[2], row[3], row[4]) tablename = re.sub(r'^\"|\"$', '', tablename) localcol = re.sub(r'^\"|\"$', '', localcol) remotecol = re.sub(r'^\"|\"$', '', remotecol) try: fk = fks[constraint_name] except KeyError: fk = ([], []) fks[constraint_name] = fk #print "row! " + repr([key for key in row.keys()]), repr(row) # look up the table based on the given table's engine, not 'self', # since it could be a ProxyEngine remotetable = schema.Table(tablename, table.metadata, autoload=True, autoload_with=connection) constrained_column = table.c[localcol].name refspec = ".".join([tablename, remotecol]) if constrained_column not in fk[0]: fk[0].append(constrained_column) if refspec not in fk[1]: fk[1].append(refspec) for name, value in fks.iteritems(): table.append_constraint( schema.ForeignKeyConstraint(value[0], value[1])) # check for UNIQUE indexes c = connection.execute("PRAGMA index_list(" + table.name + ")", {}) unique_indexes = [] while True: row = c.fetchone() if row is None: break if (row[2] == 1): unique_indexes.append(row[1]) # loop thru unique indexes for one that includes the primary key for idx in unique_indexes: c = connection.execute("PRAGMA index_info(" + idx + ")", {}) cols = [] while True: row = c.fetchone() if row is None: break cols.append(row[2]) col = table.columns[row[2]] # unique index that includes the pk is considered a multiple primary key for col in cols: table.primary_key.add(table.columns[col])