Boundary Secure Remote Access Installation
HashiCorp Boundary provides identity-based infrastructure access using zero-trust principles, removing the need for VPNs and static credentials by dynamically brokering sessions to targets. Boundary integrates with existing identity providers and Vault for credential injection, creating fully audited sessions to servers, databases, and Kubernetes clusters.
Prerequisites
- Ubuntu 22.04/20.04 or CentOS/Rocky Linux 8+
- PostgreSQL 11+ for controller storage
- HashiCorp Vault (optional, for credential injection)
- Open ports: 9200 (controller API), 9201 (cluster), 9202 (worker data-plane)
- DNS-resolvable hostnames for controller and worker nodes
Installing Boundary
# Add HashiCorp APT repository (Ubuntu/Debian)
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update
sudo apt-get install -y boundary
# For CentOS/Rocky Linux
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum install -y boundary
# Verify
boundary version
Set up the PostgreSQL database:
sudo -u postgres psql <<EOF
CREATE DATABASE boundary;
CREATE USER boundary WITH PASSWORD 'strong-password-here';
GRANT ALL PRIVILEGES ON DATABASE boundary TO boundary;
EOF
Setting Up the Controller
The controller handles API requests, state management, and session authorization.
# Generate KMS keys for encryption (use Vault or AWS KMS in production)
# For development, generate random keys:
KMS_KEY_1=$(openssl rand -base64 32)
KMS_KEY_2=$(openssl rand -base64 32)
KMS_KEY_3=$(openssl rand -base64 32)
cat > /etc/boundary/controller.hcl <<EOF
disable_mlock = true
controller {
name = "boundary-controller-01"
description = "Primary Boundary controller"
database {
url = "postgresql://boundary:strong-password-here@localhost:5432/boundary"
}
}
listener "tcp" {
address = "0.0.0.0:9200"
purpose = "api"
tls_disable = false
tls_cert_file = "/etc/boundary/tls/cert.pem"
tls_key_file = "/etc/boundary/tls/key.pem"
}
listener "tcp" {
address = "0.0.0.0:9201"
purpose = "cluster"
}
kms "aead" {
purpose = "root"
aead_type = "aes-gcm"
key = "${KMS_KEY_1}"
key_id = "global_root"
}
kms "aead" {
purpose = "worker-auth"
aead_type = "aes-gcm"
key = "${KMS_KEY_2}"
key_id = "global_worker-auth"
}
kms "aead" {
purpose = "recovery"
aead_type = "aes-gcm"
key = "${KMS_KEY_3}"
key_id = "global_recovery"
}
EOF
# Initialize the database (first time only)
boundary database init -config /etc/boundary/controller.hcl
Save the output from database init — it contains the initial auth method ID and credentials.
Create a systemd service:
cat > /etc/systemd/system/boundary-controller.service <<EOF
[Unit]
Description=HashiCorp Boundary Controller
Documentation=https://www.boundaryproject.io/docs
Requires=network-online.target
After=network-online.target postgresql.service
[Service]
User=boundary
Group=boundary
ExecStart=/usr/bin/boundary server -config=/etc/boundary/controller.hcl
ExecReload=/bin/kill -HUP \$MAINPID
KillMode=process
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
EOF
sudo useradd -r -s /bin/false boundary
sudo chown -R boundary:boundary /etc/boundary /var/log/boundary
sudo systemctl enable boundary-controller
sudo systemctl start boundary-controller
Configuring the Worker
Workers proxy sessions between clients and targets. Deploy workers in private networks close to your targets:
cat > /etc/boundary/worker.hcl <<EOF
disable_mlock = true
worker {
name = "boundary-worker-01"
description = "Worker in production subnet"
public_addr = "worker.example.com"
initial_upstreams = ["controller.example.com:9201"]
}
listener "tcp" {
address = "0.0.0.0:9202"
purpose = "proxy"
}
kms "aead" {
purpose = "worker-auth"
aead_type = "aes-gcm"
key = "SAME-WORKER-AUTH-KEY-AS-CONTROLLER"
key_id = "global_worker-auth"
}
EOF
cat > /etc/systemd/system/boundary-worker.service <<EOF
[Unit]
Description=HashiCorp Boundary Worker
After=network-online.target
[Service]
User=boundary
ExecStart=/usr/bin/boundary server -config=/etc/boundary/worker.hcl
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable boundary-worker
sudo systemctl start boundary-worker
Target Configuration
Configure targets using the Boundary CLI or Terraform:
# Authenticate to Boundary
export BOUNDARY_ADDR="https://controller.example.com:9200"
boundary authenticate password \
-auth-method-id=ampw_XXXXXXXXXXXX \
-login-name=admin \
-password=yourpassword
# Create an org and project
ORG_ID=$(boundary orgs create \
-name="production" \
-description="Production infrastructure" \
-format json | jq -r '.item.id')
PROJECT_ID=$(boundary scopes create \
-name="web-servers" \
-description="Web server access" \
-scope-id=$ORG_ID \
-format json | jq -r '.item.id')
# Create a host catalog
CATALOG_ID=$(boundary host-catalogs create static \
-name="web-catalog" \
-scope-id=$PROJECT_ID \
-format json | jq -r '.item.id')
# Add hosts
HOST_ID=$(boundary hosts create static \
-name="web-01" \
-address="10.0.1.10" \
-host-catalog-id=$CATALOG_ID \
-format json | jq -r '.item.id')
# Create host set
HOST_SET_ID=$(boundary host-sets create static \
-name="web-servers" \
-host-catalog-id=$CATALOG_ID \
-format json | jq -r '.item.id')
boundary host-sets add-hosts \
-id=$HOST_SET_ID \
-host=$HOST_ID
# Create SSH target
TARGET_ID=$(boundary targets create ssh \
-name="web-01-ssh" \
-scope-id=$PROJECT_ID \
-default-port=22 \
-format json | jq -r '.item.id')
boundary targets add-host-sets \
-id=$TARGET_ID \
-host-set=$HOST_SET_ID
Connect to a target:
# Connect via Boundary (creates a local proxy port)
boundary connect ssh -target-id=$TARGET_ID -username=ubuntu
# Or use the generic connect and pass to your own SSH command
boundary connect -target-id=$TARGET_ID -- ssh -p {{boundary.port}} ubuntu@{{boundary.ip}}
Credential Injection with Vault
Use Vault to inject credentials automatically so users never see the actual passwords:
# Configure Vault credential store
VAULT_STORE_ID=$(boundary credential-stores create vault \
-scope-id=$PROJECT_ID \
-vault-address="https://vault.example.com:8200" \
-vault-token="s.your-vault-token" \
-name="vault-store" \
-format json | jq -r '.item.id')
# Create credential library pointing to Vault SSH secret
CRED_LIB_ID=$(boundary credential-libraries create vault-ssh-certificate \
-credential-store-id=$VAULT_STORE_ID \
-vault-path="ssh/sign/boundary-role" \
-name="ssh-certs" \
-username="ubuntu" \
-format json | jq -r '.item.id')
# Attach credential library to target
boundary targets add-credential-sources \
-id=$TARGET_ID \
-injected-application-credential-source=$CRED_LIB_ID
# Now connections automatically inject Vault-signed SSH certificates
boundary connect ssh -target-id=$TARGET_ID
Session Management
Monitor and manage active sessions:
# List active sessions in a scope
boundary sessions list -scope-id=$PROJECT_ID
# Get details on a specific session
boundary sessions read -id=s_XXXXXXXXXXXX
# Cancel a session (admin action)
boundary sessions cancel -id=s_XXXXXXXXXXXX
# List recent sessions with details
boundary sessions list -scope-id=$PROJECT_ID -recursive -format json | \
jq '.items[] | {id: .id, target: .target_id, user: .user_id, status: .status}'
Identity Provider Integration
Integrate OIDC for single sign-on:
# Create OIDC auth method (Okta example)
boundary auth-methods create oidc \
-scope-id=global \
-name="okta-sso" \
-issuer="https://your-org.okta.com" \
-client-id="your-client-id" \
-client-secret="your-client-secret" \
-api-url-prefix="https://controller.example.com:9200" \
-signing-algorithm=RS256 \
-claims-scopes=profile \
-claims-scopes=email
# Create managed group from OIDC claims
boundary managed-groups create oidc \
-auth-method-id=amoidc_XXXXXXXXXXXX \
-name="devops-team" \
-filter='"/token/groups" contains "devops"'
# Create role granting the managed group access to targets
boundary roles create \
-scope-id=$ORG_ID \
-name="devops-access"
ROLE_ID=$(boundary roles list -scope-id=$ORG_ID -format json | \
jq -r '.items[] | select(.name=="devops-access") | .id')
boundary roles add-principals \
-id=$ROLE_ID \
-principal=mgoidc_XXXXXXXXXXXX
boundary roles add-grants \
-id=$ROLE_ID \
-grant="id=*;type=target;actions=list,read,authorize-session"
Troubleshooting
Controller fails to start:
# Check configuration syntax
boundary server -config /etc/boundary/controller.hcl -dev
# View detailed logs
sudo journalctl -u boundary-controller -n 100
# Test database connectivity
psql postgresql://boundary:password@localhost:5432/boundary -c "SELECT 1"
Worker not connecting to controller:
# Verify KMS worker-auth key matches between controller and worker configs
# Check firewall rules
sudo firewall-cmd --list-all # CentOS
sudo ufw status # Ubuntu
# Ensure port 9201 is accessible
nc -zv controller.example.com 9201
Session connection timeout:
# Check worker is advertising correct public_addr
boundary workers list -scope-id=global -format json
# Verify worker port 9202 is reachable from client
nc -zv worker.example.com 9202
# Test target connectivity from worker node
nc -zv 10.0.1.10 22
Authentication failures:
# Verify auth method ID
boundary auth-methods list -scope-id=global
# Re-authenticate
boundary authenticate password -auth-method-id=ampw_XXXX -login-name=admin
Conclusion
HashiCorp Boundary delivers identity-based infrastructure access that eliminates VPN complexity and static credential exposure. By combining controller/worker architecture with Vault credential injection and OIDC SSO, you get a fully audited, zero-trust access layer for all your infrastructure targets. Scale workers into each network segment to reach any host without opening firewall rules to end users.


