horkos 0.2.0

Cloud infrastructure language where insecure code won't compile
Documentation
// ============================================
// 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" }
    ]
)