diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index af5bf9c8c..cf22cf954 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -1272,18 +1272,17 @@ components: description: Network resource description type: string 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: - name - - address NetworkResourceRequest: allOf: - $ref: '#/components/schemas/NetworkResourceMinimum' - type: object 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: description: Group IDs containing the resource type: array @@ -1292,6 +1291,7 @@ components: example: "chacdk86lnnboviihd70" required: - groups + - address NetworkResource: allOf: - type: object @@ -1307,10 +1307,20 @@ components: type: array items: $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: - id - type - groups + - domain + - prefix - $ref: '#/components/schemas/NetworkResourceMinimum' NetworkResourceType: description: Network resource type based of the address diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index 80aff514a..e0bd78c42 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -551,12 +551,12 @@ type NetworkRequest struct { // NetworkResource defines model for NetworkResource. 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 *string `json:"description,omitempty"` + // Domain Domain name of the resource + Domain string `json:"domain"` + // Groups Groups that the resource belongs to Groups []GroupMinimum `json:"groups"` @@ -566,15 +566,15 @@ type NetworkResource struct { // Name Network resource name Name string `json:"name"` + // Prefix Prefix of the resource + Prefix string `json:"prefix"` + // Type Network resource type based of the address Type NetworkResourceType `json:"type"` } // NetworkResourceMinimum defines model for NetworkResourceMinimum. 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 *string `json:"description,omitempty"` diff --git a/management/server/networks/resources/manager.go b/management/server/networks/resources/manager.go index e1f15c2c3..044c2dbb7 100644 --- a/management/server/networks/resources/manager.go +++ b/management/server/networks/resources/manager.go @@ -142,13 +142,14 @@ func (m *managerImpl) UpdateResource(ctx context.Context, userID string, resourc return nil, status.NewPermissionDeniedError() } - resourceType, addr, err := types.GetResourceType(resource.Address) + resourceType, domain, prefix, err := types.GetResourceType(resource.Address) if err != nil { return nil, fmt.Errorf("failed to get resource type: %w", err) } resource.Type = resourceType - resource.Address = addr + resource.Domain = domain + resource.Prefix = prefix _, err = m.store.GetNetworkResourceByID(ctx, store.LockingStrengthShare, resource.AccountID, resource.ID) if err != nil { diff --git a/management/server/networks/resources/types/resource.go b/management/server/networks/resources/types/resource.go index 32aef433b..7d5eba869 100644 --- a/management/server/networks/resources/types/resource.go +++ b/management/server/networks/resources/types/resource.go @@ -3,9 +3,8 @@ package types import ( "errors" "fmt" - "net" + "net/netip" "regexp" - "strings" "github.com/rs/xid" @@ -31,11 +30,13 @@ type NetworkResource struct { Name string Description string 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) { - resourceType, address, err := GetResourceType(address) + resourceType, domain, prefix, err := GetResourceType(address) if err != nil { return nil, fmt.Errorf("invalid address: %w", err) } @@ -48,6 +49,8 @@ func NewNetworkResource(accountID, networkID, name, description, address string) Description: description, Type: resourceType, Address: address, + Domain: domain, + Prefix: prefix, }, nil } @@ -57,7 +60,8 @@ func (n *NetworkResource) ToAPIResponse(groups []api.GroupMinimum) *api.NetworkR Name: n.Name, Description: &n.Description, Type: api.NetworkResourceType(n.Type.String()), - Address: n.Address, + Domain: n.Domain, + Prefix: n.Prefix.String(), Groups: groups, } } @@ -84,26 +88,22 @@ func (n *NetworkResource) Copy() *NetworkResource { } // GetResourceType returns the type of the resource based on the address -func GetResourceType(address string) (NetworkResourceType, string, error) { - if ip, cidr, err := net.ParseCIDR(address); err == nil { - ones, _ := cidr.Mask.Size() - if strings.HasSuffix(address, "/32") { - return host, address, nil +func GetResourceType(address string) (NetworkResourceType, string, netip.Prefix, error) { + if prefix, err := netip.ParsePrefix(address); err == nil { + if prefix.Bits() == 32 || prefix.Bits() == 128 { + return host, "", prefix, nil } - if ip != nil && ones == 32 { - return host, address + "/32", nil - } - return subnet, address, nil + return subnet, "", prefix, nil } - if net.ParseIP(address) != nil { - return host, address + "/32", nil + if ip, err := netip.ParseAddr(address); err == nil { + return host, "", netip.PrefixFrom(ip, ip.BitLen()), nil } domainRegex := regexp.MustCompile(`^(\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$`) 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") } diff --git a/management/server/networks/resources/types/resource_test.go b/management/server/networks/resources/types/resource_test.go index 7647371ce..6af384cce 100644 --- a/management/server/networks/resources/types/resource_test.go +++ b/management/server/networks/resources/types/resource_test.go @@ -1,35 +1,38 @@ package types import ( + "net/netip" "testing" ) func TestGetResourceType(t *testing.T) { tests := []struct { - input string - expectedType NetworkResourceType - expectedErr bool - expectedAddr string + input string + expectedType NetworkResourceType + expectedErr bool + expectedDomain string + expectedPrefix netip.Prefix }{ // Valid host IPs - {"1.1.1.1", host, false, "1.1.1.1/32"}, - {"1.1.1.1/32", 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, "", netip.MustParsePrefix("1.1.1.1/32")}, // Valid subnets - {"192.168.1.0/24", subnet, false, "192.168.1.0/24"}, - {"10.0.0.0/16", subnet, false, "10.0.0.0/16"}, + {"192.168.1.0/24", subnet, false, "", netip.MustParsePrefix("192.168.1.0/24")}, + {"10.0.0.0/16", subnet, false, "", netip.MustParsePrefix("10.0.0.0/16")}, // Valid domains - {"example.com", domain, false, "example.com"}, - {"*.example.com", domain, false, "*.example.com"}, - {"sub.example.com", domain, false, "sub.example.com"}, + {"example.com", domain, false, "example.com", netip.Prefix{}}, + {"*.example.com", domain, false, "*.example.com", netip.Prefix{}}, + {"sub.example.com", domain, false, "sub.example.com", netip.Prefix{}}, // Invalid inputs - {"invalid", "", true, ""}, - {"1.1.1.1/abc", "", true, ""}, - {"1234", "", true, ""}, + {"invalid", "", true, "", netip.Prefix{}}, + {"1.1.1.1/abc", "", true, "", netip.Prefix{}}, + {"1234", "", true, "", netip.Prefix{}}, } for _, tt := range tests { 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 { 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") } - if addr != tt.expectedAddr { - t.Errorf("Expected address %v, got %v", tt.expectedAddr, addr) + if prefix != tt.expectedPrefix { + t.Errorf("Expected address %v, got %v", tt.expectedPrefix, prefix) + } + + if domain != tt.expectedDomain { + t.Errorf("Expected domain %v, got %v", tt.expectedDomain, domain) } }) } diff --git a/management/server/store/sql_store_test.go b/management/server/store/sql_store_test.go index 9bb7addcb..ec4b49534 100644 --- a/management/server/store/sql_store_test.go +++ b/management/server/store/sql_store_test.go @@ -2502,7 +2502,13 @@ func TestSqlStore_SaveNetworkResource(t *testing.T) { savedNetResource, err := store.GetNetworkResourceByID(context.Background(), LockingStrengthShare, accountID, netResource.ID) 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) {