EKS Cluster Privato con OpenVPN: Guida Sicurezza Completa

2026-03-20 · 11 min read · gen:2m 54s · tok:20009
#kubernetes #aws-eks #openvpn #devops #intermediate-tutorial #italiano

Configura un cluster EKS privato senza endpoint pubblico. Setup VPN con OpenVPN, networking sicuro e stack observability per ambienti enterprise.

Securing Your EKS Cluster: Guida Pratica al Private Networking con OpenVPN Access

La maggior parte dei tutorial su EKS ti mostra il percorso felice: cluster con endpoint pubblico, kubectl che funziona magicamente dal tuo laptop, tutto rose e fiori. Poi arriva il security audit e scopri che il tuo API server Kubernetes è esposto su Internet, raggiungibile da chiunque conosca l’URL.

Ho visto troppi team passare settimane a fare “hardening” di cluster che erano fondamentalmente aperti al mondo. La verità è che un cluster EKS sicuro richiede decisioni architetturali prese prima del provisioning, non dopo. Questo articolo ti guida attraverso la configurazione completa di un cluster EKS privato — zero esposizione pubblica dell’API server — con accesso VPN per gli sviluppatori e uno stack di observability che funziona anche in ambienti air-gapped.

Non troverai workaround o mezze misure. Alla fine avrai un’infrastruttura che passa audit di sicurezza enterprise senza compromettere la produttività del team.

Prerequisiti

Prima di iniziare, assicurati di avere:

  • AWS CLI v2 configurata con credenziali che hanno permessi per creare VPC, EKS, EC2, IAM
  • eksctl versione 0.165+ (alcune flag sono cambiate nelle versioni recenti)
  • kubectl compatibile con la versione EKS che deployerai
  • Helm 3.x per il deployment dello stack di monitoring
  • Terraform 1.5+ (opzionale ma consigliato per IaC)
  • Un dominio su Route53 per i certificati OpenVPN (può essere anche un sottodominio)
  • Budget AWS: questa configurazione costa circa $300-400/mese nella region us-east-1

💡 Se non hai mai lavorato con VPC peering o Transit Gateway, dedica 30 minuti a rivedere la documentazione AWS. Alcuni concetti li darò per assodati.

Architettura e Concetti Chiave

L’architettura che implementeremo separa nettamente tre zone di rete:

flowchart TD
    DEV[Developer Laptop] -->|VPN Tunnel UDP 1194| VPN
    ATK[Potential Attacker] -->|✗ BLOCKED| BLOCK[ ]
    style BLOCK fill:none,stroke:none

    subgraph AWS["AWS Region — VPC 10.0.0.0/16"]
        subgraph PUB["Public Subnets"]
            NAT[NAT Gateway]
            VPN[OpenVPN Access Server]
        end
        subgraph PRIV["Private Subnets"]
            subgraph EKS["EKS Cluster"]
                API[API Server Endpoint]
                WRK[Worker Nodes]
                OBS[Prometheus / Grafana]
            end
        end
        NAT -->|egress only| INTERNET((Internet))
        VPN -->|kubectl 443| API
        WRK --> NAT
    end

Decisioni architetturali chiave:

  1. API Server solo privato: L’endpoint EKS non avrà un indirizzo IP pubblico. Punto. Nessun security group al mondo rende sicuro un endpoint pubblico quanto la sua assenza.

  2. OpenVPN in subnet pubblica: Unico punto di ingresso, con autenticazione a due fattori e logging completo. Ogni connessione è tracciata.

  3. NAT Gateway per egress: I worker nodes possono raggiungere Internet (per pull di immagini, ad esempio) ma non sono raggiungibili dall’esterno.

  4. Observability interna: Prometheus e Grafana girano dentro il cluster, accessibili solo via VPN. Niente SaaS esterni che richiedono esposizione di metriche.

Implementazione Passo-Passo

Sezione 1: VPC e Networking Privato

