[management] Parse resource addr before db write (#3061)

This commit is contained in:
Pascal Fischer
2024-12-17 12:21:28 +01:00
committed by GitHub
parent 712341e73d
commit 228672aed2
6 changed files with 73 additions and 49 deletions

View File

@ -1272,18 +1272,17 @@ components:
description: Network resource description description: Network resource description
type: string type: string
example: A remote resource inside network 1 example: A remote resource inside network 1
address:
description: Network resource address (either a direct host like 1.1.1.1 or 1.1.1.1/32, or a subnet like 192.168.178.0/24, or a domain like example.com)
type: string
example: "1.1.1.1"
required: required:
- name - name
- address
NetworkResourceRequest: NetworkResourceRequest:
allOf: allOf:
- $ref: '#/components/schemas/NetworkResourceMinimum' - $ref: '#/components/schemas/NetworkResourceMinimum'
- type: object - type: object
properties: properties:
address:
description: Network resource address (either a direct host like 1.1.1.1 or 1.1.1.1/32, or a subnet like 192.168.178.0/24, or a domain like example.com)
type: string
example: "1.1.1.1"
groups: groups:
description: Group IDs containing the resource description: Group IDs containing the resource
type: array type: array
@ -1292,6 +1291,7 @@ components:
example: "chacdk86lnnboviihd70" example: "chacdk86lnnboviihd70"
required: required:
- groups - groups
- address
NetworkResource: NetworkResource:
allOf: allOf:
- type: object - type: object
@ -1307,10 +1307,20 @@ components:
type: array type: array
items: items:
$ref: '#/components/schemas/GroupMinimum' $ref: '#/components/schemas/GroupMinimum'
domain:
description: Domain name of the resource
type: string
example: example.com
prefix:
description: Prefix of the resource
type: string
example:
required: required:
- id - id
- type - type
- groups - groups
- domain
- prefix
- $ref: '#/components/schemas/NetworkResourceMinimum' - $ref: '#/components/schemas/NetworkResourceMinimum'
NetworkResourceType: NetworkResourceType:
description: Network resource type based of the address description: Network resource type based of the address

View File

@ -551,12 +551,12 @@ type NetworkRequest struct {
// NetworkResource defines model for NetworkResource. // NetworkResource defines model for NetworkResource.
type NetworkResource struct { type NetworkResource struct {
// Address Network resource address (either a direct host like 1.1.1.1 or 1.1.1.1/32, or a subnet like 192.168.178.0/24, or a domain like example.com)
Address string `json:"address"`
// Description Network resource description // Description Network resource description
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`
// Domain Domain name of the resource
Domain string `json:"domain"`
// Groups Groups that the resource belongs to // Groups Groups that the resource belongs to
Groups []GroupMinimum `json:"groups"` Groups []GroupMinimum `json:"groups"`
@ -566,15 +566,15 @@ type NetworkResource struct {
// Name Network resource name // Name Network resource name
Name string `json:"name"` Name string `json:"name"`
// Prefix Prefix of the resource
Prefix string `json:"prefix"`
// Type Network resource type based of the address // Type Network resource type based of the address
Type NetworkResourceType `json:"type"` Type NetworkResourceType `json:"type"`
} }
// NetworkResourceMinimum defines model for NetworkResourceMinimum. // NetworkResourceMinimum defines model for NetworkResourceMinimum.
type NetworkResourceMinimum struct { type NetworkResourceMinimum struct {
// Address Network resource address (either a direct host like 1.1.1.1 or 1.1.1.1/32, or a subnet like 192.168.178.0/24, or a domain like example.com)
Address string `json:"address"`
// Description Network resource description // Description Network resource description
Description *string `json:"description,omitempty"` Description *string `json:"description,omitempty"`

View File

@ -142,13 +142,14 @@ func (m *managerImpl) UpdateResource(ctx context.Context, userID string, resourc
return nil, status.NewPermissionDeniedError() return nil, status.NewPermissionDeniedError()
} }
resourceType, addr, err := types.GetResourceType(resource.Address) resourceType, domain, prefix, err := types.GetResourceType(resource.Address)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get resource type: %w", err) return nil, fmt.Errorf("failed to get resource type: %w", err)
} }
resource.Type = resourceType resource.Type = resourceType
resource.Address = addr resource.Domain = domain
resource.Prefix = prefix
_, err = m.store.GetNetworkResourceByID(ctx, store.LockingStrengthShare, resource.AccountID, resource.ID) _, err = m.store.GetNetworkResourceByID(ctx, store.LockingStrengthShare, resource.AccountID, resource.ID)
if err != nil { if err != nil {

View File

@ -3,9 +3,8 @@ package types
import ( import (
"errors" "errors"
"fmt" "fmt"
"net" "net/netip"
"regexp" "regexp"
"strings"
"github.com/rs/xid" "github.com/rs/xid"
@ -31,11 +30,13 @@ type NetworkResource struct {
Name string Name string
Description string Description string
Type NetworkResourceType Type NetworkResourceType
Address string Address string `gorm:"-"`
Domain string
Prefix netip.Prefix `gorm:"serializer:json"`
} }
func NewNetworkResource(accountID, networkID, name, description, address string) (*NetworkResource, error) { func NewNetworkResource(accountID, networkID, name, description, address string) (*NetworkResource, error) {
resourceType, address, err := GetResourceType(address) resourceType, domain, prefix, err := GetResourceType(address)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid address: %w", err) return nil, fmt.Errorf("invalid address: %w", err)
} }
@ -48,6 +49,8 @@ func NewNetworkResource(accountID, networkID, name, description, address string)
Description: description, Description: description,
Type: resourceType, Type: resourceType,
Address: address, Address: address,
Domain: domain,
Prefix: prefix,
}, nil }, nil
} }
@ -57,7 +60,8 @@ func (n *NetworkResource) ToAPIResponse(groups []api.GroupMinimum) *api.NetworkR
Name: n.Name, Name: n.Name,
Description: &n.Description, Description: &n.Description,
Type: api.NetworkResourceType(n.Type.String()), Type: api.NetworkResourceType(n.Type.String()),
Address: n.Address, Domain: n.Domain,
Prefix: n.Prefix.String(),
Groups: groups, Groups: groups,
} }
} }
@ -84,26 +88,22 @@ func (n *NetworkResource) Copy() *NetworkResource {
} }
// GetResourceType returns the type of the resource based on the address // GetResourceType returns the type of the resource based on the address
func GetResourceType(address string) (NetworkResourceType, string, error) { func GetResourceType(address string) (NetworkResourceType, string, netip.Prefix, error) {
if ip, cidr, err := net.ParseCIDR(address); err == nil { if prefix, err := netip.ParsePrefix(address); err == nil {
ones, _ := cidr.Mask.Size() if prefix.Bits() == 32 || prefix.Bits() == 128 {
if strings.HasSuffix(address, "/32") { return host, "", prefix, nil
return host, address, nil
} }
if ip != nil && ones == 32 { return subnet, "", prefix, nil
return host, address + "/32", nil
}
return subnet, address, nil
} }
if net.ParseIP(address) != nil { if ip, err := netip.ParseAddr(address); err == nil {
return host, address + "/32", nil return host, "", netip.PrefixFrom(ip, ip.BitLen()), nil
} }
domainRegex := regexp.MustCompile(`^(\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$`) domainRegex := regexp.MustCompile(`^(\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$`)
if domainRegex.MatchString(address) { if domainRegex.MatchString(address) {
return domain, address, nil return domain, address, netip.Prefix{}, nil
} }
return "", "", errors.New("not a host, subnet, or domain") return "", "", netip.Prefix{}, errors.New("not a valid host, subnet, or domain")
} }

View File

@ -1,35 +1,38 @@
package types package types
import ( import (
"net/netip"
"testing" "testing"
) )
func TestGetResourceType(t *testing.T) { func TestGetResourceType(t *testing.T) {
tests := []struct { tests := []struct {
input string input string
expectedType NetworkResourceType expectedType NetworkResourceType
expectedErr bool expectedErr bool
expectedAddr string expectedDomain string
expectedPrefix netip.Prefix
}{ }{
// Valid host IPs // Valid host IPs
{"1.1.1.1", host, false, "1.1.1.1/32"}, {"1.1.1.1", host, false, "", netip.MustParsePrefix("1.1.1.1/32")},
{"1.1.1.1/32", host, false, "1.1.1.1/32"}, {"1.1.1.1/32", host, false, "", netip.MustParsePrefix("1.1.1.1/32")},
// Valid subnets // Valid subnets
{"192.168.1.0/24", subnet, false, "192.168.1.0/24"}, {"192.168.1.0/24", subnet, false, "", netip.MustParsePrefix("192.168.1.0/24")},
{"10.0.0.0/16", subnet, false, "10.0.0.0/16"}, {"10.0.0.0/16", subnet, false, "", netip.MustParsePrefix("10.0.0.0/16")},
// Valid domains // Valid domains
{"example.com", domain, false, "example.com"}, {"example.com", domain, false, "example.com", netip.Prefix{}},
{"*.example.com", domain, false, "*.example.com"}, {"*.example.com", domain, false, "*.example.com", netip.Prefix{}},
{"sub.example.com", domain, false, "sub.example.com"}, {"sub.example.com", domain, false, "sub.example.com", netip.Prefix{}},
// Invalid inputs // Invalid inputs
{"invalid", "", true, ""}, {"invalid", "", true, "", netip.Prefix{}},
{"1.1.1.1/abc", "", true, ""}, {"1.1.1.1/abc", "", true, "", netip.Prefix{}},
{"1234", "", true, ""}, {"1234", "", true, "", netip.Prefix{}},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) { t.Run(tt.input, func(t *testing.T) {
result, addr, err := GetResourceType(tt.input) result, domain, prefix, err := GetResourceType(tt.input)
if result != tt.expectedType { if result != tt.expectedType {
t.Errorf("Expected type %v, got %v", tt.expectedType, result) t.Errorf("Expected type %v, got %v", tt.expectedType, result)
} }
@ -38,8 +41,12 @@ func TestGetResourceType(t *testing.T) {
t.Errorf("Expected error, got nil") t.Errorf("Expected error, got nil")
} }
if addr != tt.expectedAddr { if prefix != tt.expectedPrefix {
t.Errorf("Expected address %v, got %v", tt.expectedAddr, addr) t.Errorf("Expected address %v, got %v", tt.expectedPrefix, prefix)
}
if domain != tt.expectedDomain {
t.Errorf("Expected domain %v, got %v", tt.expectedDomain, domain)
} }
}) })
} }

View File

@ -2502,7 +2502,13 @@ func TestSqlStore_SaveNetworkResource(t *testing.T) {
savedNetResource, err := store.GetNetworkResourceByID(context.Background(), LockingStrengthShare, accountID, netResource.ID) savedNetResource, err := store.GetNetworkResourceByID(context.Background(), LockingStrengthShare, accountID, netResource.ID)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, netResource, savedNetResource) require.Equal(t, netResource.ID, savedNetResource.ID)
require.Equal(t, netResource.Name, savedNetResource.Name)
require.Equal(t, netResource.NetworkID, savedNetResource.NetworkID)
require.Equal(t, netResource.Type, resourceTypes.NetworkResourceType("domain"))
require.Equal(t, netResource.Domain, "example.com")
require.Equal(t, netResource.AccountID, savedNetResource.AccountID)
require.Equal(t, netResource.Prefix, netip.Prefix{})
} }
func TestSqlStore_DeleteNetworkResource(t *testing.T) { func TestSqlStore_DeleteNetworkResource(t *testing.T) {