feat(zerotier): add ZeroTier network governance module
Add comprehensive ZeroTier integration for managing network access: - Portal networks: manager-created ZeroTier network bindings - Device registration: user-owned ZeroTier node endpoints - Approval workflows: request/approve/revoke network access - Activation sessions: time-limited network authorization - Kill switch: emergency access revocation - Reconciliation job: sync portal state with ZeroTier controller Includes ZeroTier client SDK supporting both Central and self-hosted controller APIs, with full CRUD operations for networks and members.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""Device model — a user-registered ZeroTier node endpoint."""
|
||||
|
||||
from gatehouse_app.extensions import db
|
||||
from gatehouse_app.models.base import BaseModel
|
||||
from gatehouse_app.utils.constants import DeviceStatus
|
||||
|
||||
|
||||
class Device(BaseModel):
|
||||
"""A user-owned endpoint identified by a ZeroTier Node ID.
|
||||
|
||||
A user registers their device in the portal using the 10-character ZeroTier
|
||||
Node ID visible in the ZeroTier client. One active device record per
|
||||
node_id at a time (unique constraint excludes soft-deleted duplicates).
|
||||
|
||||
Attributes:
|
||||
user_id: FK to the owning user
|
||||
organization_id: FK to the organization this device is registered in
|
||||
node_id: 10-char hex ZeroTier node / device address
|
||||
device_nickname: User-assigned friendly name
|
||||
hostname: Device hostname reported by the client
|
||||
asset_tag: Corporate asset tag if available
|
||||
serial_number: Device serial number if available
|
||||
status: active / inactive
|
||||
"""
|
||||
|
||||
__tablename__ = "devices"
|
||||
|
||||
user_id = db.Column(
|
||||
db.String(36),
|
||||
db.ForeignKey("users.id"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
organization_id = db.Column(
|
||||
db.String(36),
|
||||
db.ForeignKey("organizations.id"),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
node_id = db.Column(
|
||||
db.String(10),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
device_nickname = db.Column(db.String(255), nullable=True)
|
||||
hostname = db.Column(db.String(255), nullable=True)
|
||||
asset_tag = db.Column(db.String(255), nullable=True)
|
||||
serial_number = db.Column(db.String(255), nullable=True)
|
||||
status = db.Column(
|
||||
db.Enum(DeviceStatus, name="device_status"),
|
||||
default=DeviceStatus.ACTIVE,
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# Relationships
|
||||
user = db.relationship("User", backref="devices")
|
||||
organization = db.relationship("Organization", backref="devices")
|
||||
memberships = db.relationship(
|
||||
"DeviceNetworkMembership",
|
||||
back_populates="device",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Device {self.node_id} ({self.device_nickname or 'unnamed'})>"
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
return self.device_nickname or self.hostname or self.node_id
|
||||
|
||||
def to_dict(self, exclude=None):
|
||||
exclude = exclude or []
|
||||
data = super().to_dict(exclude=exclude)
|
||||
data["display_name"] = self.display_name
|
||||
data["active_membership_count"] = sum(
|
||||
1 for m in self.memberships
|
||||
if m.state == "active_authorized" and m.deleted_at is None
|
||||
)
|
||||
return data
|
||||
Reference in New Issue
Block a user