Iniziamo dalle fondamenta. Una VPC mal configurata compromette tutto il resto.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
# vpc-config.tf - Configurazione Terraform per la VPC
# Questo file definisce l'intera topologia di rete

locals {
  # Usiamo /16 per avere spazio per crescere
  # Mai sottodimensionare il CIDR - aggiungere range dopo è doloroso
  vpc_cidr = "10.0.0.0/16"
  
  # Tre AZ per alta disponibilità
  azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
  
  # Subnet pubbliche: solo per NAT Gateway e VPN
  # Teniamo il range piccolo (/24 = 251 IP utilizzabili)
  public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  
  # Subnet private: qui vivranno i worker nodes
  # Range più ampio (/19 = 8190 IP) per supportare scaling
  private_subnets = ["10.0.32.0/19", "10.0.64.0/19", "10.0.96.0/19"]
  
  # Subnet isolate: per database e risorse che non devono
  # MAI raggiungere Internet, neanche via NAT
  isolated_subnets = ["10.0.128.0/24", "10.0.129.0/24", "10.0.130.0/24"]
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.0"

  name = "eks-private-vpc"
  cidr = local.vpc_cidr

  azs              = local.azs
  public_subnets   = local.public_subnets
  private_subnets  = local.private_subnets
  database_subnets = local.isolated_subnets

  # Un NAT Gateway per AZ garantisce che il fallimento
  # di una AZ non blocchi l'egress delle altre
  enable_nat_gateway     = true
  single_nat_gateway     = false  # MAI in produzione
  one_nat_gateway_per_az = true

  # DNS resolution necessaria per endpoint privati EKS
  enable_dns_hostnames = true
  enable_dns_support   = true

  # Tag richiesti da EKS per auto-discovery delle subnet
  public_subnet_tags = {
    "kubernetes.io/role/elb" = 1
  }

  private_subnet_tags = {
    "kubernetes.io/role/internal-elb" = 1
    # Questo tag è CRITICO - senza, EKS non trova le subnet
    "kubernetes.io/cluster/eks-private" = "shared"
  }

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }
}

# VPC Endpoints per evitare traffico via Internet
# Questo riduce latenza e costi di NAT Gateway
resource "aws_vpc_endpoint" "s3" {
  vpc_id       = module.vpc.vpc_id
  service_name = "com.amazonaws.eu-west-1.s3"
  
  # Gateway endpoint per S3 - gratuito
  vpc_endpoint_type = "Gateway"
  route_table_ids   = module.vpc.private_route_table_ids
}

resource "aws_vpc_endpoint" "ecr_api" {
  vpc_id              = module.vpc.vpc_id
  service_name        = "com.amazonaws.eu-west-1.ecr.api"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = module.vpc.private_subnets
  security_group_ids  = [aws_security_group.vpc_endpoints.id]
  private_dns_enabled = true
}

resource "aws_vpc_endpoint" "ecr_dkr" {
  vpc_id              = module.vpc.vpc_id
  service_name        = "com.amazonaws.eu-west-1.ecr.dkr"
  vpc_endpoint_type   = "Interface"
  subnet_ids          = module.vpc.private_subnets
  security_group_ids  = [aws_security_group.vpc_endpoints.id]
  private_dns_enabled = true
}

