From 70c11bf0b16d13b3d7ecfa872cc8ea447165296f Mon Sep 17 00:00:00 2001 From: fullykubed Date: Thu, 19 Dec 2024 17:56:09 -0500 Subject: [PATCH] feat: adds new cidr functions --- docs/functions/cidr_contains.md | 27 +++++++ docs/functions/cidr_count_hosts.md | 26 +++++++ docs/functions/cidrs_overlap.md | 26 +++++++ go.mod | 22 ++++-- go.sum | 37 ++++++++++ provider/cidr_contains_function.go | 70 ++++++++++++++++++ provider/cidrs_count_hosts.go | 81 +++++++++++++++++++++ provider/cidrs_overlap.go | 111 +++++++++++++++++++++++++++++ provider/cidrs_overlap_test.go | 63 ++++++++++++++++ provider/provider.go | 3 + 10 files changed, 461 insertions(+), 5 deletions(-) create mode 100644 docs/functions/cidr_contains.md create mode 100644 docs/functions/cidr_count_hosts.md create mode 100644 docs/functions/cidrs_overlap.md create mode 100644 provider/cidr_contains_function.go create mode 100644 provider/cidrs_count_hosts.go create mode 100644 provider/cidrs_overlap.go create mode 100644 provider/cidrs_overlap_test.go diff --git a/docs/functions/cidr_contains.md b/docs/functions/cidr_contains.md new file mode 100644 index 0000000..4c0f46d --- /dev/null +++ b/docs/functions/cidr_contains.md @@ -0,0 +1,27 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cidr_contains function - pf" +subcategory: "" +description: |- + Returns true if the provided IP address is in the indicated CIDR block. +--- + +# function: cidr_contains + + + + + +## Signature + + +```text +cidr_contains(ipv4_cidr_block string, ipv4_address string) bool +``` + +## Arguments + + +1. `ipv4_cidr_block` (String) The CIDR block to check against +1. `ipv4_address` (String) The IP to use for the check + diff --git a/docs/functions/cidr_count_hosts.md b/docs/functions/cidr_count_hosts.md new file mode 100644 index 0000000..38490ee --- /dev/null +++ b/docs/functions/cidr_count_hosts.md @@ -0,0 +1,26 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cidr_count_hosts function - pf" +subcategory: "" +description: |- + Returns the number of hosts / ip addresses in a given CIDR range. +--- + +# function: cidr_count_hosts + + + + + +## Signature + + +```text +cidr_count_hosts(cidr_block string) number +``` + +## Arguments + + +1. `cidr_block` (String) The CIDR block to count + diff --git a/docs/functions/cidrs_overlap.md b/docs/functions/cidrs_overlap.md new file mode 100644 index 0000000..2bf2e5e --- /dev/null +++ b/docs/functions/cidrs_overlap.md @@ -0,0 +1,26 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cidrs_overlap function - pf" +subcategory: "" +description: |- + Returns true if at least one of the listed CIDR ranges overlaps with another. +--- + +# function: cidrs_overlap + + + + + +## Signature + + +```text +cidrs_overlap(cidr_blocks list of string) bool +``` + +## Arguments + + +1. `cidr_blocks` (List of String) The CIDR blocks to check against + diff --git a/go.mod b/go.mod index fab5297..dbbe7b3 100644 --- a/go.mod +++ b/go.mod @@ -5,25 +5,36 @@ go 1.22.3 require ( github.com/hashicorp/terraform-plugin-framework v1.13.0 github.com/hashicorp/terraform-plugin-framework-validators v0.16.0 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect github.com/agext/levenshtein v1.2.2 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/fatih/color v1.18.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-checkpoint v0.5.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.6.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/hcl/v2 v2.20.1 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/hc-install v0.9.0 // indirect + github.com/hashicorp/hcl/v2 v2.23.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect + github.com/hashicorp/terraform-exec v0.21.0 // indirect + github.com/hashicorp/terraform-json v0.23.0 // indirect github.com/hashicorp/terraform-plugin-go v0.25.0 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect + github.com/hashicorp/terraform-plugin-testing v1.11.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.2 // indirect @@ -38,8 +49,9 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/zclconf/go-cty v1.14.4 // indirect - golang.org/x/mod v0.17.0 // indirect + github.com/zclconf/go-cty v1.15.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect diff --git a/go.sum b/go.sum index 534d18f..6503e69 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= +github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= @@ -5,6 +7,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -24,24 +28,46 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE= +github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg= github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= +github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= +github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= +github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= +github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= +github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= github.com/hashicorp/terraform-plugin-framework v1.12.0 h1:7HKaueHPaikX5/7cbC1r9d1m12iYHY+FlNZEGxQ42CQ= github.com/hashicorp/terraform-plugin-framework v1.12.0/go.mod h1:N/IOQ2uYjW60Jp39Cp3mw7I/OpC/GfZ0385R0YibmkE= github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= @@ -56,6 +82,10 @@ github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9T github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE= github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 h1:wyKCCtn6pBBL46c1uIIBNUOWlNfYXfXpVo16iDyLp8Y= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0/go.mod h1:B0Al8NyYVr8Mp/KLwssKXG1RqnTk7FySqSn4fRuLNgw= +github.com/hashicorp/terraform-plugin-testing v1.11.0 h1:MeDT5W3YHbONJt2aPQyaBsgQeAIckwPX41EUHXEn29A= +github.com/hashicorp/terraform-plugin-testing v1.11.0/go.mod h1:WNAHQ3DcgV/0J+B15WTE6hDvxcUdkPPpnB1FR3M910U= github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= @@ -110,13 +140,20 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= diff --git a/provider/cidr_contains_function.go b/provider/cidr_contains_function.go new file mode 100644 index 0000000..f9ce3c7 --- /dev/null +++ b/provider/cidr_contains_function.go @@ -0,0 +1,70 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/function" + "net" +) + +var ( + _ function.Function = CIDRContainsFunction{} +) + +func NewCIDRContainsFunction() function.Function { + return CIDRContainsFunction{} +} + +type CIDRContainsFunction struct{} + +func (f CIDRContainsFunction) Metadata(_ context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "cidr_contains" +} + +func (f CIDRContainsFunction) Definition(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Returns true if the provided IP address is in the indicated CIDR block.", + Parameters: []function.Parameter{ + function.StringParameter{ + AllowNullValue: false, + AllowUnknownValues: false, + Description: "The CIDR block to check against", + Name: "ipv4_cidr_block", + }, + function.StringParameter{ + AllowNullValue: false, + AllowUnknownValues: false, + Description: "The IP to use for the check", + Name: "ipv4_address", + }, + }, + Return: function.BoolReturn{}, + } +} + +func (f CIDRContainsFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var cidrStr, ipStr string + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &cidrStr, &ipStr)) + if resp.Error != nil { + return + + } + + ip := net.ParseIP(ipStr) + if ip == nil { + resp.Error = function.ConcatFuncErrors(resp.Error, function.NewFuncError(fmt.Sprintf("Invalid IP address: %s\n", ipStr))) + return + } + + _, cidrNet, err := net.ParseCIDR(cidrStr) + if err != nil { + resp.Error = function.ConcatFuncErrors(resp.Error, function.NewFuncError(fmt.Sprintf("Invalid CIDR block: %s\n", cidrStr))) + return + } + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, cidrNet.Contains(ip))) +} diff --git a/provider/cidrs_count_hosts.go b/provider/cidrs_count_hosts.go new file mode 100644 index 0000000..a9c2d99 --- /dev/null +++ b/provider/cidrs_count_hosts.go @@ -0,0 +1,81 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/function" + "net" +) + +var ( + _ function.Function = CIDRCountHosts{} +) + +func NewCIDRCountHosts() function.Function { + return CIDRCountHosts{} +} + +type CIDRCountHosts struct{} + +func (f CIDRCountHosts) Metadata(_ context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "cidr_count_hosts" +} + +func (f CIDRCountHosts) Definition(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Returns the number of hosts / ip addresses in a given CIDR range.", + Parameters: []function.Parameter{ + function.StringParameter{ + AllowNullValue: false, + AllowUnknownValues: false, + Description: "The CIDR block to count", + Name: "cidr_block", + }, + }, + Return: function.Int64Return{}, + } +} + +func (f CIDRCountHosts) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var cidrStr string + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &cidrStr)) + if resp.Error != nil { + return + + } + + _, cidrNet, err := net.ParseCIDR(cidrStr) + if err != nil { + resp.Error = function.ConcatFuncErrors(resp.Error, function.NewFuncError(fmt.Sprintf("Invalid CIDR block: %s\n", cidrStr))) + return + } + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, countHosts(cidrNet))) +} + +func countHosts(cidrNet *net.IPNet) int64 { + maskOnes, _ := cidrNet.Mask.Size() + hostBits := 32 - maskOnes + total := int64(1) << hostBits // total addresses in the subnet + + // Edge cases: + // /31: 2 usable addresses (RFC 3021) + // /32: 1 address, considered usable + // Otherwise: total - 2 for the network and broadcast addresses + switch maskOnes { + case 31: + return 2 + case 32: + return 1 + default: + if total > 2 { + return total - 2 + } + // If the subnet is somehow smaller or invalid, just return 0 + return 0 + } +} diff --git a/provider/cidrs_overlap.go b/provider/cidrs_overlap.go new file mode 100644 index 0000000..8d3dd7b --- /dev/null +++ b/provider/cidrs_overlap.go @@ -0,0 +1,111 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package provider + +import ( + "context" + "encoding/binary" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/types" + "net" + "sort" +) + +var ( + _ function.Function = CIDRsOverlapFunction{} +) + +func NewCIDRsOverlapFunction() function.Function { + return CIDRsOverlapFunction{} +} + +type CIDRsOverlapFunction struct{} + +func (f CIDRsOverlapFunction) Metadata(_ context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "cidrs_overlap" +} + +func (f CIDRsOverlapFunction) Definition(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Returns true if at least one of the listed CIDR ranges overlaps with another.", + Parameters: []function.Parameter{ + function.ListParameter{ + AllowNullValue: false, + AllowUnknownValues: false, + Description: "The CIDR blocks to check against", + Name: "cidr_blocks", + ElementType: types.StringType, + }, + }, + Return: function.BoolReturn{}, + } +} + +func (f CIDRsOverlapFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var cidrStrs []string + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &cidrStrs)) + if resp.Error != nil { + return + + } + + var cidrs []*net.IPNet + + for _, s := range cidrStrs { + _, cidrNet, err := net.ParseCIDR(s) + if err != nil { + resp.Error = function.ConcatFuncErrors(resp.Error, function.NewFuncError(fmt.Sprintf("Invalid CIDR block: %s\n", s))) + return + } + cidrs = append(cidrs, cidrNet) + } + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, AnyCIDRsOverlap(cidrs))) +} + +type ipRange struct { + start uint32 + end uint32 +} + +func AnyCIDRsOverlap(cidrs []*net.IPNet) bool { + var ranges []ipRange + + for _, c := range cidrs { + start, end := networkRange(c) + ranges = append(ranges, ipRange{start, end}) + } + + // Sort ranges by their start address so that we can do a linear + // search for overlaps + sort.Slice(ranges, func(i, j int) bool { + return ranges[i].start < ranges[j].start + }) + + // Check for overlap between adjacent ranges + for i := 1; i < len(ranges); i++ { + if ranges[i].start <= ranges[i-1].end { + return true + } + } + + return false +} + +// networkRange returns the start and end addresses of a CIDR as uint32 +func networkRange(network *net.IPNet) (uint32, uint32) { + ip := network.IP.To4() + maskOnes, _ := network.Mask.Size() + + ipInt := binary.BigEndian.Uint32(ip) + shift := uint32(32 - maskOnes) + numAddrs := uint32(1 << shift) + + start := ipInt & (0xFFFFFFFF << shift) + end := start + numAddrs - 1 + + return start, end +} diff --git a/provider/cidrs_overlap_test.go b/provider/cidrs_overlap_test.go new file mode 100644 index 0000000..e39d022 --- /dev/null +++ b/provider/cidrs_overlap_test.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package provider_test + +import ( + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "terraform-provider-pf/provider" + "testing" +) + +func TestCIDRsOverlapFunction_NoOverlap(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "pf": providerserver.NewProtocol6WithError(provider.New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + output "test" { + value = provider::pf::cidrs_overlap(["10.0.0.0/16", "11.0.0.0/11"]) + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("test", knownvalue.Bool(false)), + }, + }, + }, + }) +} + +func TestCIDRsOverlapFunction_Overlap(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "pf": providerserver.NewProtocol6WithError(provider.New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + output "test" { + value = provider::pf::cidrs_overlap(["10.0.0.0/16", "10.0.0.0/8"]) + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("test", knownvalue.Bool(true)), + }, + }, + }, + }) +} diff --git a/provider/provider.go b/provider/provider.go index 8320047..4cac979 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -167,6 +167,9 @@ func (p *PanfactumProvider) Functions(ctx context.Context) []func() function.Fun return []func() function.Function{ NewSanitizeAWSTagsFunction, NewSanitizeKubeLabelsFunction, + NewCIDRContainsFunction, + NewCIDRsOverlapFunction, + NewCIDRCountHosts, } }