HashiCorp Boundary da acceso a servidores internos por sesiones autenticadas y auditables, sin repartir acceso a toda la red como haría una VPN. El usuario inicia sesión en el controller, pide conectarse a un target concreto y un worker abre un túnel TCP solo hacia ese host y puerto. En este tutorial instalarás un controller y un worker de Boundary en el mismo servidor Ubuntu 24.04, con PostgreSQL como base de datos, TLS de Let's Encrypt y un target SSH hacia un servidor de tu red privada.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS y al menos 2 GB de RAM, por ejemplo un VPS de CubePath, con una IP pública y acceso a la red privada donde están los servidores que quieres publicar.
- Un usuario no root con privilegios
sudo. - Un dominio con un registro A que apunte a la IP pública del servidor. En la guía se usa
boundary.your_domain. - Un servidor interno accesible por SSH desde el servidor de Boundary. En la guía su IP privada es
10.0.0.20. - La CLI de Boundary en tu equipo para administrar y conectarte (se explica en el paso 6).
Puertos que usa esta instalación:
| Puerto | Uso | Exposición |
|---|---|---|
| 9200/TCP | API y panel web del controller | Público |
| 9201/TCP | Clúster (worker hacia controller) | Solo local en esta guía |
| 9202/TCP | Proxy del worker (túneles de sesión) | Público |
| 9203/TCP | Endpoint de salud (ops) | Solo local |
| 80/TCP | Emisión y renovación del certificado | Público |
Paso 1: Instalar Boundary desde el repositorio de HashiCorp
Descarga la clave GPG de HashiCorp en /etc/apt/keyrings:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/hashicorp-archive-keyring.gpg
Añade el repositorio para tu versión de Ubuntu:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(. /etc/os-release && echo "$VERSION_CODENAME") main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
Instala Boundary y comprueba la versión:
sudo apt update
sudo apt install boundary
boundary version
Version information:
Build Date: 2026-...
Git Revision: ...
Version Number: 0.x.x
Asegúrate de que existe un usuario de sistema boundary para ejecutar los servicios (el paquete puede haberlo creado ya) y de que existe el directorio de configuración:
getent passwd boundary >/dev/null || sudo useradd --system --home /etc/boundary.d --shell /usr/sbin/nologin boundary
sudo install -d -o boundary -g boundary -m 0750 /etc/boundary.d /etc/boundary.d/tls
Paso 2: Preparar PostgreSQL
El controller guarda en PostgreSQL toda su configuración (scopes, usuarios, targets) y el estado de las sesiones. Instala PostgreSQL 16, el de Ubuntu 24.04:
sudo apt install postgresql
Crea un rol y una base de datos propiedad de ese rol. Sustituye your_db_password por una contraseña larga y aleatoria:
sudo -u postgres psql -c "CREATE ROLE boundary WITH LOGIN PASSWORD 'your_db_password';"
sudo -u postgres psql -c "CREATE DATABASE boundary OWNER boundary;"
Comprueba que el rol puede conectarse:
psql "postgresql://boundary:[email protected]:5432/boundary" -c 'SELECT current_user;'
current_user
--------------
boundary
(1 row)
Paso 3: Obtener el certificado TLS
El controller sirve su API y el panel web por HTTPS. Instala Certbot y abre los puertos necesarios en UFW:
sudo apt install certbot
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 9200/tcp
sudo ufw allow 9202/tcp
sudo ufw enable
Boundary se ejecuta como el usuario boundary, que no puede leer /etc/letsencrypt. Crea un hook que copie el certificado a /etc/boundary.d/tls cada vez que se emita o renueve y reinicie el controller si está en marcha:
sudo nano /etc/letsencrypt/renewal-hooks/deploy/boundary.sh
#!/usr/bin/env bash
set -euo pipefail
install -o boundary -g boundary -m 0644 "${RENEWED_LINEAGE}/fullchain.pem" /etc/boundary.d/tls/cert.pem
install -o boundary -g boundary -m 0600 "${RENEWED_LINEAGE}/privkey.pem" /etc/boundary.d/tls/key.pem
if systemctl is-active --quiet boundary-controller.service; then
systemctl restart boundary-controller.service
fi
Hazlo ejecutable y solicita el certificado. Certbot ejecuta los scripts de renewal-hooks/deploy en cada renovación, pero no en la primera emisión, así que lanzarás el hook a mano una vez:
sudo chmod 0755 /etc/letsencrypt/renewal-hooks/deploy/boundary.sh
sudo certbot certonly --standalone -d boundary.your_domain --agree-tos -m admin@your_domain --no-eff-email
sudo RENEWED_LINEAGE=/etc/letsencrypt/live/boundary.your_domain /etc/letsencrypt/renewal-hooks/deploy/boundary.sh
sudo ls -l /etc/boundary.d/tls
-rw-r--r-- 1 boundary boundary 2868 Sep 25 11:02 cert.pem
-rw------- 1 boundary boundary 241 Sep 25 11:02 key.pem
Paso 4: Configurar el controller
Boundary cifra los datos sensibles de la base de datos con claves KMS. En producción conviene un KMS externo (Vault Transit, AWS KMS, etc.); en esta guía se usan claves AEAD estáticas guardadas en el archivo de configuración. Genera tres claves aleatorias de 32 bytes, una para cada propósito (root, worker-auth y recovery):
for purpose in root worker-auth recovery; do echo "$purpose: $(openssl rand -base64 32)"; done
Crea el archivo del controller:
sudo nano /etc/boundary.d/controller.hcl
Pega esta configuración, sustituyendo la contraseña de la base de datos, el dominio y las tres claves:
disable_mlock = true
controller {
name = "controller-1"
description = "Boundary controller"
database {
url = "postgresql://boundary:[email protected]:5432/boundary?sslmode=disable"
}
}
listener "tcp" {
address = "0.0.0.0:9200"
purpose = "api"
tls_cert_file = "/etc/boundary.d/tls/cert.pem"
tls_key_file = "/etc/boundary.d/tls/key.pem"
}
listener "tcp" {
address = "127.0.0.1:9201"
purpose = "cluster"
}
listener "tcp" {
address = "127.0.0.1:9203"
purpose = "ops"
tls_disable = true
}
kms "aead" {
purpose = "root"
aead_type = "aes-gcm"
key = "your_root_key"
key_id = "global_root"
}
kms "aead" {
purpose = "worker-auth"
aead_type = "aes-gcm"
key = "your_worker_auth_key"
key_id = "global_worker-auth"
}
kms "aead" {
purpose = "recovery"
aead_type = "aes-gcm"
key = "your_recovery_key"
key_id = "global_recovery"
}
El listener cluster escucha solo en 127.0.0.1 porque el worker está en la misma máquina. Si más adelante añades workers en otras redes, cámbialo a una IP accesible por ellos y abre el puerto 9201 solo para sus direcciones.
Este archivo contiene la contraseña de la base de datos y las claves, así que restringe sus permisos:
sudo chown boundary:boundary /etc/boundary.d/controller.hcl
sudo chmod 0640 /etc/boundary.d/controller.hcl
Inicializa la base de datos. Este comando se ejecuta una sola vez, crea el esquema y genera un método de autenticación por contraseña con un usuario admin, una organización, un proyecto y un target de ejemplo:
sudo -u boundary boundary database init -config /etc/boundary.d/controller.hcl
Initial auth information:
Auth Method ID: ampw_1234567890
Auth Method Name: Generated global scope initial password auth method
Login Name: admin
Password: AbCdEf1234567890
Scope ID: global
User ID: u_1234567890
User Name: admin
...
Initial org scope information:
Scope ID: o_1234567890
...
Initial project scope information:
Scope ID: p_1234567890
Guarda en un gestor de contraseñas el Auth Method ID, la contraseña de admin y los identificadores de la organización y el proyecto: no se vuelven a mostrar.
Crea la unidad de systemd del controller:
sudo nano /etc/systemd/system/boundary-controller.service
[Unit]
Description=HashiCorp Boundary controller
Wants=network-online.target
After=network-online.target postgresql.service
[Service]
User=boundary
Group=boundary
ExecStart=/usr/bin/boundary server -config=/etc/boundary.d/controller.hcl
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Arranca el controller y comprueba el endpoint de salud:
sudo systemctl daemon-reload
sudo systemctl enable --now boundary-controller
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:9203/health
200
Paso 5: Configurar el worker
El worker es el proceso por el que pasa el tráfico de las sesiones. Se autentica ante el controller con la misma clave worker-auth, y su public_addr es la dirección a la que se conectarán los clientes. Crea su configuración:
sudo nano /etc/boundary.d/worker.hcl
disable_mlock = true
listener "tcp" {
address = "0.0.0.0:9202"
purpose = "proxy"
}
worker {
name = "worker-1"
description = "Worker con acceso a la red privada"
public_addr = "boundary.your_domain"
initial_upstreams = ["127.0.0.1:9201"]
}
kms "aead" {
purpose = "worker-auth"
aead_type = "aes-gcm"
key = "your_worker_auth_key"
key_id = "global_worker-auth"
}
Restringe sus permisos y crea su unidad de systemd:
sudo chown boundary:boundary /etc/boundary.d/worker.hcl
sudo chmod 0640 /etc/boundary.d/worker.hcl
sudo nano /etc/systemd/system/boundary-worker.service
[Unit]
Description=HashiCorp Boundary worker
Wants=network-online.target
After=network-online.target boundary-controller.service
[Service]
User=boundary
Group=boundary
ExecStart=/usr/bin/boundary server -config=/etc/boundary.d/worker.hcl
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
Arranca el worker y comprueba en el log que se ha conectado al controller:
sudo systemctl daemon-reload
sudo systemctl enable --now boundary-worker
sudo journalctl -u boundary-worker -n 30 --no-pager
Si ves errores de autenticación, la clave worker-auth no coincide exactamente con la del controller.
Paso 6: Instalar la CLI en tu equipo e iniciar sesión
La administración y las conexiones se hacen desde tu equipo. En Ubuntu o Debian, añade el mismo repositorio del paso 1 e instala boundary; en macOS usa brew install hashicorp/tap/boundary. Para que las sesiones SSH funcionen, el equipo también necesita el cliente ssh.
Apunta la CLI a tu controller e inicia sesión con el Auth Method ID del paso 4:
export BOUNDARY_ADDR=https://boundary.your_domain:9200
boundary authenticate password -auth-method-id=ampw_1234567890 -login-name=admin
La CLI pide la contraseña y guarda el token en el llavero del sistema:
Authentication information:
Account ID: acctpw_1234567890
Auth Method ID: ampw_1234567890
User ID: u_1234567890
The token was successfully stored in the chosen keyring and is not displayed here.
Comprueba que el worker está registrado:
boundary workers list
También puedes abrir https://boundary.your_domain:9200 en el navegador: el panel web muestra las mismas organizaciones, proyectos y sesiones.
Paso 7: Crear un target SSH
Boundary organiza los recursos en scopes: global, organizaciones y proyectos. Los hosts y targets viven en un proyecto; usa el que generó database init (p_1234567890 en el ejemplo).
Crea un catálogo de hosts estático, añade el servidor interno y agrúpalo en un host set:
boundary host-catalogs create static -scope-id=p_1234567890 -name="servidores-internos"
boundary hosts create static -host-catalog-id=hcst_1234567890 -name="web-01" -address="10.0.0.20"
boundary host-sets create static -host-catalog-id=hcst_1234567890 -name="web"
boundary host-sets add-hosts -id=hsst_1234567890 -host=hst_1234567890
Cada comando devuelve el identificador del recurso creado (hcst_..., hst_..., hsst_...), que usas en el siguiente. Crea ahora un target TCP en el puerto 22 y asígnale el host set:
boundary targets create tcp -scope-id=p_1234567890 -name="ssh-web-01" -default-port=22
boundary targets add-host-sources -id=ttcp_1234567890 -host-source=hsst_1234567890
Paso 8: Conectarse y gestionar sesiones
boundary connect ssh pide una sesión al controller, abre un túnel local a través del worker y lanza tu cliente ssh contra él:
boundary connect ssh -target-id=ttcp_1234567890 -username=your_user
La autenticación SSH en el servidor de destino sigue siendo la tuya (clave o contraseña); Boundary controla quién puede llegar hasta él y registra la sesión. Mientras estás conectado, desde otra terminal puedes ver la sesión activa:
boundary sessions list -scope-id=p_1234567890
Session information:
ID: s_1234567890
Status: active
Target ID: ttcp_1234567890
User ID: u_1234567890
Un administrador puede cortar una sesión en cualquier momento:
boundary sessions cancel -id=s_1234567890
Una vez que el acceso por Boundary funciona, el servidor interno ya no necesita tener el puerto 22 abierto hacia Internet: basta con que sea accesible desde el worker por la red privada.
Solución de problemas
El controller no arranca. Revisa sudo journalctl -u boundary-controller -n 50. Los errores más comunes son una URL de base de datos incorrecta, permisos sobre los archivos de /etc/boundary.d/tls o una clave KMS que no son 32 bytes en base64.
boundary connect se queda esperando o falla con timeout. El cliente debe llegar a boundary.your_domain:9202. Comprueba el firewall con sudo ufw status y que el public_addr del worker resuelve a la IP pública.
La sesión se abre pero SSH no conecta con el host. El worker no alcanza el destino. Desde el servidor de Boundary, prueba nc -zv 10.0.0.20 22.
Conclusión
Has instalado un controller y un worker de Boundary con PostgreSQL y TLS, y has publicado un servidor interno como target SSH al que se accede con sesiones autenticadas, visibles y revocables. Como siguientes pasos puedes crear usuarios y roles limitados por proyecto en lugar de usar admin, conectar un proveedor OIDC para el inicio de sesión y desplegar workers adicionales dentro de otras redes privadas para alcanzar sus hosts sin exponerlos.