# Security group per VPC endpoints
resource "aws_security_group" "vpc_endpoints" {
  name_prefix = "vpc-endpoints-"
  vpc_id      = module.vpc.vpc_id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    # Solo traffico dalla VPC
    cidr_blocks = [local.vpc_cidr]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

⚠️ Errore comune: dimenticare i VPC endpoints per ECR. Senza di essi, ogni pull di immagine passa dal NAT Gateway, che costa $0.045/GB. Con cluster attivi, questo può aggiungere centinaia di dollari al mese.

Ora creiamo il cluster EKS con endpoint privato:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# eks-cluster.yaml - eksctl configuration
# Questo file configura un cluster completamente privato

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: eks-private
  region: eu-west-1
  version: "1.28"

# VPC esistente - non lasciare che eksctl ne crei una nuova
vpc:
  id: "vpc-xxxxxxxxx"  # Sostituisci con l'ID della VPC creata sopra
  subnets:
    private:
      eu-west-1a:
        id: "subnet-private-1a"
      eu-west-1b:
        id: "subnet-private-1b"
      eu-west-1c:
        id: "subnet-private-1c"
  
  # QUESTA È LA CONFIGURAZIONE CRITICA
  # publicAccessCIDRs vuoto + privateAccess true = endpoint solo privato
  clusterEndpoints:
    publicAccess: false   # Nessun accesso pubblico all'API server
    privateAccess: true   # Solo accesso dalla VPC

# IAM OIDC provider per service account IAM roles
iam:
  withOIDC: true

# Node groups in subnet private
managedNodeGroups:
  - name: workers-general
    instanceType: m5.large
    desiredCapacity: 3
    minSize: 2
    maxSize: 10
    
    # Nodes solo in subnet private
    subnets:
      - subnet-private-1a
      - subnet-private-1b
      - subnet-private-1c
    
    privateNetworking: true  # Nessun IP pubblico sui nodes
    
    # Labels per scheduling
    labels:
      workload-type: general
    
    # Taints se vuoi dedicare node groups
    # taints:
    #   - key: dedicated
    #     value: monitoring
    #     effect: NoSchedule
    
    iam:
      attachPolicyARNs:
        - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
        - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore  # Per debug via SSM
    
    # SSH disabilitato - usa SSM Session Manager
    ssh:
      allow: false

# Addons gestiti da AWS
addons:
  - name: vpc-cni
    version: latest
    attachPolicyARNs:
      - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
  - name: coredns
    version: latest
  - name: kube-proxy
    version: latest

# CloudWatch logging per audit
cloudWatch:
  clusterLogging:
    enableTypes:
      - api
      - audit
      - authenticator
      - controllerManager
      - scheduler

Esegui il provisioning:

1
2
3
4
5
6
7
# Crea il cluster (richiede 15-20 minuti)
eksctl create cluster -f eks-cluster.yaml

# Verifica che l'endpoint sia effettivamente privato
aws eks describe-cluster --name eks-private \
  --query 'cluster.resourcesVpcConfig.endpointPublicAccess'
# Deve restituire: false

📝 Nota: Dopo questo punto, kubectl dal tuo laptop non funzionerà. È esattamente quello che vogliamo. Configureremo l’accesso VPN nella prossima sezione.

Sezione 2: OpenVPN Access Server come Gateway Sicuro

OpenVPN Access Server offre un buon equilibrio tra sicurezza e usabilità. L’alternativa sarebbe AWS Client VPN, ma costa di più e ha meno flessibilità nella configurazione.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# openvpn-instance.tf - Deploy OpenVPN Access Server

# AMI ufficiale OpenVPN Access Server (BYOL)
data "aws_ami" "openvpn" {
  most_recent = true
  owners      = ["679593333241"]  # OpenVPN Inc.

  filter {
    name   = "name"
    values = ["OpenVPN Access Server*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

# Security group per OpenVPN
resource "aws_security_group" "openvpn" {
  name_prefix = "openvpn-"
  vpc_id      = module.vpc.vpc_id

  # Porta admin HTTPS - limitata a IP noti durante setup
  ingress {
    from_port   = 943
    to_port     = 943
    protocol    = "tcp"
    cidr_blocks = ["YOUR_OFFICE_IP/32"]  # Cambia con il tuo IP
    description = "Admin interface"
  }

  # Porta client HTTPS
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]  # Sviluppatori da ovunque
    description = "Client web interface"
  }

  # OpenVPN UDP - preferibile per performance
  ingress {
    from_port   = 1194
    to_port     = 1194
    protocol    = "udp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "OpenVPN UDP tunnel"
  }

  # Egress verso la VPC per raggiungere EKS
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [local.vpc_cidr]
    description = "Access to VPC resources"
  }

  # Egress verso Internet per updates
  egress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTPS outbound"
  }

  tags = {
    Name = "openvpn-sg"
  }
}

