Skip to content

Numerous ngrok_reserved_domain glitches #34

@jwadolowski

Description

@jwadolowski

Overview

After many years of using ngrok and creating ad-hoc configs via web UI I've recently decided it's about time to move all of that into Terraform code. In order to put it all together I started with ngrok_reserved_domain resource.

Unfortunately the migration process revealed quite a few hiccups, therefore I decided to sum it all up in a ticket (please let me know if there's a better way to report such issues). Long story short - it does work, although developer experience wasn't particularly great and there was more rough edges than anticipated.

For reference:

$ terraform -v
Terraform v1.9.6
on darwin_arm64
+ provider registry.terraform.io/hashicorp/aws v5.69.0
+ provider registry.terraform.io/ngrok/ngrok v0.3.0

It's going to be quite lengthy (sorry in advance!).

Issues

1. Required vs optional arguments

Documentation says that all the arguments are optional. That simply doesn't make any sense since user has to provide either a string (so it becomes a subdomain of <user-provided-string>.ngrok.app) or an FQDN.

resource "ngrok_reserved_domain" "this" {
}

Luckily the API returns ERR_NGROK_422 with "The reserved domain name update failed because no values were provided. Specify at least one value." message in case of above.

Suggested actions:

  1. Documentation update (I guess it may require provider codebase changes as well)
  2. Input validation - if user didn't provide required arguments it doesn't make sense to even initiate HTTP communication with the API

2. domain vs name dilemma

According to the docs either name or domain can be specified. Here's what it says at the time of writing:

  • domain - (String) hostname of the reserved domain
  • name - (String) the domain name to reserve. It may be a full domain name like app.example.com. If the name does not contain a '.' it will reserve that subdomain on ngrok.io.

I find above extremely confusing. Let's say I'd like to use dev.example.com as my ngrok domain. Which of the following should I use?

resource "ngrok_reserved_domain" "foo" {
  name = "dev.example.com"
}

# OR...

resource "ngrok_reserved_domain" "bar" {
  domain = "dev.example.com"
}

# OR... perhaps both arguments should be provided?

What's interesting is that according to API reference /reserved_domains endpoint does not support name parameter at all, but... ReservedDomainCreate struct (which becomes POST /reserved_domains payload) does include it.

Let's break it down how it behaves in real life.

Argument API response Notes
name = "01b747d1" gist 01b747d1.ngrok.app created successfully; mind the name string must be unique
name = "a2158993.ngrok.pro" gist Here's how to create a built-in subdomain different than default <whatever>.ngrok.app
name = "0f31e481.example.com" gist Domain successfully created
domain = "45c97ae6" gist That's clearly an invalid domain. The API returns 400 with ERR_NGROK_448
domain = "334b8d11.example.com" gist Domain successfully created
name = "foo" + domain = "bar.example.com" gist ERR_NGROK_449 response with "You may only specify one of 'name' or 'domain'." message

Expected state/things to consider:

  • It should be a no brainer which argument to use in order to register a custom domain - at the time of writing both name = "foo.example.com" and domain = "foo.example.com" result in the exact same state. To avoid unnecessary confusion ngrok_reserved_domain should expose only one argument that controls this aspect (probably name as it supports both scenarios - FQDN + <placeholder>.ngrok.(app|dev|pro|pizza|<etc>))
  • API reference should list all the params supported by POST /reserved_domain endpoint
  • Resource docs do not mention how to deal with <foo>.ngrok.(dev|pro|pizza|io) and <bar>.ngrok-free.(app|dev) cases
  • Yet another doc glitch - by default ngrok.app gets used, not ngrok.io (I'm on the OG Pro plan, free account may behave differently)

Aside from above I stumbled upon a few other minor issues while experimenting with ngrok_reserved_domain resource:

2a. certificate_management_status discrepancies in HTTP response (click to expand)

2a certificate_management_status discrepancies in HTTP response

That's probably irrelevant and depends on some async background process, but it caught my attention anyway. Response body of successful POST /reserved_domains request sometimes includes "certificate_management_status": null element.

{
  "id": "<redacted>",
  "uri": "https://api.ngrok.com/reserved_domains/<redacted>",
  "created_at": "2024-09-27T20:14:54Z",
  "domain": "<redacted>",
  "region": "",
  "cname_target": "<redacted>",
  "http_endpoint_configuration": null,
  "https_endpoint_configuration": null,
  "certificate": null,
  "certificate_management_policy": {
    "authority": "letsencrypt",
    "private_key_type": "ecdsa"
  },
  "certificate_management_status": {
    "renews_at": null,
    "provisioning_job": {
      "error_code": null,
      "msg": "Managed certificate provisioning in progress.",
      "started_at": "2024-09-27T20:14:54Z",
      "retries_at": null
    }
  },
  "acme_challenge_cname_target": null,
  "error_redirect_url": null
}

vs

{
  "id": "<redacted>",
  "uri": "https://api.ngrok.com/reserved_domains/<redacted>",
  "created_at": "2024-09-27T20:16:45Z",
  "domain": "<redacted>",
  "region": "",
  "cname_target": "<redacted>",
  "http_endpoint_configuration": null,
  "https_endpoint_configuration": null,
  "certificate": null,
  "certificate_management_policy": {
    "authority": "letsencrypt",
    "private_key_type": "ecdsa"
  },
  "certificate_management_status": null,
  "acme_challenge_cname_target": null,
  "error_redirect_url": null
}
2b. Undocumented ERR_NGROK_448 and ERR_NGROK_449 codes (click to expand)

2b. Undocumented ERR_NGROK_448 and ERR_NGROK_449 codes

Neither ERR_NGROK_448 nor ERR_NGROK_449 is documented here.

2c. Broken 404s for https://ngrok.com/docs/* URLs (click to expand)

2c. Broken 404s for https://ngrok.com/docs/* URLs

I thought that perhaps ERR_NGROK_448 wasn't listed for some reason and visited https://ngrok.com/docs/errors/err_ngrok_448/. Here's how it looks for me (some .js and .css requests end with 404):

ngrok com_docs_errors_err_ngrok_448_

The problem affects any https://ngrok.com/docs/* URL that does not exist, i.e. https://ngrok.com/docs/foo or https://ngrok.com/docs/bar

3. certificate_management_policy defaults

There's a glitch in the docs - it says that the certificate_management_policy block supports the following values:

certificate_management_policy {
  authority = "letsencrypt" # default
  private_key_type = "rsa" # default, can be changed to "ecdsa"
}

The thing is all my attempts (without explicitly defined certificate_management_policy block) ended with the following response body:

{
  "id": "...",
  "...": "...",
  "certificate_management_policy": {
    "authority": "letsencrypt",
    "private_key_type": "ecdsa"
  }
}

Could it be that rsa is no longer the default?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions