// ============================================
// Security Groups in Horkos
// ============================================
// Horkos enforces network security at compile time.
// Any ingress from non-private CIDRs requires explicit justification.
val logBucket = S3.createBucket("vpc-logs")
val vpc = Network.createVpc("production", cidr: "10.0.0.0/16", flowLogs: logBucket)
// ============================================
// PATTERN 1: Private CIDR (No unsafe needed)
// ============================================
// Traffic from RFC 1918 private ranges is considered safe:
// - 10.0.0.0/8
// - 172.16.0.0/12
// - 192.168.0.0/16
val appSg = Network.createSecurityGroup(
vpc: vpc,
name: "app-tier",
ingressRules: [
{ port: 8080, cidr: "10.0.0.0/8", description: "From any 10.x network" },
{ port: 8443, cidr: "172.16.0.0/12", description: "From 172.16-31.x" },
{ port: 9000, cidr: "192.168.1.0/24", description: "From specific subnet" }
]
)
// ============================================
// PATTERN 2: Security Group Reference (No unsafe needed)
// ============================================
// Referencing another security group is always safe -
// traffic is limited to resources in that group.
val dbSg = Network.createSecurityGroup(
vpc: vpc,
name: "database-tier",
ingressRules: [
{ port: 5432, sourceSecurityGroup: appSg, description: "PostgreSQL from app tier" },
{ port: 6379, sourceSecurityGroup: appSg, description: "Redis from app tier" }
]
)
// ============================================
// PATTERN 3: Localhost (No unsafe needed)
// ============================================
// 127.0.0.0/8 is considered safe (localhost only)
val localSg = Network.createSecurityGroup(
vpc: vpc,
name: "local-only",
ingressRules: [
{ port: 8080, cidr: "127.0.0.1/32", description: "Localhost only" }
]
)
// ============================================
// PATTERN 4: Public HTTPS (Requires unsafe)
// ============================================
// Any non-private CIDR requires explicit justification.
// This includes 0.0.0.0/0 and specific public IPs.
val webSg = unsafe("Public HTTPS endpoint - behind CloudFront WAF") {
Network.createSecurityGroup(
vpc: vpc,
name: "web-tier",
ingressRules: [
{ port: 443, cidr: "0.0.0.0/0", description: "HTTPS from internet" },
{ port: 80, cidr: "0.0.0.0/0", description: "HTTP redirect to HTTPS" }
]
)
}
// ============================================
// PATTERN 5: Specific Public IP (No unsafe needed)
// ============================================
// Specific IP whitelisting is considered safe - it's explicit access control.
val officeOnlySg = Network.createSecurityGroup(
vpc: vpc,
name: "office-access",
ingressRules: [
{ port: 22, cidr: "203.0.113.10/32", description: "SSH from office IP" },
{ port: 22, cidr: "198.51.100.0/24", description: "SSH from partner network" }
]
)
// ============================================
// PATTERN 6: IP Range + All Ports (Requires unsafe)
// ============================================
// Opening all ports to a range of IPs is too permissive.
// Single IP (/32) with all ports is OK (explicit trust).
val trustedNetwork = unsafe("Trusted partner network - all services needed") {
Network.createSecurityGroup(
vpc: vpc,
name: "partner-access",
ingressRules: [
{ port: 0, protocol: "-1", cidr: "203.0.113.0/24", description: "All traffic from partner" }
]
)
}
// OK: Single IP with all ports (trusted jump host)
val trustedHost = Network.createSecurityGroup(
vpc: vpc,
name: "trusted-host",
ingressRules: [
{ port: 0, protocol: "-1", cidr: "203.0.113.50/32", description: "All traffic from trusted host" }
]
)
// ============================================
// PATTERN 7: Bastion Host (Requires unsafe for critical ports)
// ============================================
// SSH (22), RDP (3389), and database ports get extra warnings.
val bastionSg = unsafe("Bastion host - MFA required, session recorded") {
Network.createSecurityGroup(
vpc: vpc,
name: "bastion",
ingressRules: [
{ port: 22, cidr: "0.0.0.0/0", description: "SSH from anywhere" }
]
)
}
// ============================================
// WHAT WON'T COMPILE (Errors)
// ============================================
// Only 0.0.0.0/0 (entire internet) requires unsafe.
// Specific IPs like "203.0.113.10/32" are allowed.
// ERROR 1: Critical port (SSH) to entire internet
// val badSsh = Network.createSecurityGroup(
// vpc: vpc,
// name: "bad",
// ingressRules: [{ port: 22, cidr: "0.0.0.0/0" }]
// )
// Error: [E0302]: SSH (port 22) open to entire internet
// ERROR 2: Database port to entire internet
// val badDb = Network.createSecurityGroup(
// vpc: vpc,
// name: "bad-db",
// ingressRules: [{ port: 5432, cidr: "0.0.0.0/0" }]
// )
// Error: [E0302]: PostgreSQL (port 5432) open to entire internet
// ERROR 3: Any port to entire internet
// val badWeb = Network.createSecurityGroup(
// vpc: vpc,
// name: "bad-web",
// ingressRules: [{ port: 443, cidr: "0.0.0.0/0" }]
// )
// Error: [E0303]: port 443 open to entire internet (0.0.0.0/0)
// ============================================
// DEFENSE IN DEPTH
// ============================================
// To actually expose a resource to the internet, you need BOTH:
// 1. Public subnet (requires unsafe)
// 2. Security group with 0.0.0.0/0 (requires unsafe)
//
// This ensures you consciously acknowledge the risk at EVERY layer.
val igw = Network.createInternetGateway(vpc: vpc)
// Layer 1: Public subnet (requires unsafe)
val publicSubnet = unsafe("Web tier needs internet access") {
Network.createSubnet(
vpc: vpc,
cidr: "10.0.1.0/24",
zone: "us-east-1a",
public: true,
mapPublicIp: true,
gateway: igw
)
}
// Layer 2: Security group with 0.0.0.0/0 (requires unsafe)
val publicSg = unsafe("Public HTTPS endpoint") {
Network.createSecurityGroup(
vpc: vpc,
name: "public",
ingressRules: [
{ port: 443, cidr: "0.0.0.0/0", description: "HTTPS from internet" }
]
)
}
// Now a resource in publicSubnet with publicSg can receive internet traffic.
// Both unsafe blocks document WHY this exposure is acceptable.
// ============================================
// MULTI-TIER ARCHITECTURE EXAMPLE
// ============================================
// Web tier: public HTTPS
val webTier = unsafe("Public ALB endpoint") {
Network.createSecurityGroup(
vpc: vpc,
name: "web",
ingressRules: [
{ port: 443, cidr: "0.0.0.0/0", description: "HTTPS" }
]
)
}
// App tier: only from web tier (no unsafe needed)
val appTier = Network.createSecurityGroup(
vpc: vpc,
name: "app",
ingressRules: [
{ port: 8080, sourceSecurityGroup: webTier, description: "From ALB" }
]
)
// DB tier: only from app tier (no unsafe needed)
val dbTier = Network.createSecurityGroup(
vpc: vpc,
name: "db",
ingressRules: [
{ port: 5432, sourceSecurityGroup: appTier, description: "PostgreSQL from app" }
]
)
// Cache tier: only from app tier (no unsafe needed)
val cacheTier = Network.createSecurityGroup(
vpc: vpc,
name: "cache",
ingressRules: [
{ port: 6379, sourceSecurityGroup: appTier, description: "Redis from app" }
]
)