diff --git a/crialab.sh b/crialab.sh index d2bb745..65ee634 100644 --- a/crialab.sh +++ b/crialab.sh @@ -5,38 +5,39 @@ set -Eeuo pipefail export AWS_PAGER="" AWS_CLI_AUTO_PROMPT=off main() { - local mode="${1:---plan}" + local mode="${1:---apply}" case "$mode" in --plan|--apply) ;; *) echo 'Use --plan ou --apply' >&2; return 2;; esac - for tool in aws python3; do + 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:-modulo12-vpn}" INSTANCE_TYPE="${INSTANCE_TYPE:-t3.micro}" - LOCAL_CIDR="${LOCAL_CIDR:-192.168.10.0/24}" - : "${KEY_NAME:?Defina KEY_NAME com um par de chaves EC2 existente na regiao.}" - : "${ADMIN_CIDR:?Defina ADMIN_CIDR com o IPv4 publico de administracao seguido de /32.}" - : "${VPN_PEER_CIDR:?Defina VPN_PEER_CIDR com o IPv4 publico de saida do laboratorio seguido de /32.}" + 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 + if [[ -z "${ADMIN_CIDR:-}" ]]; then + ADMIN_CIDR="$(curl -4 --noproxy '*' -fsSL --connect-timeout 10 --max-time 20 https://checkip.amazonaws.com)/32" + echo 'SSH autorizado para o IP de saida deste terminal. Use SSH a partir dele.' + fi [[ "$LAB_NAME" =~ ^[a-zA-Z][a-zA-Z0-9-]{0,39}$ ]] || { echo 'LAB_NAME invalido' >&2; return 2; } - # CIDRs canonicos evitam erros e sobreposicao com VPC/tunel planejados. - python3 - "$ADMIN_CIDR" "$VPN_PEER_CIDR" "$LOCAL_CIDR" <<'PY' + # Validar somente a origem do acesso administrativo por SSH. + python3 - "$ADMIN_CIDR" <<'PY' import ipaddress, sys -admin, peer, local = [ipaddress.IPv4Network(x, strict=True) for x in sys.argv[1:]] -for label, net in [('ADMIN_CIDR', admin), ('VPN_PEER_CIDR', peer)]: - if net.prefixlen != 32 or not net.network_address.is_global: - raise SystemExit(f'{label} deve ser um IPv4 publico /32 real.') -private = [ipaddress.ip_network(x) for x in ['10.0.0.0/8','172.16.0.0/12','192.168.0.0/16']] -if not any(local.subnet_of(x) for x in private): - raise SystemExit('LOCAL_CIDR deve ser uma rede privada RFC1918.') -for cidr in ['10.20.0.0/16', '10.8.0.0/24']: - if local.overlaps(ipaddress.ip_network(cidr)): - raise SystemExit(f'LOCAL_CIDR sobrepoe {cidr}.') +admin = ipaddress.IPv4Network(sys.argv[1], strict=True) +if admin.prefixlen != 32 or not admin.network_address.is_global: + raise SystemExit('ADMIN_CIDR deve ser um IPv4 publico /32 real.') PY aw() { aws --region "$REGION" "$@"; } local account existing ami root_device az account=$(aw sts get-caller-identity --query Account --output text) - aw ec2 describe-key-pairs --key-names "$KEY_NAME" >/dev/null + 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 @@ -59,9 +60,9 @@ 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 -Rede local: $LOCAL_CIDR | Tunel planejado: 10.8.0.0/24 SSH publico permitido de: $ADMIN_CIDR -OpenVPN UDP/1194 permitido de: $VPN_PEER_CIDR +Criar chave SSH automaticamente: $create_key +VPN: configuracao, encaminhamento, rotas entre sites e porta UDP ficam para a aula. 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 @@ -74,11 +75,24 @@ EOF umask 077 local state_dir="${LAB_STATE_DIR:-$PWD/aws-lab-state}" mkdir -p "$state_dir" - STATE_FILE="$state_dir/${LAB_NAME}-$(date -u +%Y%m%dT%H%M%SZ)-$$.txt" + 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 AdminCidr "$ADMIN_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. + (set -o noclobber; aw ec2 create-key-pair --key-name "$KEY_NAME" --key-type rsa --key-format pem \ + --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" @@ -105,32 +119,14 @@ EOF 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 "$ADMIN_CIDR" - allow "$gateway_sg" udp 1194 "$VPN_PEER_CIDR" - # Pacotes dos servidores atravessam o gateway para chegar a rede local. - aw ec2 authorize-security-group-ingress --group-id "$gateway_sg" --ip-permissions \ - '[{"IpProtocol":"-1","IpRanges":[{"CidrIp":"10.20.2.0/24","Description":"Encaminhamento da sub-rede privada para VPN"}]}]' >/dev/null - local cidr - for cidr in "$LOCAL_CIDR" 10.8.0.0/24 10.20.1.10/32; do - allow "$services_sg" tcp 22 "$cidr" - allow "$services_sg" tcp 80 "$cidr" - allow "$services_sg" icmp -1 "$cidr" - done - # Sem pacotes externos: bootstrap funciona inclusive na sub-rede privada isolada. - 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] -' + # Administracao do servidor privado via salto SSH, sem depender de VPN. + allow "$services_sg" tcp 22 10.20.1.10/32 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" \ + --block-device-mappings "$disk" \ --tag-specifications "$(tags instance gateway)" "$(tags volume gateway)" \ --client-token "$vpc-gateway" --query 'Instances[0].InstanceId' --output text) record GatewayInstanceId "$gateway" @@ -147,9 +143,6 @@ runcmd: 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; } - aw ec2 modify-instance-attribute --instance-id "$gateway" --no-source-dest-check - aw ec2 create-route --route-table-id "$private_rt" --destination-cidr-block "$LOCAL_CIDR" --instance-id "$gateway" >/dev/null - aw ec2 create-route --route-table-id "$private_rt" --destination-cidr-block 10.8.0.0/24 --instance-id "$gateway" >/dev/null 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 \ @@ -161,12 +154,30 @@ runcmd: 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" <