Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
.terraform.lock.hcl
terraform.tfstate*
*.tfvars
.env
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# terraform-provider-definednet

Terraform provider for [Defined Networking](https://www.defined.net), managing
networks, roles, tags, and routes through the
networks, roles, tags, routes, hosts, and join keys through the
[Defined Networking API](https://docs.defined.net/api/defined-networking-api/).

Built on terraform-plugin-framework (protocol 6). Generated reference docs live
Expand All @@ -20,6 +20,8 @@ and the
| `definednet_tag` | Config overrides, route subscriptions, firewall rules. Position with `before` / `after`. |
| `definednet_route` | Routable CIDRs plus firewall rules with `local_cidr`. |
| `definednet_network` | IPv4 prefix is optional; the IPv6 prefix is always generated by the API. Network-wide config overrides. |
| `definednet_host` | Host records for lighthouses, relays, and other hosts whose identity must exist before they boot. |
| `definednet_join_key` | Reusable enrollment secret for fleets; the hosts it enrolls stay outside Terraform. Needs join keys enabled for the organization. |
| `data.definednet_network` | Look up a network by `id` or exact `name`. |
| `data.definednet_role` | Look up a role by `id` or exact `name`. |
| `data.definednet_route` | Look up a route by `id` or exact `name`. |
Expand All @@ -46,7 +48,9 @@ The API key needs the `<resource>:create|read|update|delete` scopes for each
resource type it manages, plus `<resource>:list` for lookups by name in the
network, role, and route data sources. Domains use `domains:create|read|delete`
plus `domains:verify` for `definednet_domain_verification`; endpoint OIDC
providers use `endpoint-oidc-providers:create|read|update|delete`.
providers use `endpoint-oidc-providers:create|read|update|delete`. Hosts use
`hosts:create|read|update|delete`, plus `networks:read` when `ipv4_address` is
omitted. Join keys use `join-keys:create|read|update|delete`.
Existing resources can be brought under management with `terraform import`;
see the import examples in `docs/`.

Expand All @@ -64,6 +68,27 @@ only accepted spelling of the role's own ID, and the resource reports it back
as `self`, so copying one role's `firewall_rules` into another role grants the
copy's own hosts. Use the `definednet_role` data source to read literal IDs.

### Enrolling hosts

There are two ways to put machines on a network:

- **Join keys** for fleets. A `definednet_join_key` is a reusable secret scoped
to a network, role, and tags; every machine that enrolls with it becomes a
new host named after its hostname. Terraform manages the key, not the hosts. The
token is stored (sensitive) in state and is returned only on create, so it
is null after import.
- **A host record** for lighthouses, relays, and pets whose identity must
exist before they boot, for example because other config references their
address. `definednet_host` creates the record and mints a single-use
`enrollment_code` (sensitive, null after import) to pass to dnclient, for
example through cloud-init. Terraform does not track enrollment.

A code dies at first use, so a rebuilt machine needs a new one: put whatever
replaces the machine, such as its AMI ID, in `enrollment_code_triggers`, and
the new code flows into the machine's user data in the same apply. The rebuilt
machine takes over the existing host record; the previous machine's certificate
is not revoked, so run one machine per host.

### Endpoint OIDC client secrets

The API never returns a provider's client secret, so Terraform cannot see a
Expand Down Expand Up @@ -125,6 +150,9 @@ a time and stay within the free plan's limits (two routes, ten networks).
| `DEFINEDNET_ENDPOINT` | Optional; defaults to `https://api.defined.net`. |
| `DEFINEDNET_ACC_DOMAIN` | Domain the verification tests verify. Defaults to `tf-acc.test.undefined.works`, whose TXT record matches only the shared test organization. Against your own organization, set a domain you control and publish the `verification_record_name` / `verification_record_value` the domain gets there. |

The join key tests skip themselves when the organization does not have join
keys enabled, which the API reports as a 404 on `/v1/join-keys`.

Every object the tests create is named with a `tf-acc` prefix. `make sweep`
deletes those, plus the `DEFINEDNET_ACC_DOMAIN` domain, if a run is interrupted.

Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "definednet Provider"
description: |-
Manage Defined Networking networks, roles, tags, routes, SSO domains, and endpoint OIDC providers through the Defined Networking API https://docs.defined.net/api/defined-networking-api/.
Manage Defined Networking networks, roles, tags, routes, hosts, join keys, SSO domains, and endpoint OIDC providers through the Defined Networking API https://docs.defined.net/api/defined-networking-api/.
Create an API key at https://admin.defined.net/settings/api-keys with the scopes for the resources you manage (for example roles:create, roles:read, roles:update, roles:delete).
---

# definednet Provider

Manage Defined Networking networks, roles, tags, routes, SSO domains, and endpoint OIDC providers through the [Defined Networking API](https://docs.defined.net/api/defined-networking-api/).
Manage Defined Networking networks, roles, tags, routes, hosts, join keys, SSO domains, and endpoint OIDC providers through the [Defined Networking API](https://docs.defined.net/api/defined-networking-api/).

Create an API key at https://admin.defined.net/settings/api-keys with the scopes for the resources you manage (for example `roles:create`, `roles:read`, `roles:update`, `roles:delete`).

Expand Down
2 changes: 1 addition & 1 deletion docs/resources/endpoint_oidc_provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ resource "definednet_endpoint_oidc_provider" "google" {
> **NOTE**: [Write-only arguments](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments) are supported in Terraform 1.11 and later.

- `auth_validity_hours` (Number) How long a sign-in stays valid, 1 to 43800 hours. Omit for the API default.
- `auto_assign_cidrs` (Set of String) IPv4 prefix hosts enrolling through this provider draw addresses from: either empty or the network's own `ipv4_cidr`, which the API rejects any other prefix for. IPv6 is always assigned and is not listed here; the full stored list is the network's `cidrs`. Omit to accept the server's value (and the new network's default when `network_id` changes), set `[]` to assign IPv6 only. On a certificate version 1 network the API always assigns IPv4, so `[]` there shows a diff on every plan.
- `auto_assign_cidrs` (Set of String) IPv4 prefix hosts enrolling through this provider draw addresses from: either empty or the network's own `ipv4_cidr`, which the API rejects any other prefix for. IPv6 is always assigned and is not listed here; the full stored list is the network's `cidrs`. Omit to accept the server's value (and the new network's default when `network_id` changes), set `[]` to assign IPv6 only. A certificate version 1 network always assigns IPv4, so the API rejects `[]` there and the provider rejects it at plan time; setting `[]` needs the `networks:read` scope, because the provider reads the network to check its certificate version.
- `client_secret` (String, Sensitive) OAuth client secret, stored in state. Exactly one of `client_secret` and `client_secret_wo` must be set.
- `client_secret_wo` (String, [Write-only](https://developer.hashicorp.com/terraform/language/resources/ephemeral#write-only-arguments)) OAuth client secret as a write-only argument: sent to the API but never stored in plan or state. Requires `client_secret_wo_version`; change the version to re-send a rotated secret.
- `client_secret_wo_version` (Number) Version counter for `client_secret_wo`. Terraform cannot see the write-only value, so increment this to trigger an update that re-sends it.
Expand Down
98 changes: 98 additions & 0 deletions docs/resources/host.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "definednet_host Resource - definednet"
subcategory: ""
description: |-
Manages a host record: the identity, addresses, role, and tags a machine gets when it enrolls. Use it for lighthouses, relays, and other hosts whose identity must exist before they boot; enroll fleets with a definednet_join_key instead, whose hosts stay outside Terraform.
Creating the host mints a single-use enrollment_code; pass it to the machine, for example through cloud-init user data, and dnclient redeems it to enroll as this host. Terraform does not track enrollment. The code dies at first use, so a rebuilt machine needs a new one: put whatever replaces the machine, such as its AMI ID, in enrollment_code_triggers, and the new code flows into the user data in the same apply (with aws_instance, set user_data_replace_on_change = true). A machine replaced for any other reason, such as -replace, a taint, a failed provision retried later, or a termination outside Terraform, reuses the stored code, which may be consumed or expired; change a trigger value in the same apply. Terraform destroys the old instance before it re-mints the code, so unblock a blocked host first. Re-enrolling keeps the record and its addresses but does not revoke the previous machine's certificate; run one machine per host. To give a rebuilt machine a new record instead, replace the host with lifecycle { replace_triggered_by } pointing at a resource upstream of the machine, such as a terraform_data holding the AMI ID; pointing at the machine itself is a cycle once it uses the code.
The API key needs the hosts:create, hosts:read, hosts:update, hosts:delete, and hosts:enroll scopes, plus networks:read when ipv4_address is omitted.
A replacement deletes the old host before creating the new one; with lifecycle { create_before_destroy = true } the new host also needs a different name, and a different ipv4_address if one is set, because both are unique within the network.
---

# definednet_host (Resource)

Manages a host record: the identity, addresses, role, and tags a machine gets when it enrolls. Use it for lighthouses, relays, and other hosts whose identity must exist before they boot; enroll fleets with a `definednet_join_key` instead, whose hosts stay outside Terraform.

Creating the host mints a single-use `enrollment_code`; pass it to the machine, for example through cloud-init user data, and dnclient redeems it to enroll as this host. Terraform does not track enrollment. The code dies at first use, so a rebuilt machine needs a new one: put whatever replaces the machine, such as its AMI ID, in `enrollment_code_triggers`, and the new code flows into the user data in the same apply (with `aws_instance`, set `user_data_replace_on_change = true`). A machine replaced for any other reason, such as `-replace`, a taint, a failed provision retried later, or a termination outside Terraform, reuses the stored code, which may be consumed or expired; change a trigger value in the same apply. Terraform destroys the old instance before it re-mints the code, so unblock a blocked host first. Re-enrolling keeps the record and its addresses but does not revoke the previous machine's certificate; run one machine per host. To give a rebuilt machine a new record instead, replace the host with `lifecycle { replace_triggered_by }` pointing at a resource upstream of the machine, such as a `terraform_data` holding the AMI ID; pointing at the machine itself is a cycle once it uses the code.

The API key needs the `hosts:create`, `hosts:read`, `hosts:update`, `hosts:delete`, and `hosts:enroll` scopes, plus `networks:read` when `ipv4_address` is omitted.

A replacement deletes the old host before creating the new one; with `lifecycle { create_before_destroy = true }` the new host also needs a different `name`, and a different `ipv4_address` if one is set, because both are unique within the network.

## Example Usage

```terraform
resource "definednet_host" "lighthouse" {
network_id = definednet_network.main.id
name = "lighthouse-ams"
role_id = definednet_role.lighthouse.id
is_lighthouse = true
ipv4_address = cidrhost(definednet_network.main.ipv4_cidr, 10)
static_addresses = ["203.0.113.10:4242", "lighthouse-ams.example.com:4242"]
listen_port = 4242
tags = ["site:ams"]

config_overrides = [
{ key = "lighthouse.serve_dns", value = jsonencode(true) },
]

# A new AMI means a new machine, and a machine enrolls only with an unused
# code, so the change mints one. The host record and its addresses stay.
enrollment_code_triggers = { ami = data.aws_ami.ubuntu.id }
}

resource "aws_instance" "lighthouse" {
ami = data.aws_ami.ubuntu.id
user_data = templatefile("${path.module}/cloud-init.yaml", {
enrollment_code = definednet_host.lighthouse.enrollment_code
})
# Cloud-init runs only on first boot, so a new code needs a new instance.
user_data_replace_on_change = true
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `name` (String) Host name, 1-255 characters, unique within the network. `any` is reserved.
- `network_id` (String) Network the host belongs to. Changing it replaces the host.

### Optional

- `config_overrides` (Attributes Set) Nebula config overrides for this host, which win over its tags' and network's. `value` is JSON, so use `jsonencode()`. (see [below for nested schema](#nestedatt--config_overrides))
- `enrollment_code_lifetime_seconds` (Number) How long the next minted code stays redeemable, 60-604800 seconds. Omit for the API default of 86400 (one day). Changing it alone does not mint a new code.
- `enrollment_code_triggers` (Map of String) Arbitrary values whose change, including removing the map, mints a new `enrollment_code`, such as the machine's AMI ID. The host record itself is unchanged. On an existing host the values must be known at plan time, so the plan can say whether a code will be minted.
- `ipv4_address` (String) Overlay IPv4 address, inside the network's `ipv4_cidr`. Omit to have the API assign the next free address at creation, or none on a network without IPv4. A host without one, such as a host created before its network gained an `ipv4_cidr`, gets the configured address in place; changing an existing address replaces the host.
- `is_lighthouse` (Boolean) Whether the host is a lighthouse. A lighthouse needs `static_addresses` and a non-zero `listen_port`, and cannot also be a relay. Changing it replaces the host. Defaults to `false`.
- `is_relay` (Boolean) Whether the host is a relay. A relay needs a non-zero `listen_port`. Changing it replaces the host. Defaults to `false`.
- `listen_port` (Number) UDP port Nebula listens on, 0-65535. `0` picks a random port, which lighthouses and relays cannot use. Defaults to `0`.
- `role_id` (String) Role whose firewall rules apply to the host.
- `static_addresses` (Set of String) Public `ip:port` or `hostname:port` addresses other hosts reach this one at, at most 100. Nebula tries every address, so order carries no meaning and the API drops duplicates.
- `tags` (Set of String) Tags in `key:value` form. Tags that do not exist yet are created by the API.

### Read-Only

- `enrollment_code` (String, Sensitive) Single-use code dnclient redeems to enroll a machine as this host, minted at creation and again whenever `enrollment_code_triggers` changes. The API never returns it afterwards, so it is null after import until the triggers change.
- `id` (String) Host ID, e.g. `host-24NVITAMHMJ6WU2OXDL63L4JCI`.
- `ip_addresses` (List of String) Every overlay address the host has, IPv4 first. Hosts on a network with an IPv6 prefix always get an IPv6 address.
- `is_blocked` (Boolean) Whether the host is blocked from the network. Blocking is managed outside Terraform; a blocked host cannot enroll or connect.

<a id="nestedatt--config_overrides"></a>
### Nested Schema for `config_overrides`

Required:

- `key` (String) Dotted nebula config key, e.g. `lighthouse.dns.host`.
- `value` (String) JSON-encoded value for the key.

## Import

Import is supported using the following syntax:

The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:

```shell
terraform import definednet_host.lighthouse host-24NVITAMHMJ6WU2OXDL63L4JCI
```
Loading
Loading