# Elastic IP per indirizzo stabile
resource "aws_eip" "openvpn" {
  domain = "vpc"
  
  tags = {
    Name = "openvpn-eip"
  }
}

# Instance OpenVPN
resource "aws_instance" "openvpn" {
  ami           = data.aws_ami.openvpn.id
  instance_type = "t3.small"  # Sufficiente per ~50 connessioni simultanee
  
  subnet_id                   = module.vpc.public_subnets[0]
  vpc_security_group_ids      = [aws_security_group.openvpn.id]
  associate_public_ip_address = true
  
  # Key pair per accesso SSH iniziale (poi disabilitare)
  key_name = "your-key-pair"
  
  # Script di inizializzazione
  user_data = <<-EOF
    #!/bin/bash
    # Attendi che OpenVPN AS sia pronto
    sleep 60
    
    # Configura admin password iniziale
    echo "openvpn:${random_password.openvpn_admin.result}" | chpasswd
    
    # Abilita routing verso subnet private
    /usr/local/openvpn_as/scripts/sacli --key "vpn.server.routing.private_network.0" \
      --value "10.0.0.0/16" ConfigPut
    
    # Riavvia per applicare
    /usr/local/openvpn_as/scripts/sacli start
  EOF

  root_block_device {
    volume_size = 30
    volume_type = "gp3"
    encrypted   = true
  }

  tags = {
    Name = "openvpn-access-server"
  }

  lifecycle {
    ignore_changes = [ami]  # Non ricreare per nuove AMI
  }
}

resource "aws_eip_association" "openvpn" {
  instance_id   = aws_instance.openvpn.id
  allocation_id = aws_eip.openvpn.id
}

# Password admin generata
resource "random_password" "openvpn_admin" {
  length  = 16
  special = false
}

# Output per setup iniziale
output "openvpn_admin_url" {
  value = "https://${aws_eip.openvpn.public_ip}:943/admin"
}

output "openvpn_client_url" {
  value = "https://${aws_eip.openvpn.public_ip}/"
}

output "openvpn_admin_password" {
  value     = random_password.openvpn_admin.result
  sensitive = true
}

Configurazione post-deploy di OpenVPN:

Accedi all’interfaccia admin e configura:

  1. Network Settings → VPN Server:

    • Protocol: UDP (migliore performance)
    • Port: 1194
    • Routing: “Yes, using NAT” per le subnet private
  2. VPN Settings:

    # Split tunnel - instrada solo traffico VPC via VPN
    # Questo evita di rallentare la navigazione normale degli sviluppatori
    
    Should client Internet traffic be routed through the VPN? → No
    
    # Aggiungi route specifiche
    Specify the private subnets: 10.0.0.0/16
    
    # DNS push per risolvere nomi interni
    DNS Server 1: 10.0.0.2  # VPC DNS resolver
    
  3. Certificati - genera certificati validi:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    # Sul server OpenVPN, genera CA e certificati
    cd /usr/local/openvpn_as/scripts
    
    # Rigenera con nome dominio corretto
    ./sacli --key "cs.ca_bundle" GetActiveWebCerts
    ./sacli --key "host.name" --value "vpn.yourdomain.com" ConfigPut
    
    # Installa certificato Let's Encrypt (se hai un dominio)
    apt install certbot
    certbot certonly --standalone -d vpn.yourdomain.com
    
    # Importa in OpenVPN AS
    ./sacli --key "cs.priv_key" --value_file "/etc/letsencrypt/live/vpn.yourdomain.com/privkey.pem" ConfigPut
    ./sacli --key "cs.cert" --value_file "/etc/letsencrypt/live/vpn.yourdomain.com/cert.pem" ConfigPut
    ./sacli --key "cs.ca_bundle" --value_file "/etc/letsencrypt/live/vpn.yourdomain.com/chain.pem" ConfigPut
    
    # Applica: nessuna modifica sacli è attiva finché non riavvii
    ./sacli start
    
  4. DNS → questa è l’impostazione che fa funzionare kubectl:

    Should clients use specific DNS servers? → Yes
    DNS Server 1: 10.0.0.2         # il resolver .2 del CIDR della VPC
    Default Domain: eks.internal   # o la tua private hosted zone
    

