Terraform

HashiCorp Stack end-to-end: od budowania obrazów w Packerze do kontroli dostępu przez Boundary

2026-02-09
Podziel się

Spis treści:

Wprowadzenie: ekosystem czy uzależnienie od dostawcy

Większość platform cloud-native opiera się na pojedynczym monolitycznym rozwiązaniu lub wymagają integracji między narzędziami różnych dostawców. AWS oferuje kompletny stos (CloudFormation, Systems Manager, ECS), ale tworzy uzależnienie od dostawcy. Ekosystem Kubernetes zapewnia elastyczność, ale wymaga integracji dziesiątek projektów CNCF, z których każdy ma własny API i model operacyjny.

HashiCorp od lat buduje alternatywne podejście: zestaw dedykowanych narzędzi, z których każde rozwiązuje konkretny problem, ale wszystkie są zaprojektowane do współdziałania przez wspólne protokoły i wzorce. Terraform zarządza infrastrukturą. Packer buduje obrazy maszyn. Vault zarządza sekretami. Consul dostarcza wykrywanie usług i sieć. Nomad orkiestruje obciążenia. Boundary zapewnia bezpieczny dostęp.

Kluczowa różnica: każde narzędzie może funkcjonować niezależnie (możesz używać tylko Terraform + Vault bez reszty stosu), ale integracja między nimi odblokowuje znacznie większą wartość. Terraform może dynamicznie pobierać dane uwierzytelniające z Vault. Packer może rejestrować artefakty w rejestrze HCP Packer, które Terraform później odnosi. Nomad automatycznie rejestruje usługi w Consul. Boundary używa Vault do wstrzykiwania danych uwierzytelniających.

W tym artykule zaprezentujemy kompleksowy scenariusz integracji od początku do końca, pokazujący jak poszczególne komponenty stosu HashiCorp współpracują, tworząc spójny system od budowy infrastruktury po bezpieczny zdalny dostęp. Skupimy się na aspektach technicznych integracji — protokołach, API, wzorcach uwierzytelniania i zarządzaniu stanem.

Architektura referencyjna: co budujemy

Scenariusz: Scenariusz: Organizacja wdraża aplikację web (3-warstwowa: frontend, API backend, PostgreSQL) w środowisku wielochmurowym (AWS + VMware lokalnie).

Komponenty stosu:

  • Packer: Buduje złote obrazy (AMI dla AWS, szablon VM dla VMware) z preinstalowanym agentem Consul, monitorowaniem, utwardzeniem;
  • HCP Packer: Centralne repozytorium artefaktów ze śledzeniem metadanych (CVE, zgodność, wersje);
  • Terraform: Tworzy infrastrukturę (VPC, instancje EC2, RDS) używając złotych obrazów z HCP Packer;
  • Vault: Zarządza sekretami — dane uwierzytelniające AWS dla Terraform, dane uwierzytelniające bazy danych dla aplikacji, certyfikaty SSH dla Boundary;
  • Consul: Wykrywanie usług dla aplikacji, magazyn KV dla zdalnego stanu Terraform, sprawdzanie stanu zdrowia;
  • Nomad: Orkiestruje kontenery frontend/backend na przygotowanej infrastrukturze;
  • Boundary: Bezpieczny zdalny dostęp do instancji i baz danych z wstrzykiwaniem danych uwierzytelniających z Vault.

Przepływ danych: Packer + Ansible → Rejestr HCP Packer → Terraform (+ sekrety Vault, + backend stanu Consula) → EC2/VMware VMs → deployment Nomada → rejestracja usług w Consulu → dostęp Boundary (+ brokering danych uwierzytelniających Vault).

Krok 1: Budowa obrazów w Packerze z HCP Packer Registry

Budowa złotego obrazu dla EC2

Szablon Packera (format HCL2):

# aws-ubuntu.pkr.hcl
packer {
  required_plugins {
    amazon = {
      source  = "github.com/hashicorp/amazon"
      version = "~> 1.3"
    }
    ansible = {
      source  = "github.com/hashicorp/ansible"
      version = "~> 1.1"
    }
  }
}

variable "aws_region" {
  type    = string
  default = "us-east-1"
}

variable "consul_version" {
  type    = string
  default = "1.18.0"
}

# HCP Packer integration
hcp_packer_registry {
  bucket_name = "ubuntu-consul-base"
  description = "Ubuntu 22.04 with Consul agent and security hardening"
  
  bucket_labels = {
    "os"           = "ubuntu-22.04"
    "environment"  = "production"
    "compliance"   = "cis-level-1"
  }
}

data "amazon-ami" "ubuntu" {
  filters = {
    name                = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
    root-device-type    = "ebs"
    virtualization-type = "hvm"
  }
  most_recent = true
  owners      = ["099720109477"] # Canonical
  region      = var.aws_region
}

source "amazon-ebs" "ubuntu_consul" {
  ami_name      = "ubuntu-22.04-consul-${var.consul_version}-{{timestamp}}"
  instance_type = "t3.medium"
  region        = var.aws_region
  source_ami    = data.amazon-ami.ubuntu.id
  
  ssh_username = "ubuntu"
  
  tags = {
    Name          = "Ubuntu 22.04 Consul Base"
    ConsulVersion = var.consul_version
    BuildDate     = "{{timestamp}}"
    Packer        = "true"
  }
  
  # HCP Packer artifact metadata
  run_tags = {
    PackerBuild = "true"
  }
}

