Repository: supabase/cli
Pull request #663 (2022-12-02 07:10:03)

feat: adds experimental support for network restrictions

coveralls: ## Pull Request Test Coverage Report for [Build 3599655409](https://coveralls.io/builds/54777798)

* **9** of **100**   **(9.0%)**  changed or added relevant lines in **3** files are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.9%**) to **56.958%**

---

|  Changes Missing Coverage | Covered Lines | Changed/Added Lines | % |
| :-----|--------------|--------|---: |
| [cmd/restrictions.go](https://coveralls.io/builds/54777798/source?filename=cmd%2Frestrictions.go#L19) | 9 | 28 | 32.14%
| [internal/restrictions/get/get.go](https://coveralls.io/builds/54777798/source?filename=internal%2Frestrictions%2Fget%2Fget.go#L12) | 0 | 27 | 0.0%
| [internal/restrictions/update/update.go](https://coveralls.io/builds/54777798/source?filename=internal%2Frestrictions%2Fupdate%2Fupdate.go#L14) | 0 | 45 | 0.0%
<!-- | **Total:** | **9** | **100** | **9.0%** | -->


|  Totals | [![Coverage Status](https://coveralls.io/builds/54777798/badge)](https://coveralls.io/builds/54777798) |
| :-- | --: |
| Change from base [Build 3599149540](https://coveralls.io/builds/54776028): |  -0.9% |
| Covered Lines: | 3250 |
| Relevant Lines: | 5706 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/api/beta.yaml b/api/beta.yaml
index 4bb2d463..3e9d9544 100644
--- a/api/beta.yaml
+++ b/api/beta.yaml
@@ -714,6 +714,66 @@ paths:
           description: Failed to remove network bans.
       tags: *ref_16
       security: *ref_17
+  /v1/projects/{ref}/network-restrictions:
+    get:
+      operationId: getNetworkRestrictions
+      summary: Gets project's network restrictions
+      parameters:
+        - name: ref
+          required: true
+          in: path
+          description: Project ref
+          schema:
+            minLength: 20
+            maxLength: 20
+            type: string
+      responses:
+        '200':
+          description: ''
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/NetworkRestrictionsResponse'
+        '403':
+          description: ''
+        '500':
+          description: Failed to retrieve project's network restrictions
+      tags: &ref_18
+        - network restrictions (beta)
+      security: &ref_19
+        - bearer: []
+  /v1/projects/{ref}/network-restrictions/apply:
+    post:
+      operationId: applyNetworkRestrictions
+      summary: Updates project's network restrictions
+      parameters:
+        - name: ref
+          required: true
+          in: path
+          description: Project ref
+          schema:
+            minLength: 20
+            maxLength: 20
+            type: string
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/NetworkRestrictionsRequest'
+      responses:
+        '201':
+          description: ''
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/NetworkRestrictionsResponse'
+        '403':
+          description: ''
+        '500':
+          description: Failed to update project network restrictions
+      tags: *ref_18
+      security: *ref_19
 info:
   title: Supabase API (v1)
   description: ''
@@ -844,7 +904,7 @@ components:
         name:
           type: string
         status:
-          enum: &ref_18
+          enum: &ref_20
             - ACTIVE
             - REMOVED
             - THROTTLED
@@ -877,7 +937,7 @@ components:
         name:
           type: string
         status:
-          enum: *ref_18
+          enum: *ref_20
           type: string
         version:
           type: number
@@ -987,3 +1047,31 @@ components:
             type: string
       required:
         - ipv4_addresses
+    NetworkRestrictionsRequest:
+      type: object
+      properties:
+        dbAllowedCidrs:
+          type: array
+          items:
+            type: string
+      required:
+        - dbAllowedCidrs
+    NetworkRestrictionsResponse:
+      type: object
+      properties:
+        entitlement:
+          enum:
+            - disallowed
+            - allowed
+          type: string
+        config:
+          $ref: '#/components/schemas/NetworkRestrictionsRequest'
+        status:
+          enum:
+            - stored
+            - applied
+          type: string
+      required:
+        - entitlement
+        - config
+        - status
diff --git a/cmd/restrictions.go b/cmd/restrictions.go
new file mode 100644
index 00000000..878a44f1
--- /dev/null
+++ b/cmd/restrictions.go
@@ -0,0 +1,65 @@
+package cmd
+
+import (
+	"errors"
+	"os"
+	"os/signal"
+
+	"github.com/spf13/afero"
+	"github.com/spf13/cobra"
+	"github.com/supabase/cli/internal/restrictions/get"
+	"github.com/supabase/cli/internal/restrictions/update"
+)
+
+var (
+	restrictionsCmd = &cobra.Command{
+		GroupID: groupManagementAPI,
+		Use:     "network-restrictions",
+		Short:   "Manage network restrictions",
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			if !experimental {
+				return errors.New("must set the --experimental flag to run this command")
+			}
+			return nil
+		},
+	}
+
+	dbCidrsToAllow   []string
+	bypassCidrChecks bool
+
+	restrictionsUpdateCmd = &cobra.Command{
+		Use:   "update",
+		Short: "Update network restrictions",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			fsys := afero.NewOsFs()
+			if err := PromptLogin(fsys); err != nil {
+				return err
+			}
+			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
+			return update.Run(ctx, projectRef, dbCidrsToAllow, bypassCidrChecks, fsys)
+		},
+	}
+
+	restrictionsGetCmd = &cobra.Command{
+		Use:   "get",
+		Short: "Get the current network restrictions",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			fsys := afero.NewOsFs()
+			if err := PromptLogin(fsys); err != nil {
+				return err
+			}
+			ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
+			return get.Run(ctx, projectRef, fsys)
+		},
+	}
+)
+
+func init() {
+	restrictionsCmd.PersistentFlags().StringVar(&projectRef, "project-ref", "", "Project ref of the Supabase project.")
+	restrictionsUpdateCmd.Flags().StringSliceVar(&dbCidrsToAllow, "db-allow-cidr", []string{}, "CIDR to allow DB connections from.")
+	restrictionsUpdateCmd.Flags().BoolVar(&bypassCidrChecks, "bypass-cidr-checks", false, "Bypass some of the CIDR validation checks.")
+	restrictionsCmd.AddCommand(restrictionsGetCmd)
+	restrictionsCmd.AddCommand(restrictionsUpdateCmd)
+
+	rootCmd.AddCommand(restrictionsCmd)
+}
diff --git a/internal/restrictions/get/get.go b/internal/restrictions/get/get.go
new file mode 100644
index 00000000..279070aa
--- /dev/null
+++ b/internal/restrictions/get/get.go
@@ -0,0 +1,44 @@
+package get
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"github.com/spf13/afero"
+	"github.com/supabase/cli/internal/utils"
+)
+
+func Run(ctx context.Context, projectRefArg string, fsys afero.Fs) error {
+	// 1. Sanity checks.
+	projectRef := projectRefArg
+	{
+		if len(projectRefArg) == 0 {
+			ref, err := utils.LoadProjectRef(fsys)
+			if err != nil {
+				return err
+			}
+			projectRef = ref
+		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
+			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
+		}
+	}
+
+	// 2. get network restrictions
+	{
+		resp, err := utils.GetSupabase().GetNetworkRestrictionsWithResponse(ctx, projectRef)
+		if err != nil {
+			return fmt.Errorf("failed to retrieve network restrictions: %w", err)
+		}
+		if resp.JSON200 == nil {
+			return errors.New("failed to retrieve network restrictions; received: " + string(resp.Body))
+		}
+
+		if err != nil {
+			return err
+		}
+		fmt.Printf("DB Allowed CIDRs: %+v\n", resp.JSON200.Config.DbAllowedCidrs)
+		fmt.Printf("Restrictions applied successfully: %+v\n", resp.JSON200.Status == "applied")
+		return nil
+	}
+}
diff --git a/internal/restrictions/update/update.go b/internal/restrictions/update/update.go
new file mode 100644
index 00000000..2811b074
--- /dev/null
+++ b/internal/restrictions/update/update.go
@@ -0,0 +1,66 @@
+package update
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net"
+
+	"github.com/spf13/afero"
+	"github.com/supabase/cli/internal/utils"
+	"github.com/supabase/cli/pkg/api"
+)
+
+func validateCidrs(cidrs []string, bypassChecks bool) error {
+	for _, cidr := range cidrs {
+		ip, _, err := net.ParseCIDR(cidr)
+		if err != nil {
+			return fmt.Errorf("failed to parse IP: %s", cidr)
+		}
+		if ip.IsPrivate() && !bypassChecks {
+			return fmt.Errorf("private IP provided: %s", cidr)
+		}
+		if ip.To4() == nil {
+			return fmt.Errorf("only IPv4 supported at the moment: %s", cidr)
+		}
+	}
+	return nil
+}
+
+func Run(ctx context.Context, projectRefArg string, dbCidrsToAllow []string, bypassCidrChecks bool, fsys afero.Fs) error {
+	// 1. Sanity checks.
+	projectRef := projectRefArg
+
+	// 1. sanity checks
+	{
+		if len(projectRefArg) == 0 {
+			ref, err := utils.LoadProjectRef(fsys)
+			if err != nil {
+				return err
+			}
+			projectRef = ref
+		} else if !utils.ProjectRefPattern.MatchString(projectRef) {
+			return errors.New("Invalid project ref format. Must be like `abcdefghijklmnopqrst`.")
+		}
+		err := validateCidrs(dbCidrsToAllow, bypassCidrChecks)
+		if err != nil {
+			return err
+		}
+	}
+
+	// 2. update restrictions
+	{
+		resp, err := utils.GetSupabase().ApplyNetworkRestrictionsWithResponse(ctx, projectRef, api.ApplyNetworkRestrictionsJSONRequestBody{
+			DbAllowedCidrs: dbCidrsToAllow,
+		})
+		if err != nil {
+			return err
+		}
+		if resp.JSON201 == nil {
+			return errors.New("failed to update network restrictions: " + string(resp.Body))
+		}
+		fmt.Printf("DB Allowed CIDRs: %+v\n", resp.JSON201.Config.DbAllowedCidrs)
+		fmt.Printf("Restrictions applied successfully: %+v\n", resp.JSON201.Status == "applied")
+		return nil
+	}
+}
diff --git a/pkg/api/client.gen.go b/pkg/api/client.gen.go
index 191fc126..1a7c6bb8 100644
--- a/pkg/api/client.gen.go
+++ b/pkg/api/client.gen.go
@@ -152,6 +152,14 @@ type ClientInterface interface {
 	// GetNetworkBans request
 	GetNetworkBans(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error)
 
+	// GetNetworkRestrictions request
+	GetNetworkRestrictions(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error)
+
+	// ApplyNetworkRestrictions request with any body
+	ApplyNetworkRestrictionsWithBody(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
+
+	ApplyNetworkRestrictions(ctx context.Context, ref string, body ApplyNetworkRestrictionsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
+
 	// GetConfig request
 	GetConfig(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error)
 
@@ -453,6 +461,42 @@ func (c *Client) GetNetworkBans(ctx context.Context, ref string, reqEditors ...R
 	return c.Client.Do(req)
 }
 
+func (c *Client) GetNetworkRestrictions(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) {
+	req, err := NewGetNetworkRestrictionsRequest(c.Server, ref)
+	if err != nil {
+		return nil, err
+	}
+	req = req.WithContext(ctx)
+	if err := c.applyEditors(ctx, req, reqEditors); err != nil {
+		return nil, err
+	}
+	return c.Client.Do(req)
+}
+
+func (c *Client) ApplyNetworkRestrictionsWithBody(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
+	req, err := NewApplyNetworkRestrictionsRequestWithBody(c.Server, ref, contentType, body)
+	if err != nil {
+		return nil, err
+	}
+	req = req.WithContext(ctx)
+	if err := c.applyEditors(ctx, req, reqEditors); err != nil {
+		return nil, err
+	}
+	return c.Client.Do(req)
+}
+
+func (c *Client) ApplyNetworkRestrictions(ctx context.Context, ref string, body ApplyNetworkRestrictionsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
+	req, err := NewApplyNetworkRestrictionsRequest(c.Server, ref, body)
+	if err != nil {
+		return nil, err
+	}
+	req = req.WithContext(ctx)
+	if err := c.applyEditors(ctx, req, reqEditors); err != nil {
+		return nil, err
+	}
+	return c.Client.Do(req)
+}
+
 func (c *Client) GetConfig(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*http.Response, error) {
 	req, err := NewGetConfigRequest(c.Server, ref)
 	if err != nil {
@@ -1353,6 +1397,87 @@ func NewGetNetworkBansRequest(server string, ref string) (*http.Request, error)
 	return req, nil
 }
 
+// NewGetNetworkRestrictionsRequest generates requests for GetNetworkRestrictions
+func NewGetNetworkRestrictionsRequest(server string, ref string) (*http.Request, error) {
+	var err error
+
+	var pathParam0 string
+
+	pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref)
+	if err != nil {
+		return nil, err
+	}
+
+	serverURL, err := url.Parse(server)
+	if err != nil {
+		return nil, err
+	}
+
+	operationPath := fmt.Sprintf("/v1/projects/%s/network-restrictions", pathParam0)
+	if operationPath[0] == '/' {
+		operationPath = "." + operationPath
+	}
+
+	queryURL, err := serverURL.Parse(operationPath)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("GET", queryURL.String(), nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return req, nil
+}
+
+// NewApplyNetworkRestrictionsRequest calls the generic ApplyNetworkRestrictions builder with application/json body
+func NewApplyNetworkRestrictionsRequest(server string, ref string, body ApplyNetworkRestrictionsJSONRequestBody) (*http.Request, error) {
+	var bodyReader io.Reader
+	buf, err := json.Marshal(body)
+	if err != nil {
+		return nil, err
+	}
+	bodyReader = bytes.NewReader(buf)
+	return NewApplyNetworkRestrictionsRequestWithBody(server, ref, "application/json", bodyReader)
+}
+
+// NewApplyNetworkRestrictionsRequestWithBody generates requests for ApplyNetworkRestrictions with any type of body
+func NewApplyNetworkRestrictionsRequestWithBody(server string, ref string, contentType string, body io.Reader) (*http.Request, error) {
+	var err error
+
+	var pathParam0 string
+
+	pathParam0, err = runtime.StyleParamWithLocation("simple", false, "ref", runtime.ParamLocationPath, ref)
+	if err != nil {
+		return nil, err
+	}
+
+	serverURL, err := url.Parse(server)
+	if err != nil {
+		return nil, err
+	}
+
+	operationPath := fmt.Sprintf("/v1/projects/%s/network-restrictions/apply", pathParam0)
+	if operationPath[0] == '/' {
+		operationPath = "." + operationPath
+	}
+
+	queryURL, err := serverURL.Parse(operationPath)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("POST", queryURL.String(), body)
+	if err != nil {
+		return nil, err
+	}
+
+	req.Header.Add("Content-Type", contentType)
+
+	return req, nil
+}
+
 // NewGetConfigRequest generates requests for GetConfig
 func NewGetConfigRequest(server string, ref string) (*http.Request, error) {
 	var err error
@@ -1722,6 +1847,14 @@ type ClientWithResponsesInterface interface {
 	// GetNetworkBans request
 	GetNetworkBansWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*GetNetworkBansResponse, error)
 
+	// GetNetworkRestrictions request
+	GetNetworkRestrictionsWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*GetNetworkRestrictionsResponse, error)
+
+	// ApplyNetworkRestrictions request with any body
+	ApplyNetworkRestrictionsWithBodyWithResponse(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ApplyNetworkRestrictionsResponse, error)
+
+	ApplyNetworkRestrictionsWithResponse(ctx context.Context, ref string, body ApplyNetworkRestrictionsJSONRequestBody, reqEditors ...RequestEditorFn) (*ApplyNetworkRestrictionsResponse, error)
+
 	// GetConfig request
 	GetConfigWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*GetConfigResponse, error)
 
@@ -2117,6 +2250,50 @@ func (r GetNetworkBansResponse) StatusCode() int {
 	return 0
 }
 
+type GetNetworkRestrictionsResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+	JSON200      *NetworkRestrictionsResponse
+}
+
+// Status returns HTTPResponse.Status
+func (r GetNetworkRestrictionsResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r GetNetworkRestrictionsResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
+type ApplyNetworkRestrictionsResponse struct {
+	Body         []byte
+	HTTPResponse *http.Response
+	JSON201      *NetworkRestrictionsResponse
+}
+
+// Status returns HTTPResponse.Status
+func (r ApplyNetworkRestrictionsResponse) Status() string {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.Status
+	}
+	return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r ApplyNetworkRestrictionsResponse) StatusCode() int {
+	if r.HTTPResponse != nil {
+		return r.HTTPResponse.StatusCode
+	}
+	return 0
+}
+
 type GetConfigResponse struct {
 	Body         []byte
 	HTTPResponse *http.Response
@@ -2449,6 +2626,32 @@ func (c *ClientWithResponses) GetNetworkBansWithResponse(ctx context.Context, re
 	return ParseGetNetworkBansResponse(rsp)
 }
 
+// GetNetworkRestrictionsWithResponse request returning *GetNetworkRestrictionsResponse
+func (c *ClientWithResponses) GetNetworkRestrictionsWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*GetNetworkRestrictionsResponse, error) {
+	rsp, err := c.GetNetworkRestrictions(ctx, ref, reqEditors...)
+	if err != nil {
+		return nil, err
+	}
+	return ParseGetNetworkRestrictionsResponse(rsp)
+}
+
+// ApplyNetworkRestrictionsWithBodyWithResponse request with arbitrary body returning *ApplyNetworkRestrictionsResponse
+func (c *ClientWithResponses) ApplyNetworkRestrictionsWithBodyWithResponse(ctx context.Context, ref string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ApplyNetworkRestrictionsResponse, error) {
+	rsp, err := c.ApplyNetworkRestrictionsWithBody(ctx, ref, contentType, body, reqEditors...)
+	if err != nil {
+		return nil, err
+	}
+	return ParseApplyNetworkRestrictionsResponse(rsp)
+}
+
+func (c *ClientWithResponses) ApplyNetworkRestrictionsWithResponse(ctx context.Context, ref string, body ApplyNetworkRestrictionsJSONRequestBody, reqEditors ...RequestEditorFn) (*ApplyNetworkRestrictionsResponse, error) {
+	rsp, err := c.ApplyNetworkRestrictions(ctx, ref, body, reqEditors...)
+	if err != nil {
+		return nil, err
+	}
+	return ParseApplyNetworkRestrictionsResponse(rsp)
+}
+
 // GetConfigWithResponse request returning *GetConfigResponse
 func (c *ClientWithResponses) GetConfigWithResponse(ctx context.Context, ref string, reqEditors ...RequestEditorFn) (*GetConfigResponse, error) {
 	rsp, err := c.GetConfig(ctx, ref, reqEditors...)
@@ -2929,6 +3132,58 @@ func ParseGetNetworkBansResponse(rsp *http.Response) (*GetNetworkBansResponse, e
 	return response, nil
 }
 
+// ParseGetNetworkRestrictionsResponse parses an HTTP response from a GetNetworkRestrictionsWithResponse call
+func ParseGetNetworkRestrictionsResponse(rsp *http.Response) (*GetNetworkRestrictionsResponse, error) {
+	bodyBytes, err := io.ReadAll(rsp.Body)
+	defer func() { _ = rsp.Body.Close() }()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &GetNetworkRestrictionsResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+		var dest NetworkRestrictionsResponse
+		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+			return nil, err
+		}
+		response.JSON200 = &dest
+
+	}
+
+	return response, nil
+}
+
+// ParseApplyNetworkRestrictionsResponse parses an HTTP response from a ApplyNetworkRestrictionsWithResponse call
+func ParseApplyNetworkRestrictionsResponse(rsp *http.Response) (*ApplyNetworkRestrictionsResponse, error) {
+	bodyBytes, err := io.ReadAll(rsp.Body)
+	defer func() { _ = rsp.Body.Close() }()
+	if err != nil {
+		return nil, err
+	}
+
+	response := &ApplyNetworkRestrictionsResponse{
+		Body:         bodyBytes,
+		HTTPResponse: rsp,
+	}
+
+	switch {
+	case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201:
+		var dest NetworkRestrictionsResponse
+		if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+			return nil, err
+		}
+		response.JSON201 = &dest
+
+	}
+
+	return response, nil
+}
+
 // ParseGetConfigResponse parses an HTTP response from a GetConfigWithResponse call
 func ParseGetConfigResponse(rsp *http.Response) (*GetConfigResponse, error) {
 	bodyBytes, err := io.ReadAll(rsp.Body)
diff --git a/pkg/api/types.gen.go b/pkg/api/types.gen.go
index d95702e8..16b2d07a 100644
--- a/pkg/api/types.gen.go
+++ b/pkg/api/types.gen.go
@@ -46,6 +46,18 @@ const (
 	FunctionSlugResponseStatusTHROTTLED FunctionSlugResponseStatus = "THROTTLED"
 )
 
+// Defines values for NetworkRestrictionsResponseEntitlement.
+const (
+	Allowed    NetworkRestrictionsResponseEntitlement = "allowed"
+	Disallowed NetworkRestrictionsResponseEntitlement = "disallowed"
+)
+
+// Defines values for NetworkRestrictionsResponseStatus.
+const (
+	Applied NetworkRestrictionsResponseStatus = "applied"
+	Stored  NetworkRestrictionsResponseStatus = "stored"
+)
+
 // Defines values for UpdateCustomHostnameResponseStatus.
 const (
 	N1NotStarted           UpdateCustomHostnameResponseStatus = "1_not_started"
@@ -134,6 +146,24 @@ type NetworkBanResponse struct {
 	BannedIpv4Addresses []string `json:"banned_ipv4_addresses"`
 }
 
+// NetworkRestrictionsRequest defines model for NetworkRestrictionsRequest.
+type NetworkRestrictionsRequest struct {
+	DbAllowedCidrs []string `json:"dbAllowedCidrs"`
+}
+
+// NetworkRestrictionsResponse defines model for NetworkRestrictionsResponse.
+type NetworkRestrictionsResponse struct {
+	Config      NetworkRestrictionsRequest             `json:"config"`
+	Entitlement NetworkRestrictionsResponseEntitlement `json:"entitlement"`
+	Status      NetworkRestrictionsResponseStatus      `json:"status"`
+}
+
+// NetworkRestrictionsResponseEntitlement defines model for NetworkRestrictionsResponse.Entitlement.
+type NetworkRestrictionsResponseEntitlement string
+
+// NetworkRestrictionsResponseStatus defines model for NetworkRestrictionsResponse.Status.
+type NetworkRestrictionsResponseStatus string
+
 // OrganizationResponse defines model for OrganizationResponse.
 type OrganizationResponse struct {
 	Id   string `json:"id"`
@@ -238,6 +268,9 @@ type UpdateFunctionJSONRequestBody = UpdateFunctionBody
 // RemoveNetworkBanJSONRequestBody defines body for RemoveNetworkBan for application/json ContentType.
 type RemoveNetworkBanJSONRequestBody = RemoveNetworkBanRequest
 
+// ApplyNetworkRestrictionsJSONRequestBody defines body for ApplyNetworkRestrictions for application/json ContentType.
+type ApplyNetworkRestrictionsJSONRequestBody = NetworkRestrictionsRequest
+
 // UpdateConfigJSONRequestBody defines body for UpdateConfig for application/json ContentType.
 type UpdateConfigJSONRequestBody = UpdatePgsodiumConfigBody
 


Test patch:
diff --git a/internal/restrictions/update/update_test.go b/internal/restrictions/update/update_test.go
new file mode 100644
index 00000000..8b865826
--- /dev/null
+++ b/internal/restrictions/update/update_test.go
@@ -0,0 +1,28 @@
+package update
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestPrivateSubnet(t *testing.T) {
+	err := validateCidrs([]string{"12.3.4.5/32", "10.0.0.0/8", "1.2.3.1/24"}, false)
+	assert.ErrorContains(t, err, "private IP provided: 10.0.0.0/8")
+	err = validateCidrs([]string{"10.0.0.0/8"}, true)
+	assert.Nil(t, err, "should bypass private subnet checks")
+}
+
+func TestIpv4(t *testing.T) {
+	err := validateCidrs([]string{"12.3.4.5/32", "2001:db8:abcd:0012::0/64", "1.2.3.1/24"}, false)
+	assert.ErrorContains(t, err, "only IPv4 supported at the moment: 2001:db8:abcd:0012::0/64")
+	err = validateCidrs([]string{"12.3.4.5/32", "2001:db8:abcd:0012::0/64", "1.2.3.1/24"}, true)
+	assert.ErrorContains(t, err, "only IPv4 supported at the moment: 2001:db8:abcd:0012::0/64")
+}
+
+func TestInvalidSubnets(t *testing.T) {
+	err := validateCidrs([]string{"12.3.4.5", "10.0.0.0/8", "1.2.3.1/24"}, false)
+	assert.ErrorContains(t, err, "failed to parse IP: 12.3.4.5")
+	err = validateCidrs([]string{"100/36"}, true)
+	assert.ErrorContains(t, err, "failed to parse IP: 100/36")
+}