How do you validate IP addresses in Terraform?
Asked Answered
K

5

5

I am using Terraform to deploy code into my Azure infrastructure.

I have an Azure Firewall with some Network Rules which has a bunch of free text IP addresses as Source/Destination. An example below; [note that the real IP addresses have been changed for the sake of this example];

NETWORK RULES COLLECTION

  network_rule_collection {
    name     = "test_rules"
    priority = 400
    action   = "Allow"
    rule {
      name                  = "test-rule-1"
      protocols             = ["Any"]
      source_addresses      = ["123.0.2.10/32"]
      destination_ports     = ["1234"]
      destination_addresses = ["124.82.189.200/32","125,82.189.200/32","126..82.189.200/32"]
    }
  }
}

[note that the IP addresses have errors in them purposely to demonstrate]

However, when I run a Terraform Validate, it shows no errors (even when theres invalid IP addresses entered above)

So I need a way for free text IP addresses to be validated as part of a Terraform validate/plan. Currently, Terraform accepts this and deploys out invalid IPs into the infrastructure.

Kenyakenyatta answered 16/2, 2022 at 14:37 Comment(1)
A
6

Since the arguments you're populating are defined as taking CIDR block addresses, in principle the provider could validate these, but it seems like it doesn't have its own validation rules in this particular case and so it's relying on the remote API's validation that will therefore take effect only during the apply step.

It may be worth opening an issue in the provider's GitHub repository to discuss the possibility of adding built-in validation here, but for the sake of this answer I'll assume that's impossible and discuss how you can define validation rules in your own configuration instead.


With these values hard-coded directly inside the resource configuration, there is no recourse for custom validation because the contents of a resource block are (aside from some special arguments like count and for_each) entirely the responsibility of the corresponding provider.

However, if you are intending to write a shared Terraform module that takes these IP addresses as input variables and then refers to them from the resource configuration, you can write custom validation rules to describe additional constraints that must be true in order for a particular value of that variable to be considered valid.

The condition argument in a validation block expects a boolean value which is true if the condition holds and false if the condition is not met. However, since Terraform also has lots of built-in functions that are defined to fail with an error if their own validation rules aren't met, Terraform also has a special can function which evaluates an experssion in a special context where a failure is translated to a false result, and any success is translated to a true result, thus meeting the expectations of the condition argument.

A typical strategy then is to identify an existing Terraform function which has the same validation rule that you want to apply and to call that value inside can to use its success or failure as the validation condition.

For CIDR block addresses in particular, Terraform has a small number of functions which take those as input and produce derived CIDR block addresses or IP addresses. For example, cidrnetmask takes an IPv4 CIDR block address and returns the same address in netmask notation. It will fail if the given string isn't a valid IPv4 CIDR block, and so it seems like a good candidate for describing the rule you want to check here:

variable "source_address" {
  type = string

  validation {
    condition     = can(cidrnetmask(var.source_address))
    error_message = "Must be a valid IPv4 CIDR block address."
  }
}

Your example here has an extra requirement of accepting multiple addresses, and so that requires a slightly more complicated validation rule to test whether the condition holds for all members of the given collection, which is what the alltrue function is intended for:

variable "source_addresses" {
  type = set(string)

  validation {
    condition = alltrue([
      for a in var.source_addresses : can(cidrnetmask(a))
    ])
    error_message = "All elements must be valid IPv4 CIDR block addresses."
  }
}

The [ for ... ] expression here is producing a list of boolean results from trying can(cidrnetmask(a)) for each element a of the set. alltrue then interprets that result and returns a single true only if every element of the list is true.

In order for these rules to apply, you'll need to pass the potentially-invalid IP addresses through these input variables, instead of providing them directly inside the resource block as in your example. That only really makes sense if you are intending to write a reusable module where callers will be providing different IP addresses for each instance. If you intend to have just a fixed set of IP addresses anyway, it'd be simpler to just make sure they are valid when you initially specify them and trust that they'll therefore stay valid in future unless changed.

Alexalexa answered 16/2, 2022 at 16:38 Comment(1)
or to test an individual address in non cidr format ``` can(cidrnetmask("${var.a}/32")) ```Truism
T
3

or to test an individual address in non cidr format

variable "source_address" {
  type = string

  validation {
    condition     = can(cidrnetmask("${var.source_address}/32"))
    error_message = "Must be a valid IPv4 CIDR block address."
  }
}
 
Truism answered 21/4, 2023 at 11:52 Comment(0)
C
1

According to the documentation, Validate will verify the syntax, not the values.

Validate runs checks that verify whether a configuration is syntactically valid

One option is to define a variable and add a custom validation rule using a regex. Reference

variable "ip_address" {
    type          = string
    description   = "Example to validate IP address."
    validation {
        condition = can(regex("^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",var.ip_address))
        error_message = "Invalid IP address provided."
    }
}
Crocodilian answered 16/2, 2022 at 14:58 Comment(0)
R
1

you can define the IP adresse as a variable and use validation rules :

https://www.terraform.io/language/values/variables#custom-validation-rules

In addition to Type Constraints as described above, a module author can specify arbitrary custom validation rules for a particular variable using a validation block nested within the corresponding variable block:

and then use a regex expression to validate the given IP address take a look at this medium blog to get inspired https://ishusinghkanwar.medium.com/variable-validation-in-terraform-a9c887d987ff

Rubble answered 16/2, 2022 at 14:58 Comment(0)
R
0

With regex:

variable "ip_address" {
  type        = string
  validation {
    condition     = can(regex("^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$", var.ip_address))
    error_message = "The IP address is not valid. Please provide a valid IP address."
  }
}

based on: source: https://mcmap.net/q/24952/-validating-ipv4-addresses-with-regexp

With cidrnetmask (recommended):

variable "ip_address" {
  type        = string
  validation {
    condition     = can(cidrnetmask(join("/", [var.ip_address, "32"])))
    error_message = "The IP address is not valid. Please provide a valid IP address."
  }
}
Reformed answered 8/8 at 9:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.