build {
  sources = ["source.amazon-ebs.ubuntu_consul"]
  
  # Install base packages
  provisioner "shell" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y curl unzip jq",
      "sudo apt-get upgrade -y"
    ]
  }
  
  # Install Consul
  provisioner "shell" {
    inline = [
      "curl -fsSL https://releases.hashicorp.com/consul/${var.consul_version}/consul_${var.consul_version}_linux_amd64.zip -o /tmp/consul.zip",
      "sudo unzip /tmp/consul.zip -d /usr/local/bin/",
      "sudo chmod +x /usr/local/bin/consul",
      "consul version"
    ]
  }
  
  # Apply Ansible hardening playbook
  provisioner "ansible" {
    playbook_file = "./playbooks/cis-hardening.yml"
    user          = "ubuntu"
    
    extra_arguments = [
      "--extra-vars",
      "consul_version=${var.consul_version}"
    ]
  }
  
  # Generate SBOM for supply chain security
  provisioner "shell" {
    inline = [
      "sudo syft packages -o spdx-json > /tmp/sbom.json"
    ]
  }
  
  # Upload SBOM to HCP Packer
  post-processor "manifest" {
    output     = "manifest.json"
    strip_path = true
  }
}

Integracja z HCP Packer Registry

Kluczowe koncepty:

  1. Buckets: Logiczne grupowanie typów obrazów. Każdy bucket (np. ubuntu-consul-base) reprezentuje określony typ obrazu;
  2. Iterations (Wersje): Każda kompilacja Packer tworzy nową iterację w bucket. Iteracja zawiera metadane: znacznik czasu, wersję Packer, wersje wtyczek, bazowy AMI, artefakty kompilacji;
  3. Channels (Kanały): Nazwane wskaźniki do konkretnych iteracji (analogia do tagów Git). Przykład: kanał produkcyjny wskazuje na iterację z 15 stycznia 2026, kanał przejściowy na nowszą iterację z 1 lutego;
  4. Śledzenie artefaktów: HCP Packer śledzi artefakty w różnych regionach i platformach. Jedna kompilacja może produkować AMI w eu-west-1, eu-west-2, eu-central-1 — wszystkie są traktowane jako artefakty tej samej iteracji;
  5. Przechowywanie SBOM: Packer 1.10+ wspiera przesyłanie Software Bill of Materials (SBOM) do HCP Packer. SBOM zawiera listę wszystkich pakietów oprogramowania w obrazie — kluczowe dla bezpieczeństwa łańcucha dostaw i śledzenia podatności.

Budowanie pakietu:

export HCP_CLIENT_ID="..." 
export HCP_CLIENT_SECRET="..."
export HCP_PROJECT_ID="..."

packer init aws-ubuntu.pkr.hcl
packer build -var "consul_version=1.18.0" aws-ubuntu.pkr.hcl

Wynik:

  • AMI w AWS (np. ami-0abcd1234);
  • Iteracja w bucket HCP Packera ubuntu-consul-base;
  • Metadane: bazowy AMI, wersja Consula, znacznik czasu budowy/kompilacji, SBOM.

Interfejs użytkownika HCP Packera: Administrator może zobaczyć wszystkie iteracje, porównać SBOM między wersjami, sprawdzić podatności CVE (integracja z bazami danych podatności) i zarządzać kanałami.

Zarządzanie kanałami (channels) dla kontrolowanego wdrożenia

Scenariusz: Nowa kompilacja obrazu z Consul 1.18.1 (łatka bezpieczeństwa). Chcemy przetestować w środowisku przejściowym przed wdrożeniem do produkcji.

# Promote latest iteration to staging channel
packer channel assign ubuntu-consul-base staging --iteration-id iter-abc123

# Po testach staging — promote do production
packer channel assign ubuntu-consul-base production --iteration-id iter-abc123

Kod infrastruktury Terraform odnosi się do nazwy kanału, nie konkretnego ID AMI. Gdy kanał zostaje zaktualizowany, kolejne polecenie terraform apply automatycznie użyje nowego obrazu.

Krok 2: Vault jako podstawa zarządzania sekretami

Dynamiczne dane uwierzytelniające AWS dla Terraform

Problem: Zakodowane na stałe klucze dostępu AWS w kodzie Terraform lub zmiennych środowiskowych to antywzorzec bezpieczeństwa. Klucze są statyczne, długotrwałe i trudne do rotacji.

Rozwiązanie: Silnik sekretów AWS Vault generuje dynamiczne, krótkotrwałe dane uwierzytelniające AWS na żądanie.

Konfiguracja Vault:

# Enable AWS secrets engine
vault secrets enable -path=aws aws

# Configure AWS master credentials (Vault używa ich do generowania temporary credentials)
vault write aws/config/root \
  access_key=AKIA... \
  secret_key=... \
  region=eu-west-1

# Define role — Terraform infrastruktura ma potrzebować EC2/VPC/RDS permissions
vault write aws/roles/terraform-infra \
  credential_type=assumed_role \
  role_arns=arn:aws:iam::123456789012:role/TerraformInfraRole \
  default_sts_ttl=3600 \
  max_sts_ttl=7200

Jak to działa:

  1. Terraform uwierzytelnia się do Vault (przez AppRole, Kubernetes Auth, etc.);
  2. Terraform żąda tymczasowych danych uwierzytelniających AWS: vault read aws/creds/terraform-infra;
  3. Vault używa AWS master credentials do wywołania AWS STS AssumeRole;
  4. AWS zwraca tymczasowe dane uwierzytelniające (klucz dostępu + klucz tajny + token sesji) ważne 1 godzinę;
  5. Terraform używa tych danych uwierzytelniających do tworzenia infrastruktury;
  6. Po 1h dane uwierzytelniające automatycznie wygasają — zero ręcznej rotacji.

Korzyści:

  1. Audit trail: Każde żądanie danych uwierzytelniających jest logowane w Vault (kto, kiedy, z jakiego IP);
  2. Najmniejsze uprawnienia: Rola może mieć bardzo szczegółowe uprawnienia IAM;
  3. Automatyczna rotacja: Dane uwierzytelniające są krótkotrwałe i automatycznie odnawiane;
  4. Unieważnienie: Administrator może unieważnić (revoke) wszystkie aktywne dane uwierzytelniające dla danej roli w Vault.

Konfiguracja dostawcy Terraform

# providers.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    vault = {
      source  = "hashicorp/vault"
      version = "~> 4.0"
    }
  }
}

provider "vault" {
  address = "https://vault.company.com"
  # Authentication przez environment variable VAULT_TOKEN
  # lub Kubernetes service account (w CI/CD)
}

data "vault_aws_access_credentials" "creds" {
  backend = "aws"
  role    = "terraform-infra"
  type    = "sts"  # STS AssumeRole
}

provider "aws" {
  region     = "eu-west-1"
  access_key = data.vault_aws_access_credentials.creds.access_key
  secret_key = data.vault_aws_access_credentials.creds.secret_key
  token      = data.vault_aws_access_credentials.creds.security_token
}

Uwierzytelnianie Vault w CI/CD: Przy korzystaniu z GitLab CI/CD, Terraform może uwierzytelniać się do Vault używając tokenu JWT z GitLab:

provider "vault" {
  address = "https://vault.company.com"
  
  auth_login_jwt {
    role = "gitlab-terraform"
    jwt  = var.gitlab_jwt_token  # Auto-provided by GitLab CI
  }
}

Vault weryfikuje podpis JWT, sprawdza czy ID/ref projektu pasuje do polityki dla danej roli i wydaje token Vault.

Dane uwierzytelniające bazy danych dla aplikacji

Silnik sekretów baz danych Vault może generować dynamiczne dane uwierzytelniające dla PostgreSQL, MySQL, MongoDB etc.

Konfiguracja:

# Enable database secrets engine
vault secrets enable database

# Configure PostgreSQL connection
vault write database/config/postgres \
  plugin_name=postgresql-database-plugin \
  allowed_roles="app-backend" \
  connection_url="postgresql://{{username}}:{{password}}@postgres.internal:5432/myapp" \
  username="vault-admin" \
  password="..."

# Define role — Vault będzie tworzyć DB users z tymi permissions
vault write database/roles/app-backend \
  db_name=postgres \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
    GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

Podczas działania (runtime): Aplikacja backendowa przy starcie żąda danych uwierzytelniających:

vault read database/creds/app-backend
# Output:
# username: v-appbackend-abc123
# password: A1b2C3d4...

Vault utworzył tymczasowego użytkownika bazy danych v-appbackend-abc123 w PostgreSQL z uprawnieniami zdefiniowanymi w roli. Po 1h, Vault automatycznie usuwa tego użytkownika (REVOKE + DROP ROLE).

Rotacja: Aplikacja może odnowić dzierżawę przed wygaśnięciem (biblioteki klienckie Vault robią to automatycznie).

Krok 3: Consul jako platforma wielofunkcyjna

Przypadek użycia 1: Wykrywanie usług dla zadań Nomad

Consul fundamentalnie zmienia sposób, w jaki aplikacje znajdują swoje zależności. Zamiast zakodowanych na stałe adresów IP lub nazw DNS, aplikacje odpytują katalog usług Consul.

Specyfikacja zadania Nomad z integracją z Consul:

job "api-backend" {
  datacenters = ["dc1"]
  type        = "service"
  
  group "api" {
    count = 3
    
    network {
      port "http" {
        to = 8080
      }
    }
    
    # Consul service registration
    service {
      name     = "api-backend"
      port     = "http"
      provider = "consul"
      
      tags = [
        "version-2.1.0",
        "production"
      ]
      
      # Health check
      check {
        type     = "http"
        path     = "/health"
        interval = "10s"
        timeout  = "2s"
      }
      
      # Service mesh integration
      connect {
        sidecar_service {}
      }
    }
    
    task "api" {
      driver = "docker"
      
      config {
        image = "company/api-backend:2.1.0"
        ports = ["http"]
      }
      
      # Application retrieves DB credentials from Vault
      template {
        data = <<EOF
{{ with secret "database/creds/app-backend" }}
DB_USERNAME={{ .Data.username }}
DB_PASSWORD={{ .Data.password }}
{{ end }}
EOF
        destination = "secrets/db.env"
        env         = true
      }
    }
  }
}

Przepływ sterowania:

  1. Nomad planuje zadanie api-backend na dostępnym węźle;
  2. Klient Nomad automatycznie rejestruje usługę w Consulu (nazwa: api-backend, port, sprawdzanie stanu zdrowia);
  3. Consul rozpoczyna sprawdzanie stanu zdrowia (HTTP GET /health co 10s);
  4. Usługa pojawia się w katalogu Consula z tagami i metadanymi;
  5. Aplikacja frontendowa może odkryć backend: curl http://api-backend.service.consul:8080/ .

DNS resolution: Interfejs DNS Consul (*.service.consul) automatycznie rozwiązuje adresy do zdrowych instancji. Jeśli jedna z 3 replik backendu nie przejdzie pozytywnie sprawdzania stanu zdrowia, DNS Consul zwraca tylko 2 zdrowe adresy IP.

Service mesh (Consul Connect): connect { sidecar_service {} } dokonuje deploymentu Envoy (proxy sidecar) obok aplikacji. Wszystkie żądania między usługami przechodzą przez sidecar, który wymusza szyfrowanie mTLS i intencje (polityki autoryzacji).

Przypadek użycia 2: Zdalny backend stanu Terraform

