Terraform
Configure self-hosted Boundary with Terraform
With Boundary is still running in dev mode, you are going to use Terraform to configure your Boundary environment.
This tutorial will configure the following resources:
Type | Name | Notes |
---|---|---|
Organization | corp_one | A new organization |
Users | (multiple) | Creates 9 users (Jim, Jeff, Randy, etc.) |
Group | read-only | A new group with 3 users |
Roles | (multiple) | 2 new roles (Read-only and admin) |
Auth Method | Corp Password | A new password auth method |
Project | core_infra | A new project within the corp_one organization |
Host catalog | backend_servers | A new host catalog with one host set |
Host set | backend_servers_ssh | A new host set with 2 hosts |
Targets | (multiple) | 2 new targets (ssh_server and backend_server) |
Prerequisites
- Terraform 0.13.0 or later installed
- Boundary is still running in dev mode
Background
Terraform is an infrastructure automation tool that makes provisioning resources simple and repeatable. The process of configuring Boundary resources using the CLI can be tedious and time-intensive. Using Terraform, setting up the resources needed for a Boundary deployment can be simplified and easily repeated at scale.
This tutorial uses version 1.0.10 of the official Boundary provider in the Terraform registry. Other Terraform modules exist that simplify the process of standing up an environment further, such as the getting-started module that deploys everything needed for first log in.
Configure Boundary
To get started, create a directory named, boundary-test
.
$ mkdir ~/boundary-test && cd ~/boundary-test
Create a Terraform configuration file, main.tf
and paste in the following.
main.tf
provider "boundary" {
addr = "http://127.0.0.1:9200"
auth_method_id = "ampw_1234567890"
password_auth_method_login_name = "admin"
password_auth_method_password = "password"
}
variable "users" {
type = set(string)
default = [
"Jim",
"Mike",
"Todd",
"Jeff",
"Randy",
"Susmitha"
]
}
variable "readonly_users" {
type = set(string)
default = [
"Chris",
"Pete",
"Justin"
]
}
variable "backend_server_ips" {
type = set(string)
default = [
"10.1.0.1",
"10.1.0.2",
]
}
resource "boundary_scope" "global" {
global_scope = true
description = "My first global scope!"
scope_id = "global"
}
resource "boundary_scope" "corp" {
name = "corp_one"
description = "My first scope!"
scope_id = boundary_scope.global.id
auto_create_admin_role = true
auto_create_default_role = true
}
## Use password auth method
resource "boundary_auth_method" "password" {
name = "Corp Password"
scope_id = boundary_scope.corp.id
type = "password"
}
resource "boundary_account_password" "users_acct" {
for_each = var.users
name = each.key
description = "User account for ${each.key}"
type = "password"
login_name = lower(each.key)
password = "password"
auth_method_id = boundary_auth_method.password.id
}
resource "boundary_user" "users" {
for_each = var.users
name = each.key
description = "User resource for ${each.key}"
scope_id = boundary_scope.corp.id
}
resource "boundary_user" "readonly_users" {
for_each = var.readonly_users
name = each.key
description = "User resource for ${each.key}"
scope_id = boundary_scope.corp.id
}
resource "boundary_group" "readonly" {
name = "read-only"
description = "Organization group for readonly users"
member_ids = [for user in boundary_user.readonly_users : user.id]
scope_id = boundary_scope.corp.id
}
resource "boundary_role" "organization_readonly" {
name = "Read-only"
description = "Read-only role"
principal_ids = [boundary_group.readonly.id]
grant_strings = ["ids=*;type=*;actions=read"]
scope_id = boundary_scope.corp.id
}
resource "boundary_role" "organization_admin" {
name = "admin"
description = "Administrator role"
principal_ids = concat(
[for user in boundary_user.users: user.id]
)
grant_strings = ["ids=*;type=*;actions=create,read,update,delete"]
scope_id = boundary_scope.corp.id
}
resource "boundary_scope" "core_infra" {
name = "core_infra"
description = "My first project!"
scope_id = boundary_scope.corp.id
auto_create_admin_role = true
}
resource "boundary_host_catalog_static" "backend_servers" {
name = "backend_servers"
description = "Backend servers host catalog"
scope_id = boundary_scope.core_infra.id
}
resource "boundary_host_static" "backend_servers" {
for_each = var.backend_server_ips
type = "static"
name = "backend_server_service_${each.value}"
description = "Backend server host"
address = each.key
host_catalog_id = boundary_host_catalog_static.backend_servers.id
}
resource "boundary_host_set_static" "backend_servers_ssh" {
type = "static"
name = "backend_servers_ssh"
description = "Host set for backend servers"
host_catalog_id = boundary_host_catalog_static.backend_servers.id
host_ids = [for host in boundary_host_static.backend_servers : host.id]
}
# create target for accessing backend servers on port :8000
resource "boundary_target" "backend_servers_service" {
type = "tcp"
name = "backend_server"
description = "Backend service target"
scope_id = boundary_scope.core_infra.id
default_port = "8080"
host_source_ids = [
boundary_host_set_static.backend_servers_ssh .id
]
}
# create target for accessing backend servers on port :22
resource "boundary_target" "backend_servers_ssh" {
type = "tcp"
name = "ssh_server"
description = "Backend SSH target"
scope_id = boundary_scope.core_infra.id
default_port = "22"
host_source_ids = [
boundary_host_set_static.backend_servers_ssh.id
]
}
The Terraform resources in this configuration map to these Boundary resources:
- boundary_scope (line 37) -> A Scope is a permission boundary modeled as a container.
- boundary_auth_method (line 52) -> An auth method provides a mechanism for users to authenticate to Boundary.
- boundary_account_password (line 58) -> An account represents a unique set of credentials issued from a configured authentication method which can be used to establish the identity of a user.
- boundary_user (line 68) -> A user represents an individual person or entity for the purposes of access control.
- boundary_group (line 82) -> A group represents a collection of users which can be treated equally for the purposes of access control.
- boundary_role (line 97) -> A role contains a collection of permissions which are granted to any principal assigned to the role.
- boundary_host_catalog_static (line 114) -> A static host catalog contains hosts and host sets.
- boundary_host_static (line 121) -> A static host represents a computing element with a network address reachable from Boundary.
- boundary_host_set_static (line 130) -> A static host set represents a collection of hosts which are considered equivalent for the purposes of access control.
- boundary_target (line 152) -> A target represents a networked service with an associated set of permissions a user can connect to and interact with through Boundary by way of a session.
For more detail description and example for each resource, refer to the Terraform Boundary provider documentation.
Now, you are ready to initialize Terraform.
$ terraform init
Initializing the backend...
Initializing provider plugins...
##...snip...
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
The init
command downloads the latest available Terraform Provider for
Boundary. Alternatively, you can clone the Terraform Boundary Provider GitHub
repository and build
it from the source code. Refer to its README for more detail.
Run terraform apply
and review the planned actions. Your terminal output
should indicate the plan is running and what resources will be created.
$ terraform apply
##...snip...
Plan: 28 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
Enter yes
to confirm and resume.
When it completes, you should see "Apply complete" message. Any warnings about deprecated attributes can be ignored for this example.
Apply complete! Resources: 28 added, 0 changed, 0 destroyed.
From the admin console, select the newly created corp_one organization, and verify that Terraform created users, groups, roles and other resources.
Connecting to Targets
The boundary connect
command can be used to establish sessions with hosts that
Boundary manages. First, log into Boundary as the admin
user. Enter the
password password
when prompted.
$ boundary authenticate
Please enter the login name (it will be hidden):
Please enter the password (it will be hidden):
Authentication information:
Account ID: acctpw_VOeNSFX8pQ
Auth Method ID: ampw_ZbB6UXpW3B
Expiration Time: Mon, 13 Feb 2023 12:35:32 MST
User ID: u_ogz79sV4sT
The token was successfully stored in the chosen keyring and is not displayed here.
Next, find the names of all the available scopes using recursive listing.
$ boundary scopes list -recursive
Scope information:
ID: o_1234567890
Scope ID: global
Version: 1
Name: Generated org scope
Description: Provides an initial org scope in Boundary
Authorized Actions:
no-op
read
update
delete
ID: o_zW0hNEE0PT
Scope ID: global
Version: 1
Name: corp_one
Description: My first scope!
Authorized Actions:
no-op
read
update
delete
ID: p_1234567890
Scope ID: o_1234567890
Version: 1
Name: Generated project scope
Description: Provides an initial project scope in Boundary
Authorized Actions:
no-op
read
update
delete
ID: p_c58m00i3u4
Scope ID: o_zW0hNEE0PT
Version: 1
Name: core_infra
Description: My first project!
Authorized Actions:
no-op
read
update
delete
Copy the corp_one Scope ID, and use recursive listing again to find all the available targets in the new scope.
$ boundary targets list -scope-id o_7NAS3dPsSo -recursive
Target information:
ID: ttcp_WaRDd3pQGi
Scope ID: p_c58m00i3u4
Version: 2
Type: tcp
Name: ssh_server
Description: Backend SSH target
Authorized Actions:
no-op
read
update
delete
add-host-sources
set-host-sources
remove-host-sources
add-credential-sources
set-credential-sources
remove-credential-sources
authorize-session
ID: ttcp_ClFIihPbCU
Scope ID: p_c58m00i3u4
Version: 2
Type: tcp
Name: backend_server
Description: Backend service target
Authorized Actions:
no-op
read
update
delete
add-host-sources
set-host-sources
remove-host-sources
add-credential-sources
set-credential-sources
remove-credential-sources
authorize-session
Two tcp targets are available, ssh_server
and backend_server
.
It's important to note that these targets do not actually exist in this dev environment; the targets have simply been added to the Boundary host catalog using Terraform. In a more realistic scenario these targets would have first been provisioned, and then added to Boundary.
Sessions can be established to targets using the boundary connect
command.
There are several built-in sub-commands for accessing targets with specific
protocols.
Subcommands:
http
Authorize a session against a target and invoke an HTTP client to connectkube
Authorize a session against a target and invoke a Kubernetes client to connectpostgres
Authorize a session against a target and invoke a Postgres client to connectrdp
Authorize a session against a target and invoke an RDP client to connectssh
Authorize a session against a target and invoke an SSH client to connect
Start by establishing an ssh connection to the Backend SSH target using boundary connect
.
$ boundary connect ssh -target-id ttcp_WaRDd3pQGi
The pending connection will hang as a session is attempted. Remember, these
targets do not actually exist, so this demonstration simply shows how the
boundary connect
command can be used against targets in the host catalog.
The pending connection can be viewed by recursively listing the sessions.
$ boundary sessions list -recursive
Session information:
ID: s_vaKCsqjT1p
Scope ID: p_c58m00i3u4
Status: active
Created Time: Thu, 11 Aug 2022 15:56:07 MDT
Expiration Time: Thu, 11 Aug 2022 23:56:07 MDT
Updated Time: Thu, 11 Aug 2022 15:56:07 MDT
User ID: u_1234567890
Target ID: ttcp_WaRDd3pQGi
Authorized Actions:
no-op
read
read:self
cancel
cancel:self
The session can be canceled using the boundary sessions
command and providing
the session ID.
$ boundary sessions cancel -id s_vaKCsqjT1p
Session information:
Auth Token ID: at_7oFKYe3Wdq
Created Time: Thu, 11 Aug 2022 15:56:07 MDT
Endpoint: tcp://10.1.0.1:22
Expiration Time: Thu, 11 Aug 2022 23:56:07 MDT
Host ID: hst_6hFlfbub97
Host Set ID: hsst_AIwMLxLozx
ID: s_vaKCsqjT1p
Status: canceling
Target ID: ttcp_WaRDd3pQGi
Type: tcp
Updated Time: Thu, 11 Aug 2022 15:57:22 MDT
User ID: u_1234567890
Version: 3
Scope:
ID: p_c58m00i3u4
Name: core_infra
Parent Scope ID: o_zW0hNEE0PT
Type: project
Authorized Actions:
no-op
read
read:self
cancel
cancel:self
States:
Start Time: Thu, 11 Aug 2022 15:57:22 MDT
Status: canceling
End Time: Thu, 11 Aug 2022 15:57:22 MDT
Start Time: Thu, 11 Aug 2022 15:56:07 MDT
Status: active
End Time: Thu, 11 Aug 2022 15:56:07 MDT
Start Time: Thu, 11 Aug 2022 15:56:07 MDT
Status: pending
Verify that the session shows Status: Terminated
by running boundary sessions list -recursive
again.
Targets can be addressed directly using the -target-id
option, or using a
combination of the -target-name
and -target-scope-name
options if you
provide the project name that the target exists within (core_infra, in this
example).
Try this workflow with the other target, backend_server
. The boundary connect
command can be used for generic tcp connections, without the protocol
sub-command.
$ boundary connect -target-name backend_server -target-scope-name core_infra
Proxy listening information:
Address: 127.0.0.1
Connection Limit: -1
Expiration: Thu, 11 Aug 2022 23:58:15 MDT
Port: 56822
Protocol: tcp
Session ID: s_p1SRXOqkuM
Again, the session will hang as a connection is attempted with the target. Like
the previous example with the SSH target, verify the pending connection, and
then cancel it using the boundary sessions
command.
Warning
You man encounter the following error when running boundary connect
:
$ boundary connect -target-name backend_server -target-scope-name corp_one
Error from controller when performing authorize-session action against given target
Error information:
Kind: FailedPrecondition
Message: No workers are available to handle this session, or all have been filtered.
Status: 400
context: Error from controller when performing authorize-session action against given target
Warning
If you check the terminal window where boundary dev
was run, you may see a
log entry related to runtime memory.
==> Boundary server started! Log data will stream in below:
[INFO] worker: connected to controller: address=127.0.0.1:9201
[INFO] controller: worker successfully authed: name=dev-worker
[INFO] controller.worker-handler: session activated: session_id=s_S7kiNTnKB9 target_id=ttcp_kj5dezNOhU user_id=u_1234567890 host_set_id=hsst_pw1tP5ehvD host_id=hst_g13wOr6k7e
[INFO] controller.worker-handler: authorized connection: session_id=s_S7kiNTnKB9 connection_id=sc_pt0YuhtP76 connections_left=0
[INFO] controller.worker-handler: connection closed: connection_id=sc_eWPtlZpM7Q
[ERROR] worker: error dialing endpoint: error="dial tcp 10.1.0.1:22: connect: operation timed out" endpoint=tcp://10.1.0.1:22
[INFO] controller.worker-handler: connection closed: connection_id=sc_eWPtlZpM7Q
[INFO] worker: http: panic serving 127.0.0.1:60746: runtime error: invalid memory address or nil pointer dereference
Warning
This implies that the Boundary controller started with dev mode has run out
of memory. This can occur after running too many connections agains the dev
sever, or by re-provisioning too many times. Simply stop dev mode by entering
Ctrl+C, start dev mode again with boundary dev
, and run terraform apply --auto-approve
to set up the environment again.
You can edit the Terraform configuration file (main.tf
) to make changes,
and then run terraform apply
again to commit the changes. Terraform stores
state about your managed infrastructure and configuration which is used to map
real world resources to your configuration, keep track of metadata, and to
improve performance for large infrastructures. To learn more about Terraform,
visit Terraform Learn.
To stop the Boundary dev server, enter Ctrl+C in the terminal where it is running.