⚠️ Senza il push DNS, l’endpoint privato di EKS (XXXX.gr7.eu-west-1.eks.amazonaws.com) non si risolve: risponde con un IP privato solo da dentro la VPC. Se il client VPN tiene il resolver locale, ogni comando kubectl resta appeso fino al timeout.

Sezione 3: Generare i Profili Client e Collegare kubectl

Crea l’utente ed esporta il profilo .ovpn:

1
2
3
4
5
6
# Sul server OpenVPN AS
cd /usr/local/openvpn_as/scripts

./sacli --user devops --key "type" --value "user_connect" UserPropPut
./sacli --user devops --key "prop_autologin" --value "true" UserPropPut
./sacli --user devops GetUserlogin > devops.ovpn

Dalla tua postazione, connettiti e poi punta kubectl all’endpoint privato:

1
2
3
4
5
6
7
8
9
# Importa devops.ovpn nel client VPN (Tunnelblick, OpenVPN Connect, nmcli...) e connettiti.
ping -c1 10.0.1.10        # un IP qualsiasi di subnet privata

# Scarica il kubeconfig del cluster privato
aws eks update-kubeconfig --name eks-privato --region eu-west-1

# Funziona solo con la VPN attiva
kubectl get nodes
kubectl get pods -A
1
2
3
4
# Prova che l'endpoint è davvero privato: da FUORI dalla VPN questo fallisce
nslookup $(aws eks describe-cluster --name eks-privato \
  --query 'cluster.endpoint' --output text | sed 's|https://||')
# -> NXDOMAIN / nessuna risposta   (si risolve solo via VPN)

Configurazione per Produzione

Alta disponibilità. Il singolo NAT Gateway e la singola istanza OpenVPN vanno bene per un team, non per la produzione:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Un NAT Gateway per AZ, così il guasto di una zona non taglia l'egress dei nodi
resource "aws_nat_gateway" "main" {
  for_each      = aws_subnet.public
  allocation_id = aws_eip.nat[each.key].id
  subnet_id     = each.value.id
}

# OpenVPN AS come cluster a 2 nodi dietro un Network Load Balancer
resource "aws_lb" "openvpn" {
  name               = "openvpn-nlb"
  internal           = false
  load_balancer_type = "network"
  subnets            = [for s in aws_subnet.public : s.id]
}

Autenticazione. Sostituisci l’auth locale con il tuo IdP (SAML o LDAP) e imponi la MFA:

1
2
3
4
./sacli --key "auth.module.type" --value "saml" ConfigPut
./sacli --key "vpn.server.tls_auth" --value "true" ConfigPut
./sacli --key "cs.tls_version_min" --value "1.2" ConfigPut
./sacli start

Security group a privilegio minimo. Esponi sull’istanza OpenVPN solo le porte necessarie, e solo dai range dell’ufficio; il control plane EKS deve accettare traffico solo dal SG dei nodi e dal SG della VPN:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
resource "aws_security_group_rule" "openvpn_ingress" {
  for_each          = { https = 443, admin = 943, udp = 1194 }
  type              = "ingress"
  security_group_id = aws_security_group.openvpn.id
  protocol          = each.key == "udp" ? "udp" : "tcp"
  from_port         = each.value
  to_port           = each.value
  cidr_blocks       = var.office_cidrs   # non 0.0.0.0/0
}