Problem: Plik stanu Terraform zawiera dane wrażliwe (ID zasobów, adresy IP, wyjścia które mogą zawierać dane uwierzytelniające). Przechowywanie lokalnie lub w Git to ryzyko bezpieczeństwa a backendy chmurowe (S3, Azure Blob) wymagają dodatkowej infrastruktury.

Rozwiązanie: Magazyn KV Consul jako backend Terraform.

Konfiguracja backendu:

# backend.tf
terraform {
  backend "consul" {
    address = "consul.company.com:8500"
    scheme  = "https"
    path    = "terraform/production/infrastructure"
    
    # Locking configuration
    lock     = true
    gzip     = true
  }
}

Mechanizm blokowania stanu:

Backend Consula implementuje blokowanie stanu przez sesje Consul Sessions:

  1. Terraform tworzy sesję Consula (tymczasowa instancja z TTL);
  2. Terraform próbuje pozyskać blokadę na kluczu terraform/production/infrastructure/.lock używając sesji;
  3. Consul zapewnia atomowe porównanie i ustawienie (compare-and-set) — tylko pierwszy żądający uzyskuje blokadę;
  4. Terraform wykonuje plan/apply i zapisuje nowy stan do terraform/production/infrastructure;
  5. Terraform zwalnia blokadę (usuwa klucz .lock);
  6. Jeśli Terraform ulega awarii, limit czasu sesji (domyślnie 15s) automatycznie zwalnia blokadę.

Ochrona przed współbieżnością: Drugi programista lub zadanie CI próbujący wykonać terraform apply dostaje poniższy błąd:

Error: Error acquiring the state lock

Error message: Locked by: user@hostname
Lock Info:
  ID:        abc123...
  Path:      terraform/production/infrastructure
  Operation: OperationTypeApply
  Who:       user@hostname
  Version:   1.7.0
  Created:   2026-02-04 10:30:15

Korzyści::

  • Brak dodatkowej infrastruktury: Jeśli już używasz Consul do wykrywania usług, backend stanu jest darmowy;
  • Szyfrowanie w spoczynku: Magazyn KV Consul może być szyfrowany;
  • Kontrola dostępu: Reguły ACL Consula kontrolują, kto może czytać i zapisywać stan;
  • Wersjonowanie: Migawki Consula mogą tworzyć kopie zapasowe historii stanu.

Przypadek użycia 3: Magazyn KV konfiguracji

Aplikacje mogą używać KV Consul do dynamicznej konfiguracji:

consul kv put config/api-backend/log_level DEBUG
consul kv put config/api-backend/feature_flags/new_auth true

Aplikacja obserwuje klucze KV Consula i przeładowuje konfigurację bez restartu:

// Go example
watch, _ := consul.KV().Watch("config/api-backend/", nil)
for {
    kvPair := <-watch
    updateConfig(kvPair.Value)
}

Krok 4: Prowizjonowanie infrastruktury w Terraform

Odwoływanie się do artefaktów HCP Packer

Problem: Zakodowane na stałe ID AMI w kodzie Terraform są kruche. Każda nowa kompilacja Packera wymaga w takiej sytuacji ręcznej aktualizacji kodu Terraform.

Rozwiązanie: Źródło danych (data source) HCP Packera.

# instances.tf
data "hcp_packer_artifact" "ubuntu_consul" {
  bucket_name    = "ubuntu-consul-base"
  channel_name   = "production"
  platform       = "aws"
  region         = "eu-west-1"
}

resource "aws_instance" "consul_server" {
  count         = 3
  ami           = data.hcp_packer_artifact.ubuntu_consul.external_identifier
  instance_type = "t3.medium"
  
  tags = {
    Name          = "consul-server-${count.index}"
    PackerBucket  = data.hcp_packer_artifact.ubuntu_consul.bucket_name
    PackerChannel = "production"
    BuildID       = data.hcp_packer_artifact.ubuntu_consul.build_id
  }
}

Przepływ kontroli::

  1. Terraform odpytuje API HCP Packera: "Jaki artefakt jest w kanale produkcyjnym dla bucket ubuntu-consul-base w regionie eu-west-1?";
  2. HCP Packer zwraca ID AMI (np. ami-0abcd1234) oraz metadane;
  3. Kontrola dostępu: Reguły ACL Consula kontrolują, kto może czytać i zapisywać stan;
  4. Wersjonowanie: Migawki Consula mogą tworzyć kopie zapasowe historii stanu.

Korzyści:

Separacja (decoupling): Zespół Packera może wydawać nowe obrazy (i aktualizować kanał) bez potrzeby koordynacji z użytkownikami Terraform.

Kontrolowany roll-out: Aktualizacja kanału produkcyjnego → kolejne polecenie terraform apply automatycznie użyje nowego AMI.

Wycofanie: W przypadku problemu, przywrócenie kanału do poprzedniej iteracji.

Kompletny przykład infrastruktury

# main.tf
module "vpc" {
  source = "./modules/vpc"
  
  cidr_block = "10.0.0.0/16"
  azs        = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
}

module "consul_cluster" {
  source = "./modules/consul"
  
  ami_id         = data.hcp_packer_artifact.ubuntu_consul.external_identifier
  instance_count = 3
  subnet_ids     = module.vpc.private_subnet_ids
  
  # Consul cluster bootstrap configuration
  datacenter = "eu-west-1"
}

module "nomad_cluster" {
  source = "./modules/nomad"
  
  ami_id              = data.hcp_packer_artifact.ubuntu_consul.external_identifier
  server_count        = 3
  client_count        = 5
  subnet_ids          = module.vpc.private_subnet_ids
  consul_address      = module.consul_cluster.server_addresses
}

