Feat: Added CA-merged with Securid-Principals, Depart, Client-CLI
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
"""Certificate Authority (CA) model."""
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
from gatehouse_app.extensions import db
|
||||
from gatehouse_app.models.base import BaseModel
|
||||
|
||||
|
||||
class KeyType(str, Enum):
|
||||
"""SSH CA key types."""
|
||||
|
||||
ED25519 = "ed25519"
|
||||
RSA = "rsa"
|
||||
ECDSA = "ecdsa"
|
||||
|
||||
|
||||
class CertType(str, Enum):
|
||||
"""SSH certificate types."""
|
||||
|
||||
USER = "user"
|
||||
HOST = "host"
|
||||
|
||||
|
||||
class CA(BaseModel):
|
||||
"""Certificate Authority (CA) model for SSH certificate signing.
|
||||
|
||||
Each organization can have multiple CAs for different purposes
|
||||
(e.g., production vs. staging). Private keys are encrypted at rest
|
||||
and should be protected with KMS.
|
||||
"""
|
||||
|
||||
__tablename__ = "cas"
|
||||
|
||||
organization_id = db.Column(
|
||||
db.String(36),
|
||||
db.ForeignKey("organizations.id"),
|
||||
nullable=True, # NULL for the global system-config CA
|
||||
index=True,
|
||||
)
|
||||
|
||||
# CA name and description
|
||||
name = db.Column(db.String(255), nullable=False)
|
||||
description = db.Column(db.Text, nullable=True)
|
||||
|
||||
# Key type (ED25519, RSA, ECDSA)
|
||||
key_type = db.Column(
|
||||
db.Enum(KeyType, values_callable=lambda x: [e.value for e in x]),
|
||||
default=KeyType.ED25519,
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# Private key (encrypted at rest by database/KMS)
|
||||
# Format: PEM-encoded private key
|
||||
private_key = db.Column(db.Text, nullable=False)
|
||||
|
||||
# Public key (PEM format)
|
||||
public_key = db.Column(db.Text, nullable=False)
|
||||
|
||||
# SHA256 fingerprint of the public key
|
||||
fingerprint = db.Column(db.String(255), nullable=False, unique=True)
|
||||
|
||||
# CRL (Certificate Revocation List) configuration
|
||||
crl_enabled = db.Column(db.Boolean, default=True, nullable=False)
|
||||
crl_endpoint = db.Column(db.String(512), nullable=True)
|
||||
|
||||
# Default certificate validity in hours
|
||||
# Can be overridden per certificate request
|
||||
default_cert_validity_hours = db.Column(
|
||||
db.Integer,
|
||||
default=1,
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# Maximum validity duration allowed
|
||||
max_cert_validity_hours = db.Column(
|
||||
db.Integer,
|
||||
default=24,
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# CA status
|
||||
is_active = db.Column(db.Boolean, default=True, nullable=False, index=True)
|
||||
|
||||
# Key rotation tracking
|
||||
rotated_at = db.Column(db.DateTime, nullable=True)
|
||||
rotation_reason = db.Column(db.String(255), nullable=True)
|
||||
|
||||
# Relationships
|
||||
organization = db.relationship("Organization", back_populates="cas")
|
||||
certificates = db.relationship(
|
||||
"SSHCertificate",
|
||||
back_populates="ca",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint(
|
||||
"organization_id", "name", name="uix_org_ca_name"
|
||||
),
|
||||
db.Index("idx_ca_org_active", "organization_id", "is_active"),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
"""String representation of CA."""
|
||||
return f"<CA {self.name} (org_id={self.organization_id}, type={self.key_type})>"
|
||||
|
||||
def to_dict(self, exclude=None):
|
||||
"""Convert CA to dictionary."""
|
||||
exclude = exclude or []
|
||||
# Never expose private key in API responses
|
||||
exclude.extend(["private_key"])
|
||||
data = super().to_dict(exclude=exclude)
|
||||
|
||||
# Add computed fields
|
||||
data["total_certs"] = len([c for c in self.certificates if c.deleted_at is None])
|
||||
data["active_certs"] = len([
|
||||
c for c in self.certificates
|
||||
if c.deleted_at is None and not c.revoked
|
||||
])
|
||||
data["revoked_certs"] = len([
|
||||
c for c in self.certificates
|
||||
if c.deleted_at is None and c.revoked
|
||||
])
|
||||
|
||||
return data
|
||||
|
||||
def get_active_certificates(self):
|
||||
"""Get all active (non-revoked) certificates issued by this CA.
|
||||
|
||||
Returns:
|
||||
List of non-revoked SSHCertificate objects
|
||||
"""
|
||||
return [
|
||||
c for c in self.certificates
|
||||
if c.deleted_at is None and not c.revoked
|
||||
]
|
||||
|
||||
def rotate_key(self, new_private_key, new_public_key, new_fingerprint, reason=None):
|
||||
"""Rotate the CA's key pair.
|
||||
|
||||
This should only be done in carefully controlled circumstances.
|
||||
All existing certificates remain valid but no new certs can be
|
||||
signed with the old key.
|
||||
|
||||
Args:
|
||||
new_private_key: New PEM-encoded private key
|
||||
new_public_key: New PEM-encoded public key
|
||||
new_fingerprint: SHA256 fingerprint of new public key
|
||||
reason: Optional reason for rotation
|
||||
"""
|
||||
self.private_key = new_private_key
|
||||
self.public_key = new_public_key
|
||||
self.fingerprint = new_fingerprint
|
||||
self.rotated_at = datetime.utcnow()
|
||||
self.rotation_reason = reason
|
||||
self.save()
|
||||
Reference in New Issue
Block a user