resource "aws_security_group_rule" "eks_api_from_vpn" {
  type                     = "ingress"
  security_group_id        = module.eks.cluster_security_group_id
  protocol                 = "tcp"
  from_port                = 443
  to_port                  = 443
  source_security_group_id = aws_security_group.openvpn.id
}

Errori Comuni e Troubleshooting

kubectl resta appeso e va in timeout. Quasi sempre è DNS. Verifica che la VPN stia spingendo 10.0.0.2 come resolver (scutil --dns su macOS, resolvectl status su Linux) e che il nome dell’endpoint privato si risolva a un indirizzo 10.x mentre sei connesso.

dial tcp ... i/o timeout sulla 443. Il security group del cluster non accetta traffico dal SG di OpenVPN. Aggiungi la regola eks_api_from_vpn mostrata sopra.

I nodi non scaricano immagini o non raggiungono STS/ECR. Hai creato un cluster completamente privato ma senza i VPC endpoint (ecr.api, ecr.dkr, gateway s3, sts, ec2, logs). Senza, i nodi privati non hanno alcun percorso verso le API AWS. Il NAT Gateway copre lo stesso caso, ma gli endpoint costano meno e tengono il traffico fuori da internet.

Le modifiche sacli non hanno effetto. Nessun ConfigPut è attivo finché non lanci ./sacli start.

La VPN si connette ma non raggiungi 10.0.0.0/16. Manca la route della subnet spinta al client, oppure il client è su una LAN che usa anch’essa 10.0.0.0/16 e le route vanno in conflitto. Sposta la VPC su un CIDR meno comune (es. 10.42.0.0/16).

aws eks update-kubeconfig scrive un endpoint “pubblico”. Scrive quello che ritorna cluster.endpoint: l’URL è lo stesso, cambia solo come si risolve in DNS a seconda della rete. Se fuori dalla VPN fallisce, è la feature che funziona.

Performance e Scalabilità

  • Usa UDP/1194 per il data channel. Il “TCP-over-TCP meltdown” peggiora sensibilmente kubectl port-forward e lo streaming dei log su link con perdita di pacchetti. Tieni TCP/443 solo come fallback per reti restrittive.
  • Split-tunnel di default. Instradare solo 10.0.0.0/16 (non 0.0.0.0/0) tiene la navigazione degli sviluppatori fuori dalla VPN e dalla bolletta del NAT.
  • Dimensiona bene l’istanza AS. t3.small regge ~25-50 tunnel concorrenti; passa a c6i.large per team più grandi o traffico pesante di kubectl cp / registry. Il collo di bottiglia è la CPU (cifratura), non la memoria.
  • Preferisci i VPC endpoint al NAT per il traffico verso le API AWS: un singolo cluster attivo può spingere centinaia di GB/mese nel NAT a $0,045/GB.
  • Scala l’AS orizzontalmente con un cluster a 2+ nodi dietro un NLB quando una singola istanza satura; i profili client continuano a funzionare durante un failover.

Conclusioni e Next Steps

Ora hai un cluster EKS senza endpoint API pubblico, nodi in subnet private senza percorso inbound da internet, e accesso amministrativo interamente dietro un tunnel OpenVPN autenticato e cifrato. La postura di sicurezza è “devi essere sulla VPN anche solo per risolvere il nome del cluster”, il che elimina un’intera classe di esposizione.

Prossimi passi:

  • Porta i passi post-install di OpenVPN in Terraform con user_data + sacli così l’intero stack è riproducibile.
  • Configura le EKS access entry così gli utenti VPN mappano su ruoli RBAC a privilegio minimo, non system:masters.
  • Metti la UI admin dietro SSO e imponi la MFA sull’IdP.
  • Abilita l’audit logging del control plane (hai già attivato CloudWatch) e spediscilo a un SIEM.
  • Valuta AWS VPN Client o Tailscale come alternativa gestita se preferisci non operare tu il server VPN.

Risorse Aggiuntive