Skip to content

Add IP allow list support - #252

Open
areina wants to merge 1 commit into
mainfrom
toni/allowlists
Open

areina wants to merge 1 commit into
mainfrom
toni/allowlists

Conversation

@areina

@areina areina commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Adds tiger allowlist create/get/list/update/delete and tiger service allowlist attach/detach, plus matching MCP tools, gated behind TIGER_EXPERIMENTAL.

service_allowlist_detach prompts the user via MCP elicitation before detaching a PROD-tagged service, since detaching silently widens a production service's network exposure.

I manually tested the flow with the following commands:

  export TIGER_EXPERIMENTAL=1   # allow-list commands are still gated

  # 1. Create a throwaway DEV service
  tiger service create --name allowlist-test --cpu shared --environment DEV --no-set-default

  # 2. Baseline: confirm it's reachable with no allow list attached
  tiger db ping allowlist-test --timeout 10s                     # connects

  # 3. Create an allow list that excludes the test machine's IP
  tiger allowlist create --description "allowlist-test excludes-my-ip" --cidr 203.0.113.0/24

  # 4. Attach it, then confirm the connection is now blocked
  tiger service allowlist attach allowlist-test --allow-list <allow-list-id>
  tiger db ping allowlist-test --timeout 8s                      # times out

  # 5. Update the allow list to include the test machine's real IP
  tiger allowlist update <allow-list-id> --cidr <YOUR_IP>/32
  tiger db ping allowlist-test --timeout 8s                      # connects

  # 6. Detach, then confirm the service is unrestricted again
  tiger service allowlist detach allowlist-test --allow-list <allow-list-id>
  tiger db ping allowlist-test --timeout 8s                      # connects

  # 7. Exercise read commands
  tiger allowlist list
  tiger allowlist get <allow-list-id>

  # 8. Clean up
  tiger allowlist delete <allow-list-id> --confirm
  tiger service delete allowlist-test --confirm