# RDS PostgreSQL z Vault-managed credentials
resource "aws_db_instance" "app_database" {
  identifier          = "app-db-prod"
  engine              = "postgres"
  engine_version      = "15.5"
  instance_class      = "db.t3.medium"
  allocated_storage   = 100
  
  username = "vaultadmin"
  password = data.vault_generic_secret.rds_master_password.data["password"]
  
  vpc_security_group_ids = [aws_security_group.database.id]
  db_subnet_group_name   = aws_db_subnet_group.database.name
  
  # Backup configuration
  backup_retention_period = 7
  backup_window          = "03:00-04:00"
  
  tags = {
    ManagedBy = "Terraform"
    VaultPath = "database/config/postgres"
  }
}

# Output dla Vault database configuration
output "rds_endpoint" {
  value     = aws_db_instance.app_database.endpoint
  sensitive = true
}

Wykonanie w Terraform:

export VAULT_ADDR="https://vault.company.com"
export VAULT_TOKEN="..."
export HCP_CLIENT_ID="..."
export HCP_CLIENT_SECRET="..."
export CONSUL_HTTP_ADDR="https://consul.company.com:8500"

terraform init
terraform plan -out=tfplan
terraform apply tfplan

Przepływ stanu::

  1. Terraform init konfiguruje backend Consula;
  2. Terraform blokuje stan w Consul (terraform/production/infrastructure/.lock);
  3. Terraform odpytuje HCP Packer dla ID AMI;
  4. Terraform żąda danych uwierzytelniających AWS z Vault;
  5. Terraform tworzy infrastrukturę (VPC, EC2, RDS);
  6. Terraform zapisuje nowy stan do Consul KV;
  7. Terraform zwalnia blokadę.

Krok 5: Orkiestracja zadań w Nomad

Deployment aplikacji z integracją z Vault

Specyfikacja zadania frontendowego:

job "frontend" {
  datacenters = ["dc1"]
  
  group "web" {
    count = 5
    
    network {
      port "http" {
        static = 80
        to     = 8080
      }
    }
    
    service {
      name     = "frontend"
      port     = "http"
      provider = "consul"
      
      tags = [
        "traefik.enable=true",
        "traefik.http.routers.frontend.rule=Host(`app.company.com`)"
      ]
      
      check {
        type     = "http"
        path     = "/"
        interval = "10s"
        timeout  = "2s"
      }
      
      connect {
        sidecar_service {
          proxy {
            # Upstream services — frontend może komunikować się tylko z api-backend
            upstreams {
              destination_name = "api-backend"
              local_bind_port  = 8081
            }
          }
        }
      }
    }
    
    task "nginx" {
      driver = "docker"
      
      config {
        image = "company/frontend:2.1.0"
        ports = ["http"]
      }
      
      env {
        API_BACKEND_URL = "http://localhost:8081"  # Consul Connect upstream
      }
      
      resources {
        cpu    = 500
        memory = 256
      }
    }
  }
}

Specyfikacja zadania backendowego (fragment z integracją Vault):

task "api" {
  driver = "docker"
  
  config {
    image = "company/api-backend:2.1.0"
  }
  
  # Vault integration przez Nomad native support
  vault {
    policies = ["app-backend"]
    change_mode   = "restart"  # Restart task when secrets rotate
    change_signal = "SIGTERM"
  }
  
  # Template z Consul Template syntax — Nomad używa Consul Template engine
  template {
    data = <<EOF
{{ with secret "database/creds/app-backend" }}
DATABASE_URL=postgresql://{{ .Data.username }}:{{ .Data.password }}@{{ env "NOMAD_UPSTREAM_ADDR_postgres" }}/myapp
{{ end }}

{{ range service "cache-redis" }}
REDIS_URL=redis://{{ .Address }}:{{ .Port }}
{{ end }}
EOF
    
    destination = "secrets/app.env"
    env         = true
    
    # Vault lease renewal
    vault_grace = "15s"
  }
}

Przepływ kontroli::

  1. Nomad robi deployment zadania api-backend;
  2. Klient Nomad uwierzytelnia się do Vault (używając tokenu Nomad);
  3. Klient Nomad żąda tokenu Vault z polityką app-backend;
  4. Consul Template (wbudowany w Nomad) generuje szablon — odpytuje Vault o dane uwierzytelniające bazy danych;
  5. Zmienne środowiskowe są wypełniane i zadanie startuje;
  6. Nomad automatycznie odnawia dzierżawę Vault (dane uwierzytelniające bazy danych są ważne 1h, Nomad odnawia co 30 min);
  7. Gdy dzierżawa nie może być odnowiona (wygasła lub unieważniona), Nomad restartuje zadanie z nowymi danymi uwierzytelniającymi (zgodnie z change_mode = "restart").

Intencje Consul Connect

# Frontend może komunikować się z backendem
consul intention create -allow frontend api-backend

# Backend może komunikować się z bazą danych
consul intention create -allow api-backend postgres

# Wszystkie inne połączenia są domyślnie odrzucane (zero-trust)

Sidecar Envoy przy aplikacji frontendowej weryfikuje intencje przed przekazaniem żądania do backendu. Żądanie bez prawidłowej intencji jest odrzucane na poziomie proxy (aplikacja nawet nie widzi żądania).

Krok 6: Bezpieczny dostęp przez Boundary poprzez brokering danych uwierzytelniających w Vault

Architektura Boundary

Boundary rozwiązuje problem bezpiecznego zdalnego dostępu do infrastruktury bez VPN, hostów bastionu lub współdzielonych danych uwierzytelniających.

Komponenty::

  • Boundary Controller: Serwer API, uwierzytelnianie użytkowników (OIDC, LDAP), zarządzanie sesjami;
  • Boundary Worker: Proxy dla połączeń, może być wdrożony w różnych sieciach/chmurach;
  • Targets (Cele): Zasoby, do których użytkownicy chcą się połączyć (hosty SSH, serwery RDP, bazy danych, klastry Kubernetes);
  • Integracja Vault: Pośredniczenie danych uwierzytelniających — Boundary żąda danych uwierzytelniających z Vault i wstrzykuje do sesji.

Konfiguracja dostępu SSH do instancji EC2

1. Konfiguracja Vault SSH Secrets Engine:

# Enable SSH secrets engine
vault secrets enable ssh

# Configure CA dla signed certificates
vault write ssh/config/ca \
  generate_signing_key=true

# Create role — Boundary będzie używać tej roli do żądań certyfikatów SSH 
vault write ssh/roles/boundary-ssh \
  key_type=ca \
  ttl=5m \
  max_ttl=10m \
  algorithm_signer=rsa-sha2-512 \
  allowed_users="ubuntu,ec2-user" \
  allowed_extensions="permit-pty,permit-port-forwarding" \
  default_extensions="permit-pty=true"

2. Konfiguracja docelowych instancji EC2:

Instancje EC2 muszą mieć dodany certyfikat CA Vault do listy zaufanych urzędów certyfikacji.

# Get Vault CA public key
curl -s https://vault.company.com/v1/ssh/config/ca | jq -r '.data.public_key' | sudo tee -a /etc/ssh/trusted-user-ca-keys.pem

# Configure sshd
echo "TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem" | sudo tee -a /etc/ssh/sshd_config
sudo systemctl restart sshd

3. Konfiguracja docelowego zasobu dla Boundary:

# boundary-config.hcl
resource "boundary_target" "ec2_instances" {
  name         = "Production EC2 Instances"
  description  = "SSH access to production EC2 instances"
  type         = "ssh"
  scope_id     = boundary_scope.project.id
  
  # Network address — może być IP, FQDN, lub dynamiczne (Consul DNS)
  address      = "consul-server-0.node.consul"
  default_port = 22
  
  # Vault credential library
  brokered_credential_source_ids = [
    boundary_credential_library_vault_ssh_certificate.ssh_certs.id
  ]
}

resource "boundary_credential_library_vault_ssh_certificate" "ssh_certs" {
  name            = "Vault SSH Certificates"
  description     = "Dynamic SSH certificates from Vault"
  credential_store_id = boundary_credential_store_vault.vault.id
  path            = "ssh/sign/boundary-ssh"
  username        = "ubuntu"
  
  # Key type and bits
  key_type = "ed25519"
  
  # Extensions
  extensions = {
    permit-pty = ""
  }
}

resource "boundary_credential_store_vault" "vault" {
  name        = "Vault Credential Store"
  address     = "https://vault.company.com"
  token       = vault_token.boundary.client_token
  namespace   = "admin"
  
  # Boundary periodically renews Vault token
  token_hmac  = vault_token.boundary.accessor
}

4. Scenariusz uzyskiwania połączenia przez użytkownika:

# User authenticate do Boundary (OIDC)
boundary authenticate oidc -auth-method-id=ampw_1234567890

# List available targets
boundary targets list -scope-id=p_1234567890

# Connect to target
boundary connect ssh -target-id=ttcp_1234567890

Przepływ sterowania::

  1. Użytkownik uwierzytelnia się do Boundary Controller (tu: OIDC z Azure AD);
  2. Boundary sprawdza w ustawieniach RBAC — czy użytkownik ma uprawnienia do docelowego zasobu (target) ttcp_1234567890;
  3. Użytkownik żąda połączenia;
  4. Boundary Controller komunikuje się z Vault:
    • Boundary uwierzytelnia się do Vault (przez dedykowany token Vault);
    • Boundary żąda certyfikatu SSH: POST /v1/ssh/sign/boundary-ssh z payload: { "public_key": "", "valid_principals": "ubuntu" };
    • Vault zwraca podpisany certyfikat SSH ważny 5 minut;
  5. Boundary Controller przekazuje informację o sesji do Boundary Worker (może znajdować się w AWS VPC z instancjami EC2);
  6. Boundary Worker nawiązuje połączenie SSH do instancji docelowej używając podpisanego certyfikatu;
  7. Użytkownik otrzymuje sesję SSH — nigdy nie widzi certyfikatu ani klucza prywatnego;
  8. Po 5 minutach certyfikat wygasa — połączenie jest zamykane (lub odnawiane jeśli sesja jest dalej aktywna).

Wstrzykiwanie uwierzytelnień (credential injection): Użytkownik nie musi wiedzieć o Vault, certyfikatach SSH, ani zarządzać ręcznie uwierzytelnieniami. Boundary zajmuje się tym wszystkim.

Dostęp do baz danych przez Boundary

Boundary wspiera protokoły bazodanowe (PostgreSQL, MySQL) ze wstrzykiwaniem uwierzytelnień (credential injection):

resource "boundary_target" "postgres_database" {
  name         = "Production PostgreSQL"
  type         = "tcp"
  default_port = 5432
  address      = aws_db_instance.app_database.address
  
  brokered_credential_source_ids = [
    boundary_credential_library_vault.postgres_creds.id
  ]
}

resource "boundary_credential_library_vault" "postgres_creds" {
  name                = "Vault PostgreSQL Credentials"
  credential_store_id = boundary_credential_store_vault.vault.id
  path                = "database/creds/dba-role"
  http_method         = "GET"
}

Połączenie:

boundary connect postgres -target-id=ttcp_db123 -dbname=myapp

Boundary żąda dynamicznych danych uwierzytelniających z Vault (database/creds/dba-role), ustanawia przekierowanie portu i wstrzykuje dane uwierzytelniające do połączenia PostgreSQL. Użytkownik używa psql bez wiedzy o danych uwierzytelniających.

Rejestracja sesji

Boundary 1.9+ wspiera rejestrację sesji (wideo dla RDP, logi tekstowe dla SSH):

resource "boundary_storage_bucket" "session_recordings" {
  name        = "Session Recordings"
  scope_id    = "global"
  plugin_name = "aws"
  bucket_name = "boundary-session-recordings"
  region      = "eu-west-1"
  
  # IAM role dla Boundary Workers do upload recordings
  worker_filter = "\"worker-tag\" in \"/tags/boundary-role\""
}

resource "boundary_target" "recorded_ssh" {
  # ... SSH target config
  
  enable_session_recording       = true
  storage_bucket_id             = boundary_storage_bucket.session_recordings.id
}

Wszystkie sesje SSH do docelowego zasobu są rejestrowane (naciśnięcia klawiszy, dane wyjściowe) i przechowywane w S3. Zespoły zgodności i bezpieczeństwa mogą audytować sesje po fakcie.

Kompleksowy workflow (end-to-end): podsumowanie integracji

Dzień 0: Konfiguracja infrastruktury

1. Packer buduje złote obrazy (Ubuntu + Consul + wzmocnienie bezpieczeństwa) używając prowizjonera Ansible;

2. Rejestr HCP Packer śledzi artefakty obrazów, SBOM i CVE;

3. Terraform prowizjonuje infrastrukturę AWS (VPC, EC2, RDS), wykorzystując:

  • Źródło danych HCP Packer dla identyfikatorów AMI;
  • Vault AWS Secrets Engine dla danych uwierzytelniających AWS;
  • Backend Consul do zarządzania stanem.

Dzień 1: Wdrożenie aplikacji

4. Nomad wdraża aplikację (kontenery frontend + backend);

5. Consul automatycznie rejestruje usługi (poprzez service blocks w Nomad);

6. Consul Connect wymusza mTLS i intentions między usługami;

7. Vault dostarcza dynamiczne dane uwierzytelniające do bazy danych dla backendu (automatyczna rotacja co 1 godzinę).

Dzień 2: Operacje

8. Boundary zapewnia bezpieczny zdalny dostęp:

  • SSH do instancji EC2 z certyfikatami podpisanymi przez Vault (wstrzykiwanie danych uwierzytelniających);
  • Dostęp do PostgreSQL z dynamicznymi danymi uwierzytelniającymi z Vault;
  • Nagrywanie sesji dla zgodności z wymogami compliance.

9. Consul kontroluje stan zdrowia usług — niesprawne instancje są automatycznie usuwane z wykrywania usług (discovery);

10. Odnawianie dzierżaw Vault - Nomad automatycznie odnawia dane uwierzytelniające i restartuje zadania, gdy rotacja jest wymagana

Security posture:

  • Brak długotrwałych danych uwierzytelniających: wszystkie dane (AWS, bazy danych, SSH) są dynamiczne i krótkotrwałe;
  • Sieć zero-trust: Consul Connect wymusza mTLS i autoryzację poprzez intentions;
  • Bezpieczeństwo łańcucha dostaw: śledzenie SBOM przez HCP Packer, monitoring CVE dla wszystkich obrazów

Alternatywy i kompromisy

→ względem ekosystemu Kubernetes:

  • HashiCorp: natywne wsparcie wielochmurowe, obsługa maszyn wirtualnych, prostszy model operacyjny dla zróżnicowanych obciążeń (kontenery + VM + serverless);
  • Kubernetes: większy ekosystem, lepszy dla czystych obciążeń kontenerowych, więcej wtyczek społeczności.

→ względem stosów natywnych dla chmury (AWS/Azure/GCP):

  • HashiCorp: niezależność od dostawcy, przenośność, spójne API w różnych chmurach;
  • Rozwiązania natywne: lepsza integracja z usługami chmurowymi, zarządzane płaszczyzny sterowania, mniejszy nakład operacyjny dla jednej chmury.

→ względem zarządzania konfiguracją (Ansible/Chef):

  • HashiCorp (Terraform): deklaratywna infrastruktura, zarządzanie stanem, idempotentność z założenia;
  • Ansible/Chef: lepsze do wykrywania dryftu konfiguracji, wdrażania aplikacji, operacji Dnia Drugiego.

Podejście hybrydowe: Wiele organizacji używa kombinacji — Terraform + Packer do infrastruktury, Kubernetes do obciążeń, Vault do sekretów, Ansible do zarządzania konfiguracją. Każde narzędzie ma swoje optymalne zastosowanie.

Dobre praktyki i pułapki

HCP Packer

  • Używaj kanałów (channels) dla wszystkich środowisk (dev, staging, production) zamiast zakodowanych na stałe identyfikatorów iteracji;
  • Włącz śledzenie SBOM do zarządzania podatnościami;
  • Centralizuj rejestr — jeden rejestr HCP Packer na organizację do śledzenia pochodzenia obrazów.

Buduj złożone obrazy z pomocą doświadczonego partnera – Linux Polska wspiera wdrożenia Packer w organizacjach >>

Vault

  • Rotuj tokeny root regularnie i stosuj przestrzenie nazw (namespaces) dla wielodostępu (multi-tenancy);
  • Używaj dynamicznych sekretów wszędzie, gdzie to możliwe — unikaj KV v2 do przechowywania danych uwierzytelniających;
  • Monitoruj wygasanie dzierżaw (lease) — aplikacje muszą obsługiwać odnawianie/rotację w sposób odporny na błędy;
  • Twórz kopie zapasowe klastra Vault — zaszyfrowane snapshoty minimum co 24 godziny.

Zarządzaj sekretami w złożonych środowiskach z wsparciem ekspertów – wdrażamy i wspieramy Vault w środowiskach produkcyjnych >>

Consul

  • Włącz ACL nawet w zaufanych sieciach — obrona wielowarstwowa (defense in depth);
  • Używaj Consul Connect dla service mesh — sieć zero-trust;
  • Monitoruj błędy protokołu gossip (Serf/SWIM) — zdrowie klastra Consul jest krytyczne dla całego stosu;
  • Consul KV nie jest bazą danych — używaj tylko do konfiguracji, nie do danych o wysokiej przepustowości.

Wdróż service mesh z pomocą specjalistów] – wspieramy implementacje Consul w środowiskach produkcyjnych >>

Terraform

  • Zawsze zdalny stan (remote state) — nigdy lokalnie ani w Git;
  • Blokowanie stanu (state locking) — zapobiega współbieżnym modyfikacjom (backend Consula ma to wbudowane);
  • Modularyzuj kod — moduły wielokrotnego użytku dla typowych wzorców;
  • Rozdziel środowiska — osobny stan dla każdego środowiska (dev/staging/prod).

Skaluj Infrastructure as Code z profesjonalnym wsparciem] – Linux Polska oferuje doradztwo i usługi profesjonalne dla Terraform >>

Nomad

  • Używaj integracji z Consul — automatyczna rejestracja usług eliminuje ręczne wykrywanie usług (service discovery);
  • Polityki Vault dla każdego zadania — najmniejsze uprawnienia (least privilege) do dostępu do danych uwierzytelniających;
  • Izolacja zasobów — ustaw limity CPU/pamięci dla wszystkich zadań;
  • Ograniczenia zadań (job constraints) — używaj affinity/spread dla dostępności.

Orkiestruj aplikacje z pomocą doświadczonego zespołu] – Linux Polska oferuje doradztwo i wdrożenia dla Nomad >>

Boundary

  • Używaj pośrednictwa danych uwierzytelniających przez Vault (credential brokering) — eliminuje ręczne zarządzanie;
  • Włącz nagrywanie sesji dla wrażliwych celów (produkcyjne bazy danych, hosty bastion);
  • Integruj z dostawcą tożsamości (OIDC) — unikaj lokalnych użytkowników;
  • Regularna rotacja danych uwierzytelniających — dynamiczne dane z Vault powinny mieć krótki TTL (<1h dla SSH, <24h dla baz danych).

Wdrażasz zero-trust access? Skonsultuj architekturę z ekspertami Boundary] – Linux Polska wspiera projekty bezpiecznego dostępu >>

Podsumowanie: kiedy stos HashiCorp ma sens

Stos HashiCorp jest optymalny dla organizacji, które:

Potrzebują środowiska wielochmurowego: Terraform + Vault + Consul działają identycznie w AWS, Azure, GCP i środowiskach lokalnych (on-premise). Zapewniają jednolity model operacyjny w różnych chmurach.

Mają zróżnicowane obciążenia: maszyny wirtualne, kontenery, serverless i bare metal. Nomad + Consul wspierają wszystkie typy, podczas gdy Kubernetes koncentruje się wyłącznie na kontenerach.

Priorytetyzują bezpieczeństwo: dynamiczne sekrety w Vault + wstrzykiwanie danych uwierzytelniających przez Boundary + mTLS w Consul Connect = kompleksowa architektura zero-trust.

Chcą rozwiązań niezależnych od dostawcy: brak uzależnienia od konkretnych dostawców chmury. Możliwość migracji obciążeń między chmurami bez przepisywania kodu infrastruktury.

Dysponują zespołami platform engineering: stos HashiCorp wymaga specjalistycznej wiedzy operacyjnej — instalacji, konfiguracji i utrzymania klastrów Vault/Consul/Nomad. Dla małych zespołów zarządzane rozwiązania (HCP Terraform, HCP Vault, HCP Consul) redukują nakład pracy.

Podsumowanie: Kompleksowy workflow — od budowania obrazów przez Packera po bezpieczny dostęp przez Boundary — pokazuje siłę integracji między wyspecjalizowanymi narzędziami. Każde z nich rozwiązuje konkretny problem, ale razem tworzą platformę do zarządzania bezpieczną, skalowalną infrastrukturą i cyklem życia aplikacji w środowisku wielochmurowym.

Potrzebujesz pomocy z wdrożeniem?

Wdrożenie kompleksowego stosu HashiCorp wymaga nie tylko wiedzy technicznej, ale także doświadczenia w integracji poszczególnych narzędzi w spójny ekosystem. Linux Polska oferuje kompleksowe wsparcie dla rozwiązań HashiCorp – od doradztwa i projektowania architektury, przez wdrożenie i konfigurację, aż po bieżące wsparcie techniczne.

W ramach naszej oferty zapewniamy oficjalne subskrypcje HashiCorp, pomoc w migracji istniejącej infrastruktury, szkolenia dla zespołów DevOps oraz ongoing support dostosowany do potrzeb Twojej organizacji. Nasi inżynierowie posiadają certyfikacje i wieloletnie doświadczenie w projektach z wykorzystaniem Packer, Terraform, Vault, Consul i Boundary.

Poznaj pełną ofertę HashiCorp w Linux Polska i dowiedz się, jak możemy wspomóc Twoją organizację w automatyzacji infrastruktury i zarządzaniu bezpieczeństwem.

Zobacz również