Skip to content

Commit 6b67230

Browse files
authored
feat: support configuring network restrictions (#76)
1 parent dbb542e commit 6b67230

File tree

4 files changed

+169
-0
lines changed

4 files changed

+169
-0
lines changed

docs/resources/settings.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ resource "supabase_settings" "production" {
2020
statement_timeout = "10s"
2121
})
2222
23+
network = jsonencode({
24+
restrictions = [
25+
"0.0.0.0/0",
26+
"::/0"
27+
]
28+
})
29+
2330
api = jsonencode({
2431
db_schema = "public,storage,graphql_public"
2532
db_extra_search_path = "public,extensions"

examples/resources/supabase_settings/resource.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ resource "supabase_settings" "production" {
55
statement_timeout = "10s"
66
})
77

8+
network = jsonencode({
9+
restrictions = [
10+
"0.0.0.0/0",
11+
"::/0"
12+
]
13+
})
14+
815
api = jsonencode({
916
db_schema = "public,storage,graphql_public"
1017
db_extra_search_path = "public,extensions"

internal/provider/settings_resource.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"encoding/json"
99
"fmt"
10+
"net"
1011
"net/http"
1112
"reflect"
1213
"strings"
@@ -135,6 +136,9 @@ func (r *SettingsResource) Create(ctx context.Context, req resource.CreateReques
135136
if !data.Database.IsNull() {
136137
resp.Diagnostics.Append(updateDatabaseConfig(ctx, &data, r.client)...)
137138
}
139+
if !data.Network.IsNull() {
140+
resp.Diagnostics.Append(updateNetworkConfig(ctx, &data, r.client)...)
141+
}
138142
if !data.Api.IsNull() {
139143
resp.Diagnostics.Append(updateApiConfig(ctx, &data, r.client)...)
140144
}
@@ -170,6 +174,9 @@ func (r *SettingsResource) Read(ctx context.Context, req resource.ReadRequest, r
170174
if !data.Database.IsNull() {
171175
resp.Diagnostics.Append(readDatabaseConfig(ctx, &data, r.client)...)
172176
}
177+
if !data.Network.IsNull() {
178+
resp.Diagnostics.Append(readNetworkConfig(ctx, &data, r.client)...)
179+
}
173180
if !data.Api.IsNull() {
174181
resp.Diagnostics.Append(readApiConfig(ctx, &data, r.client)...)
175182
}
@@ -200,6 +207,9 @@ func (r *SettingsResource) Update(ctx context.Context, req resource.UpdateReques
200207
if !data.Database.IsNull() {
201208
resp.Diagnostics.Append(updateDatabaseConfig(ctx, &data, r.client)...)
202209
}
210+
if !data.Network.IsNull() {
211+
resp.Diagnostics.Append(updateNetworkConfig(ctx, &data, r.client)...)
212+
}
203213
if !data.Api.IsNull() {
204214
resp.Diagnostics.Append(updateApiConfig(ctx, &data, r.client)...)
205215
}
@@ -233,6 +243,7 @@ func (r *SettingsResource) ImportState(ctx context.Context, req resource.ImportS
233243
// Read all configs from API when importing so it's easier to pick
234244
// individual fields to manage through TF.
235245
resp.Diagnostics.Append(readDatabaseConfig(ctx, &data, r.client)...)
246+
resp.Diagnostics.Append(readNetworkConfig(ctx, &data, r.client)...)
236247
resp.Diagnostics.Append(readApiConfig(ctx, &data, r.client)...)
237248
resp.Diagnostics.Append(readAuthConfig(ctx, &data, r.client)...)
238249

@@ -417,3 +428,82 @@ func copyConfig(source any, target map[string]interface{}) {
417428
}
418429
}
419430
}
431+
432+
type NetworkConfig struct {
433+
Restrictions []string `json:"restrictions,omitempty"`
434+
}
435+
436+
func readNetworkConfig(ctx context.Context, state *SettingsResourceModel, client *api.ClientWithResponses) diag.Diagnostics {
437+
httpResp, err := client.GetNetworkRestrictionsWithResponse(ctx, state.Id.ValueString())
438+
if err != nil {
439+
msg := fmt.Sprintf("Unable to read network settings, got error: %s", err)
440+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
441+
}
442+
// Deleted project is an orphan resource, not returning error so it can be destroyed.
443+
switch httpResp.StatusCode() {
444+
case http.StatusNotFound, http.StatusNotAcceptable:
445+
return nil
446+
}
447+
if httpResp.JSON200 == nil {
448+
msg := fmt.Sprintf("Unable to read network settings, got status %d: %s", httpResp.StatusCode(), httpResp.Body)
449+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
450+
}
451+
452+
var network NetworkConfig
453+
if v4 := httpResp.JSON200.Config.DbAllowedCidrs; v4 != nil {
454+
network.Restrictions = append(network.Restrictions, *v4...)
455+
}
456+
if v6 := httpResp.JSON200.Config.DbAllowedCidrsV6; v6 != nil {
457+
network.Restrictions = append(network.Restrictions, *v6...)
458+
}
459+
460+
if state.Network, err = parseConfig(state.Network, network); err != nil {
461+
msg := fmt.Sprintf("Unable to read network settings, got error: %s", err)
462+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
463+
}
464+
return nil
465+
}
466+
467+
func updateNetworkConfig(ctx context.Context, plan *SettingsResourceModel, client *api.ClientWithResponses) diag.Diagnostics {
468+
var network NetworkConfig
469+
if diags := plan.Network.Unmarshal(&network); diags.HasError() {
470+
return diags
471+
}
472+
473+
body := api.ApplyNetworkRestrictionsJSONRequestBody{
474+
DbAllowedCidrs: &[]string{},
475+
DbAllowedCidrsV6: &[]string{},
476+
}
477+
for _, cidr := range network.Restrictions {
478+
ip, _, err := net.ParseCIDR(cidr)
479+
if err != nil {
480+
msg := fmt.Sprintf("Invalid CIDR provided for network restrictions: %s", err)
481+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
482+
}
483+
if ip.IsPrivate() {
484+
msg := fmt.Sprintf("Private IP provided for network restrictions: %s", cidr)
485+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
486+
}
487+
if ip.To4() != nil {
488+
*body.DbAllowedCidrs = append(*body.DbAllowedCidrs, cidr)
489+
} else {
490+
*body.DbAllowedCidrsV6 = append(*body.DbAllowedCidrsV6, cidr)
491+
}
492+
}
493+
494+
httpResp, err := client.ApplyNetworkRestrictionsWithResponse(ctx, plan.ProjectRef.ValueString(), body)
495+
if err != nil {
496+
msg := fmt.Sprintf("Unable to update network settings, got error: %s", err)
497+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
498+
}
499+
if httpResp.JSON201 == nil {
500+
msg := fmt.Sprintf("Unable to update network settings, got status %d: %s", httpResp.StatusCode(), httpResp.Body)
501+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
502+
}
503+
504+
if plan.Network, err = parseConfig(plan.Network, network); err != nil {
505+
msg := fmt.Sprintf("Unable to update network settings, got error: %s", err)
506+
return diag.Diagnostics{diag.NewErrorDiagnostic("Client Error", msg)}
507+
}
508+
return nil
509+
}

internal/provider/settings_resource_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,24 @@ func TestAccSettingsResource(t *testing.T) {
2929
JSON(api.PostgresConfigResponse{
3030
StatementTimeout: Ptr("10s"),
3131
})
32+
gock.New("https://api.supabase.com").
33+
Get("/v1/projects/mayuaycdtijbctgqbycg/network-restrictions").
34+
Reply(http.StatusOK).
35+
JSON(api.NetworkRestrictionsResponse{
36+
Config: api.NetworkRestrictionsRequest{
37+
DbAllowedCidrs: Ptr([]string{"0.0.0.0/0"}),
38+
DbAllowedCidrsV6: Ptr([]string{"::/0"}),
39+
},
40+
})
41+
gock.New("https://api.supabase.com").
42+
Post("/v1/projects/mayuaycdtijbctgqbycg/network-restrictions").
43+
Reply(http.StatusCreated).
44+
JSON(api.NetworkRestrictionsResponse{
45+
Config: api.NetworkRestrictionsRequest{
46+
DbAllowedCidrs: Ptr([]string{"0.0.0.0/0"}),
47+
DbAllowedCidrsV6: Ptr([]string{"::/0"}),
48+
},
49+
})
3250
gock.New("https://api.supabase.com").
3351
Get("/v1/projects/mayuaycdtijbctgqbycg/postgrest").
3452
Reply(http.StatusOK).
@@ -70,6 +88,24 @@ func TestAccSettingsResource(t *testing.T) {
7088
JSON(api.PostgresConfigResponse{
7189
StatementTimeout: Ptr("10s"),
7290
})
91+
gock.New("https://api.supabase.com").
92+
Get("/v1/projects/mayuaycdtijbctgqbycg/network-restrictions").
93+
Reply(http.StatusOK).
94+
JSON(api.NetworkRestrictionsResponse{
95+
Config: api.NetworkRestrictionsRequest{
96+
DbAllowedCidrs: Ptr([]string{"0.0.0.0/0"}),
97+
DbAllowedCidrsV6: Ptr([]string{"::/0"}),
98+
},
99+
})
100+
gock.New("https://api.supabase.com").
101+
Get("/v1/projects/mayuaycdtijbctgqbycg/network-restrictions").
102+
Reply(http.StatusOK).
103+
JSON(api.NetworkRestrictionsResponse{
104+
Config: api.NetworkRestrictionsRequest{
105+
DbAllowedCidrs: Ptr([]string{"0.0.0.0/0"}),
106+
DbAllowedCidrsV6: Ptr([]string{"::/0"}),
107+
},
108+
})
73109
gock.New("https://api.supabase.com").
74110
Get("/v1/projects/mayuaycdtijbctgqbycg/postgrest").
75111
Reply(http.StatusOK).
@@ -117,6 +153,31 @@ func TestAccSettingsResource(t *testing.T) {
117153
JSON(api.PostgresConfigResponse{
118154
StatementTimeout: Ptr("20s"),
119155
})
156+
gock.New("https://api.supabase.com").
157+
Get("/v1/projects/mayuaycdtijbctgqbycg/network-restrictions").
158+
Reply(http.StatusOK).
159+
JSON(api.NetworkRestrictionsResponse{
160+
Config: api.NetworkRestrictionsRequest{
161+
DbAllowedCidrs: Ptr([]string{"0.0.0.0/0"}),
162+
DbAllowedCidrsV6: Ptr([]string{"::/0"}),
163+
},
164+
})
165+
gock.New("https://api.supabase.com").
166+
Post("/v1/projects/mayuaycdtijbctgqbycg/network-restrictions").
167+
Reply(http.StatusCreated).
168+
JSON(api.NetworkRestrictionsResponse{
169+
Config: api.NetworkRestrictionsRequest{
170+
DbAllowedCidrs: Ptr([]string{"0.0.0.0/0"}),
171+
},
172+
})
173+
gock.New("https://api.supabase.com").
174+
Get("/v1/projects/mayuaycdtijbctgqbycg/network-restrictions").
175+
Reply(http.StatusOK).
176+
JSON(api.NetworkRestrictionsResponse{
177+
Config: api.NetworkRestrictionsRequest{
178+
DbAllowedCidrs: Ptr([]string{"0.0.0.0/0"}),
179+
},
180+
})
120181
gock.New("https://api.supabase.com").
121182
Get("/v1/projects/mayuaycdtijbctgqbycg/postgrest").
122183
Reply(http.StatusOK).
@@ -200,6 +261,10 @@ resource "supabase_settings" "production" {
200261
statement_timeout = "20s"
201262
})
202263
264+
network = jsonencode({
265+
restrictions = ["0.0.0.0/0"]
266+
})
267+
203268
api = jsonencode({
204269
db_schema = "public,storage,graphql_public"
205270
db_extra_search_path = "public,extensions"

0 commit comments

Comments
 (0)