@areina
areina force-pushed the toni/allowlists branch 3 times, most recently from d382403 to 5c47f30 Compare October 1, 2026 12:07
Comment thread internal/cmd/allowlist.go
// buildAllowListCmd creates the allowlist group command.
func buildAllowListCmd(app *common.App) *cobra.Command {
cmd := &cobra.Command{
Use: "allowlist",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nathanjcochran not sure about this: should we call it ipallowlists? feels a bit ugly, but more precise. What do you think?

@nathanjcochran nathanjcochran Oct 6, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is tricky 🤔

I think I prefer allowlist over ipallowlist. allowlist is often written as a single word (without a hyphen), which makes it a decent fit for Tiger CLI, since we're trying to move towards short, single-word commands when possible. ipallowlist feels more like several words mashed together, which I don't love, and doesn't match the design of the rest of the CLI. ip-allowlist or ip-allow-list would probably be more correct (since we typically hyphenate the multi-words commands we support, such as tiger db save-password), but it feels too long and awkward for a top-level command imo.

For what it's worth, we write it as "IP allow list" in the docs, rather than as "IP allowlist". Given that, it would probably be more technically correct/consistent to hyphenate it as tiger allow-list here too. But I don't really like that, since it goes against other well-known style guides (e.g. Google's style guide, which lists it as a single word), and would make it a hyphenated multi-word command, which we're generally trying to avoid.

So I think allowlist is probably the best option. The only real downside, imo, is that it doesn't match how we write "allow list" in the docs. But I think that's probably acceptable. It might be worth posing the question to the CLI/MCP Slack channel though, to see if anybody else has opinions.

In any case, we can always adjust it later before we remove the TIGER_EXPERIMENTAL gate if we change our minds. It's also easy to change commands later on in a backwards-compatible way by keeping the old command name as an alias, so it's not like this is set in stone.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For comparison, Neon uses ip-allow as their command, which is not bad, and avoids the whole "allowlist" vs "allow list" question. But the semantics are a bit different - in Neon, each project has just a single IP allowlist, and the ip-allow commands modify that allow list. So for example, neon ip-allow list just lists all of the addresses in the project's IP Allowlist, and neon ip-allow add adds a single address to the list. There is no notion of "listing all of the IP allow lists", which is what our tiger allowlist list command does (which justifies the "stutter" of using "list" twice in that command). So I don't actually think Neon's model is the right thing for us to copy (just mentioning it here as another data point).

Comment thread internal/cmd/allowlist.go
)

// buildAllowListCmd creates the allowlist group command.
func buildAllowListCmd(app *common.App) *cobra.Command {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nathanjcochran I added the command at the root level. However, I'm wondering if we would like to group them? Eventually we will also have vpcs. Not sure if you already discussed about it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last time we discussed adding VPC support, the plan was to add tiger vpc. So I think adding tiger allowlist at the root level makes sense, at least for now 👍.

If we start to feel like the root is getting too many commands in the future, we can re-evaluate and group them under tiger network or something like that. But I don't think that's necessary now - I'd rather keep the commands more easily discoverable and shorter to type.

@areina
areina marked this pull request as ready for review October 1, 2026 13:15
@areina
areina requested a review from a team as a code owner October 1, 2026 13:15
Adds tiger allowlist create/get/list/update/delete and tiger service
allowlist attach/detach, plus matching MCP tools, gated behind
TIGER_EXPERIMENTAL.

service_allowlist_detach prompts the user via MCP elicitation before
detaching a PROD-tagged service, since detaching silently widens a
production service's network exposure.

@nathanjcochran nathanjcochran left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, this looks good to me 👍. Left some minor comments about naming consistency, whether --cidr should be a flag or a positional argument, missing shell completions, and whether we really need to support JSON/YAML output for some of the commands, but nothing major - feel free to push back on anything you disagree with, or address them in follow-up PRs (since this is gated behind TIGER_EXPERIMENTAL, it should be okay to iterate on it after merging). Note that I did not test it myself.

Comment thread internal/cmd/allowlist.go
)

// buildAllowListCmd creates the allowlist group command.
func buildAllowListCmd(app *common.App) *cobra.Command {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last time we discussed adding VPC support, the plan was to add tiger vpc. So I think adding tiger allowlist at the root level makes sense, at least for now 👍.

If we start to feel like the root is getting too many commands in the future, we can re-evaluate and group them under tiger network or something like that. But I don't think that's necessary now - I'd rather keep the commands more easily discoverable and shorter to type.

Comment thread internal/cmd/allowlist.go
// buildAllowListCmd creates the allowlist group command.
func buildAllowListCmd(app *common.App) *cobra.Command {
cmd := &cobra.Command{
Use: "allowlist",

@nathanjcochran nathanjcochran Oct 6, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is tricky 🤔

I think I prefer allowlist over ipallowlist. allowlist is often written as a single word (without a hyphen), which makes it a decent fit for Tiger CLI, since we're trying to move towards short, single-word commands when possible. ipallowlist feels more like several words mashed together, which I don't love, and doesn't match the design of the rest of the CLI. ip-allowlist or ip-allow-list would probably be more correct (since we typically hyphenate the multi-words commands we support, such as tiger db save-password), but it feels too long and awkward for a top-level command imo.

For what it's worth, we write it as "IP allow list" in the docs, rather than as "IP allowlist". Given that, it would probably be more technically correct/consistent to hyphenate it as tiger allow-list here too. But I don't really like that, since it goes against other well-known style guides (e.g. Google's style guide, which lists it as a single word), and would make it a hyphenated multi-word command, which we're generally trying to avoid.

So I think allowlist is probably the best option. The only real downside, imo, is that it doesn't match how we write "allow list" in the docs. But I think that's probably acceptable. It might be worth posing the question to the CLI/MCP Slack channel though, to see if anybody else has opinions.

In any case, we can always adjust it later before we remove the TIGER_EXPERIMENTAL gate if we change our minds. It's also easy to change commands later on in a backwards-compatible way by keeping the old command name as an alias, so it's not like this is set in stone.

Comment on lines +73 to +74
markFlagRequired(cmd, "description")
markFlagRequired(cmd, "cidr")

@nathanjcochran nathanjcochran Oct 6, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider making CIDR a positional argument, instead of a flag? Imo, when a parameter is required, it's usually better for it to be a positional argument than a flag (since flags just require the user to type more, and often make it look like the parameter is optional).

The main reason we don't always use positional arguments elsewhere is because the service ID argument (which is the first argument to many commands) is optional and falls back to the default service if not provided. And that makes parsing subsequent arguments challenging, because their meaning can be ambiguous. But that issue doesn't apply here, since these commands don't take a service ID argument.

So imo, the CIDR should probably be a required positional argument, rather than a flag. It could still be repeatable, assuming we don't also make the description a positional argument. And if we do that, we should probably do the same for the tiger allowlist update command, so we're consistent in how we pass CIDRs to commands (though it does feel a little more awkward in tiger allowlist update, since the CIDRs are optional there 🤔).

Regarding the description, does it really need to be required? It feels to me like it should be optional (some users might not feel the need to add a description, and forcing them to add one just adds friction - especially because some plans only allow a single allowlist anyways). It also feels more natural for it to be a flag if it's optional, in my opinion. Is there a reason it has to be required, or could we relax that rule? Also, what currently happens if someone provides an empty string for the description (i.e. --description="")?

var deleteConfirm bool

cmd := &cobra.Command{
Use: "delete <allow-list-id>",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way you wrote allow-list-id here differs from the command name (allowlist). Especially in usage text like this, I would have expected it to match the rest of the CLI (i.e. delete <allowlist-id>). I think it's fine if the free-form text matches the docs and console (which use "IP allow list"), but I think we should consistently use the single-word allowlist across the CLI surface area (i.e. in usage text, flags, command names, MCP tool names/parameters, etc.), assuming we decide to keep allowlist as the command name.

# Delete an IP allow list without a confirmation prompt
tiger allowlist delete 1234567890 --confirm`,
Args: cobra.ExactArgs(1),
ValidArgsFunction: cobra.NoFileCompletions,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should wire up shell completions for the allowlist IDs here. Same for other arguments/flags that take an allowlist ID.

cmd.Flags().VarP(new(outputFlag), "output", "o", "Output format (json, yaml, table)")
registerFlagCompletion(cmd, "output", outputCompletion())

markFlagRequired(cmd, "allow-list")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be --allowlist if the command is allowlist. Same for the detach command's flag.


// AllowListDeleteInput represents input for allowlist_delete
type AllowListDeleteInput struct {
AllowListID string `json:"allow_list_id"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to other comments, I think this should probably be allowlist_id. Same for other MCP tools that take an allowlist ID as a parameter.

Comment on lines +23 to +24
schema.Properties["allow_list_id"].Description = "Unique identifier of the IP allow list. Use allowlist_list to find IP allow list IDs."
schema.Properties["allow_list_id"].Examples = []any{"1234567890"}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this description/example is duplicated across several tools. It should probably be consolidated in internal/mcp/utils.go, like we do for other MCP tool parameters whose schemas are re-used across multiple tools.

Comment thread internal/cmd/allowlist.go
// buildAllowListCmd creates the allowlist group command.
func buildAllowListCmd(app *common.App) *cobra.Command {
cmd := &cobra.Command{
Use: "allowlist",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For comparison, Neon uses ip-allow as their command, which is not bad, and avoids the whole "allowlist" vs "allow list" question. But the semantics are a bit different - in Neon, each project has just a single IP allowlist, and the ip-allow commands modify that allow list. So for example, neon ip-allow list just lists all of the addresses in the project's IP Allowlist, and neon ip-allow add adds a single address to the list. There is no notion of "listing all of the IP allow lists", which is what our tiger allowlist list command does (which justifies the "stutter" of using "list" twice in that command). So I don't actually think Neon's model is the right thing for us to copy (just mentioning it here as another data point).

Comment thread internal/mcp/utils.go
func allowListOutputFor(allowList api.AllowList) AllowListOutput {
return AllowListOutput{
AllowListID: allowList.AllowListID,
ProjectID: allowList.ProjectID,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we really need to return the project ID here. The MCP is always scoped to just a single project at a time, and we don't return this from any other MCP tools (to my knowledge). I'd probably omit it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants