Files
Seguran-a-em-Sistemas-Opera…/crialab.sh
T
2026-09-29 14:01:45 +00:00

226 lines
13 KiB
Bash

#!/usr/bin/env bash
# Infraestrutura do laboratorio. Compativel com curl -fsSL URL | bash.
# Configuracao por variaveis de ambiente; nao le perguntas de stdin.
set -Eeuo pipefail
export AWS_PAGER="" AWS_CLI_AUTO_PROMPT=off
main() {
local mode="${1:---apply}"
case "$mode" in --plan|--apply) ;; *) echo 'Use --plan ou --apply' >&2; return 2;; esac
for tool in aws python3 curl; do
command -v "$tool" >/dev/null || { echo "Falta instalar: $tool" >&2; return 2; }
done
REGION="${REGION:-us-east-1}"
LAB_NAME="${LAB_NAME:-ssor-vpn}"
INSTANCE_TYPE="${INSTANCE_TYPE:-t3.micro}"
LOCAL_CIDR="${LOCAL_CIDR:-172.17.0.0/24}"
VPN_CIDR="${VPN_CIDR:-10.8.0.0/24}"
local create_key=false key_file='' run_id
run_id="$(date -u +%Y%m%dT%H%M%SZ)-$$"
if [[ -z "${KEY_NAME:-}" ]]; then
KEY_NAME="$LAB_NAME-$run_id"
create_key=true
fi
[[ "$LAB_NAME" =~ ^[a-zA-Z][a-zA-Z0-9-]{0,39}$ ]] || { echo 'LAB_NAME invalido' >&2; return 2; }
# Validar as redes local e VPN.
python3 - "$LOCAL_CIDR" "$VPN_CIDR" <<'PY'
import ipaddress, sys
local, vpn = [ipaddress.IPv4Network(x, strict=True) for x in sys.argv[1:]]
vpc = ipaddress.IPv4Network('10.20.0.0/16')
private = [ipaddress.ip_network(x) for x in ['10.0.0.0/8','172.16.0.0/12','192.168.0.0/16']]
for net in [local, vpn]:
if not any(net.subnet_of(x) for x in private):
raise SystemExit('Redes local e VPN devem usar faixas privadas RFC1918.')
if local.overlaps(vpn) or local.overlaps(vpc) or vpn.overlaps(vpc):
raise SystemExit('As redes local, VPN e VPC nao podem se sobrepor.')
PY
aw() { aws --region "$REGION" "$@"; }
local account existing ami root_device az
account=$(aw sts get-caller-identity --query Account --output text)
if [[ "$create_key" == false ]]; then
aw ec2 describe-key-pairs --key-names "$KEY_NAME" >/dev/null
fi
existing=$(aw ec2 describe-vpcs --filters "Name=tag:Lab,Values=$LAB_NAME" --query 'Vpcs[].VpcId' --output text)
if [[ -n "$existing" && "$existing" != None ]]; then
echo "Ja existe uma VPC deste laboratorio: $existing. Revise-a antes de executar novamente." >&2
return 2
fi
# Imagem oficial Canonical Ubuntu 24.04 LTS amd64, sem Marketplace.
ami=$(aw ec2 describe-images --owners 099720109477 --filters \
'Name=name,Values=ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*' \
'Name=state,Values=available' 'Name=architecture,Values=x86_64' \
'Name=root-device-type,Values=ebs' 'Name=virtualization-type,Values=hvm' \
--query 'sort_by(Images,&CreationDate)[-1].ImageId' --output text)
[[ -n "$ami" && "$ami" != None ]] || { echo 'AMI Ubuntu nao encontrada.' >&2; return 2; }
root_device=$(aw ec2 describe-images --image-ids "$ami" --query 'Images[0].RootDeviceName' --output text)
az=$(aw ec2 describe-instance-type-offerings --location-type availability-zone \
--filters "Name=instance-type,Values=$INSTANCE_TYPE" \
--query 'sort_by(InstanceTypeOfferings,&Location)[0].Location' --output text)
[[ -n "$az" && "$az" != None ]] || { echo 'Tipo de instancia indisponivel na regiao.' >&2; return 2; }
cat <<EOF
Conta: $account | Regiao: $REGION | Zona: $az | Lab: $LAB_NAME
Ubuntu 24.04: $ami | Duas EC2: $INSTANCE_TYPE | Chave: $KEY_NAME
VPC: 10.20.0.0/16
Gateway publico: 10.20.1.10 | Servidor privado: 10.20.2.10
SSH publico permitido de: 0.0.0.0/0
Criar chave SSH automaticamente: $create_key
VPC 10.20.0.0/16: todos os protocolos liberados entre as maquinas.
VPN: UDP/1194 aberto; rotas AWS, permissoes e encaminhamento IPv4 preparados.
Rede local: $LOCAL_CIDR | Tunel: $VPN_CIDR | OpenVPN roteado sem NAT entre sites.
Serao criados recursos que consomem o saldo do laboratorio: EC2, EBS e IPv4 publico.
Aloca um Elastic IP fixo para o gateway. Nao cria NAT Gateway, IAM ou certificados VPN.
EOF
if [[ "$mode" == --plan ]]; then
echo 'Plano somente: nenhuma alteracao. Use --apply para criar.'
return
fi
# Diario salvo no terminal que executar o script. Nao contem chaves privadas.
umask 077
local state_dir="${LAB_STATE_DIR:-$PWD/aws-lab-state}"
mkdir -p "$state_dir"
state_dir=$(cd "$state_dir" && pwd -P)
STATE_FILE="$state_dir/${LAB_NAME}-$run_id.txt"
printf 'Account=%s\nRegion=%s\nLab=%s\n' "$account" "$REGION" "$LAB_NAME" > "$STATE_FILE"
trap 'echo "Falha na linha $LINENO. Recursos parciais podem existir; consulte $STATE_FILE e a tag Lab=$LAB_NAME. Nao houve rollback automatico." >&2' ERR
record() { printf '%s=%s\n' "$1" "$2" | tee -a "$STATE_FILE"; }
tags() { printf 'ResourceType=%s,Tags=[{Key=Lab,Value=%s},{Key=Name,Value=%s-%s}]' "$1" "$LAB_NAME" "$LAB_NAME" "$2"; }
record LocalCidr "$LOCAL_CIDR"
record VpnCidr "$VPN_CIDR"
record KeyName "$KEY_NAME"
record KeyCreatedByScript "$create_key"
if [[ "$create_key" == true ]]; then
key_file="$state_dir/$KEY_NAME.pem"
# noclobber impede sobrescrever uma chave preexistente; conteudo nunca vai para stdout.
# RSA/PEM sao os padroes; omitir flags novas permite usar a CLI antiga do Learner Lab.
(set -o noclobber; aw ec2 create-key-pair --key-name "$KEY_NAME" \
--tag-specifications "$(tags key-pair ssh)" --query KeyMaterial --output text > "$key_file")
chmod 600 "$key_file"
[[ -s "$key_file" ]] || { echo 'Arquivo da chave vazio.' >&2; return 1; }
record PrivateKeyFile "$key_file"
fi
local vpc igw public_subnet private_subnet public_rt private_rt gateway_sg services_sg gateway server
vpc=$(aw ec2 create-vpc --cidr-block 10.20.0.0/16 --tag-specifications "$(tags vpc vpc)" --query Vpc.VpcId --output text)
record VpcId "$vpc"
aw ec2 wait vpc-available --vpc-ids "$vpc"
aw ec2 modify-vpc-attribute --vpc-id "$vpc" --enable-dns-support '{"Value":true}'
aw ec2 modify-vpc-attribute --vpc-id "$vpc" --enable-dns-hostnames '{"Value":true}'
igw=$(aw ec2 create-internet-gateway --tag-specifications "$(tags internet-gateway igw)" --query InternetGateway.InternetGatewayId --output text)
record InternetGatewayId "$igw"
aw ec2 attach-internet-gateway --internet-gateway-id "$igw" --vpc-id "$vpc"
public_subnet=$(aw ec2 create-subnet --vpc-id "$vpc" --cidr-block 10.20.1.0/24 --availability-zone "$az" --tag-specifications "$(tags subnet publica)" --query Subnet.SubnetId --output text)
record PublicSubnetId "$public_subnet"
private_subnet=$(aw ec2 create-subnet --vpc-id "$vpc" --cidr-block 10.20.2.0/24 --availability-zone "$az" --tag-specifications "$(tags subnet privada)" --query Subnet.SubnetId --output text)
record PrivateSubnetId "$private_subnet"
public_rt=$(aw ec2 create-route-table --vpc-id "$vpc" --tag-specifications "$(tags route-table publica)" --query RouteTable.RouteTableId --output text)
record PublicRouteTableId "$public_rt"
aw ec2 associate-route-table --route-table-id "$public_rt" --subnet-id "$public_subnet" >/dev/null
aw ec2 create-route --route-table-id "$public_rt" --destination-cidr-block 0.0.0.0/0 --gateway-id "$igw" >/dev/null
private_rt=$(aw ec2 create-route-table --vpc-id "$vpc" --tag-specifications "$(tags route-table privada)" --query RouteTable.RouteTableId --output text)
record PrivateRouteTableId "$private_rt"
aw ec2 associate-route-table --route-table-id "$private_rt" --subnet-id "$private_subnet" >/dev/null
gateway_sg=$(aw ec2 create-security-group --vpc-id "$vpc" --group-name "$LAB_NAME-gateway" --description 'Gateway OpenVPN do laboratorio' --tag-specifications "$(tags security-group gateway)" --query GroupId --output text)
record GatewaySecurityGroupId "$gateway_sg"
services_sg=$(aw ec2 create-security-group --vpc-id "$vpc" --group-name "$LAB_NAME-servicos" --description 'Servicos privados do laboratorio' --tag-specifications "$(tags security-group servicos)" --query GroupId --output text)
record ServicesSecurityGroupId "$services_sg"
allow() { aw ec2 authorize-security-group-ingress --group-id "$1" --protocol "$2" --port "$3" --cidr "$4" >/dev/null; }
allow "$gateway_sg" tcp 22 0.0.0.0/0
allow "$gateway_sg" udp 1194 0.0.0.0/0
# Comunicacao interna irrestrita na VPC; saida padrao dos grupos ja permite tudo.
local internal_sg internal_cidr
for internal_sg in "$gateway_sg" "$services_sg"; do
for internal_cidr in 10.20.0.0/16 "$LOCAL_CIDR" "$VPN_CIDR"; do
aw ec2 authorize-security-group-ingress --group-id "$internal_sg" --ip-permissions \
"[{\"IpProtocol\":\"-1\",\"IpRanges\":[{\"CidrIp\":\"$internal_cidr\",\"Description\":\"Redes privadas do laboratorio\"}]}]" >/dev/null
done
done
# Encaminhamento persistente, sem depender de download de pacotes.
local gateway_user_data='#cloud-config
write_files:
- path: /etc/sysctl.d/90-vpn-lab.conf
permissions: "0644"
content: |
net.ipv4.ip_forward=1
runcmd:
- [sysctl, --system]
'
local disk="[{\"DeviceName\":\"$root_device\",\"Ebs\":{\"VolumeSize\":8,\"VolumeType\":\"gp3\",\"DeleteOnTermination\":true,\"Encrypted\":true}}]"
gateway=$(aw ec2 run-instances --image-id "$ami" --instance-type "$INSTANCE_TYPE" --count 1 \
--key-name "$KEY_NAME" --subnet-id "$public_subnet" --private-ip-address 10.20.1.10 \
--security-group-ids "$gateway_sg" --no-associate-public-ip-address \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--block-device-mappings "$disk" --user-data "$gateway_user_data" \
--tag-specifications "$(tags instance gateway)" "$(tags volume gateway)" \
--client-token "$vpc-gateway" --query 'Instances[0].InstanceId' --output text)
record GatewayInstanceId "$gateway"
aw ec2 wait instance-running --instance-ids "$gateway"
aw ec2 modify-instance-attribute --instance-id "$gateway" --no-source-dest-check
local return_rt return_cidr
for return_rt in "$public_rt" "$private_rt"; do
for return_cidr in "$LOCAL_CIDR" "$VPN_CIDR"; do
aw ec2 create-route --route-table-id "$return_rt" --destination-cidr-block "$return_cidr" \
--instance-id "$gateway" >/dev/null
done
done
# Registrar a alocacao antes da associacao permite recuperar uma falha parcial.
local eip_allocation eip_association eip_target public_ip
eip_allocation=$(aw ec2 allocate-address --domain vpc --query AllocationId --output text)
record ElasticIpAllocationId "$eip_allocation"
aw ec2 create-tags --resources "$eip_allocation" \
--tags "Key=Lab,Value=$LAB_NAME" "Key=Name,Value=$LAB_NAME-gateway"
public_ip=$(aw ec2 describe-addresses --allocation-ids "$eip_allocation" --query 'Addresses[0].PublicIp' --output text)
record GatewayPublicIp "$public_ip"
eip_association=$(aw ec2 associate-address --allocation-id "$eip_allocation" \
--instance-id "$gateway" --no-allow-reassociation --query AssociationId --output text)
record ElasticIpAssociationId "$eip_association"
eip_target=$(aw ec2 describe-addresses --allocation-ids "$eip_allocation" --query 'Addresses[0].InstanceId' --output text)
[[ "$eip_target" == "$gateway" ]] || { echo 'Elastic IP nao associado ao gateway esperado.' >&2; return 1; }
server=$(aw ec2 run-instances --image-id "$ami" --instance-type "$INSTANCE_TYPE" --count 1 \
--key-name "$KEY_NAME" --subnet-id "$private_subnet" --private-ip-address 10.20.2.10 \
--security-group-ids "$services_sg" --no-associate-public-ip-address \
--metadata-options HttpTokens=required,HttpEndpoint=enabled --block-device-mappings "$disk" \
--tag-specifications "$(tags instance servicos)" "$(tags volume servicos)" \
--client-token "$vpc-servicos" --query 'Instances[0].InstanceId' --output text)
record ServicesInstanceId "$server"
aw ec2 wait instance-running --instance-ids "$server"
local private_public_ip
private_public_ip=$(aw ec2 describe-instances --instance-ids "$server" --query 'Reservations[0].Instances[0].PublicIpAddress' --output text)
[[ "$private_public_ip" == None || -z "$private_public_ip" ]] || { echo 'Servidor privado recebeu IP publico inesperado.' >&2; return 1; }
if [[ "$create_key" == true ]]; then
local ssh_config="$state_dir/${LAB_NAME}-$run_id.ssh-config"
cat > "$ssh_config" <<EOF
Host lab-gateway
HostName $public_ip
User ubuntu
IdentityFile "$key_file"
IdentitiesOnly yes
Host lab-servicos
HostName 10.20.2.10
User ubuntu
IdentityFile "$key_file"
IdentitiesOnly yes
ProxyCommand ssh -F "$ssh_config" -W %h:%p lab-gateway
EOF
record SshConfigFile "$ssh_config"
printf 'Acesso pelo terminal atual:\n ssh -F %q lab-gateway\n ssh -F %q lab-servicos\n' "$ssh_config" "$ssh_config"
echo 'Guarde uma copia privada do arquivo .pem antes de perder acesso a este terminal.'
fi
cat <<EOF
Infraestrutura criada. Diario: $STATE_FILE
Gateway: ubuntu@$public_ip | Servidor privado: ubuntu@10.20.2.10
As EC2 estao running; cloud-init e SSH podem levar mais alguns minutos.
Se usou KEY_NAME preexistente, configure a IdentityFile para ambos os hosts (veja README).
OpenVPN, certificados, intranet e coleta de logs ainda precisam ser configurados.
AWS pronta para rede local $LOCAL_CIDR e tunel $VPN_CIDR; use essas faixas no OpenVPN.
No gateway, apos cloud-init: sysctl net.ipv4.ip_forward deve retornar 1.
A sub-rede privada nao possui saida para Internet. Veja README para instalar pacotes.
Elastic IP do gateway: $public_ip (alocacao: $eip_allocation).
O endereco permanece ao parar/iniciar a instancia enquanto a alocacao e associacao forem mantidas.
Ao desmontar o laboratorio, desassocie e libere esse Elastic IP para encerrar sua cobranca.
EOF
}
# Executar somente depois de Bash ter lido a funcao inteira do pipe.
main "$@"