From 8bfb3b80ed6a394d93f7d044ed290d697492e4aa Mon Sep 17 00:00:00 2001 From: Pavel Sviderski Date: Tue, 10 Dec 2024 17:24:29 +1000 Subject: [PATCH] basic validation for hostname in port publishing --- internal/api/port.go | 23 ++++++++++++++++++----- internal/api/service_test.go | 35 ++++++++++++++++++++++------------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/internal/api/port.go b/internal/api/port.go index 3442aad1..db019848 100644 --- a/internal/api/port.go +++ b/internal/api/port.go @@ -45,12 +45,17 @@ func (p *PortSpec) Validate() error { } switch p.Mode { - case PortModeIngress: + case "", PortModeIngress: // Default mode is ingress if not specified. if p.HostIP.IsValid() { return fmt.Errorf("host IP cannot be specified in %s mode", PortModeIngress) } - if p.Hostname != "" && p.Protocol != ProtocolHTTP && p.Protocol != ProtocolHTTPS { - return fmt.Errorf("hostname is only valid with '%s' or '%s' protocols", ProtocolHTTP, ProtocolHTTPS) + if p.Hostname != "" { + if p.Protocol != ProtocolHTTP && p.Protocol != ProtocolHTTPS { + return fmt.Errorf("hostname is only valid with '%s' or '%s' protocols", ProtocolHTTP, ProtocolHTTPS) + } + if err := validateHostname(p.Hostname); err != nil { + return fmt.Errorf("invalid hostname '%s': %w", p.Hostname, err) + } } if p.Hostname == "" && (p.Protocol == ProtocolHTTP || p.Protocol == ProtocolHTTPS) { return fmt.Errorf("hostname is required with '%s' or '%s' protocols", ProtocolHTTP, ProtocolHTTPS) @@ -136,7 +141,6 @@ func ParsePortSpec(port string) (PortSpec, error) { if parts[0] == "" { return spec, fmt.Errorf("hostname must not be empty") } - // TODO: validate hostname? spec.Hostname = parts[0] } @@ -170,7 +174,6 @@ func ParsePortSpec(port string) (PortSpec, error) { if parts[0] == "" { return spec, fmt.Errorf("hostname must not be empty") } - // TODO: validate hostname? spec.Hostname = parts[0] } @@ -208,3 +211,13 @@ func parsePort(s string) (uint16, error) { } return uint16(port), nil } + +func validateHostname(hostname string) error { + if hostname == "" { + return fmt.Errorf("must not be empty") + } + if !strings.Contains(hostname, ".") { + return fmt.Errorf("must be a valid domain name containing at least one dot") + } + return nil +} diff --git a/internal/api/service_test.go b/internal/api/service_test.go index 8648eb64..5381ae38 100644 --- a/internal/api/service_test.go +++ b/internal/api/service_test.go @@ -214,7 +214,7 @@ func TestParsePortSpec(t *testing.T) { }, { name: "invalid published port", - port: "test:invalid:8080", + port: "app.example.com:invalid:8080", wantErr: "invalid published port", }, { @@ -232,6 +232,11 @@ func TestParsePortSpec(t *testing.T) { port: "8000:8080/https", wantErr: "hostname is required", }, + { + name: "invalid hostname", + port: "app:8080/http", + wantErr: "invalid hostname 'app': must be a valid domain name containing at least one dot", + }, { name: "hostname with tcp protocol", port: "app.example.com:8080/tcp", @@ -324,13 +329,19 @@ func TestPortSpec_Validate(t *testing.T) { Mode: PortModeIngress, }, }, + { + name: "ingress mode tcp", + spec: PortSpec{ + ContainerPort: 8080, + Protocol: ProtocolTCP, + }, + }, { name: "ingress mode with published tcp port", spec: PortSpec{ PublishedPort: 80, ContainerPort: 8080, Protocol: ProtocolTCP, - Mode: PortModeIngress, }, }, { @@ -338,7 +349,6 @@ func TestPortSpec_Validate(t *testing.T) { spec: PortSpec{ ContainerPort: 8080, Protocol: ProtocolUDP, - Mode: PortModeIngress, }, }, { @@ -347,7 +357,6 @@ func TestPortSpec_Validate(t *testing.T) { PublishedPort: 80, ContainerPort: 8080, Protocol: ProtocolUDP, - Mode: PortModeIngress, }, }, { @@ -356,7 +365,6 @@ func TestPortSpec_Validate(t *testing.T) { Hostname: "app.example.com", ContainerPort: 8080, Protocol: ProtocolHTTP, - Mode: PortModeIngress, }, }, { @@ -365,7 +373,6 @@ func TestPortSpec_Validate(t *testing.T) { Hostname: "app.example.com", ContainerPort: 8080, Protocol: ProtocolHTTPS, - Mode: PortModeIngress, }, }, { @@ -375,7 +382,6 @@ func TestPortSpec_Validate(t *testing.T) { PublishedPort: 6443, ContainerPort: 8080, Protocol: ProtocolHTTPS, - Mode: PortModeIngress, }, }, @@ -415,7 +421,6 @@ func TestPortSpec_Validate(t *testing.T) { name: "missing container port", spec: PortSpec{ Protocol: ProtocolTCP, - Mode: PortModeIngress, }, wantErr: "container port must be non-zero", }, @@ -424,7 +429,6 @@ func TestPortSpec_Validate(t *testing.T) { spec: PortSpec{ ContainerPort: 8080, Protocol: "invalid", - Mode: PortModeIngress, }, wantErr: "invalid protocol: 'invalid'", }, @@ -443,16 +447,23 @@ func TestPortSpec_Validate(t *testing.T) { Hostname: "app.example.com", ContainerPort: 8080, Protocol: ProtocolTCP, - Mode: PortModeIngress, }, wantErr: "hostname is only valid with 'http' or 'https' protocols", }, + { + name: "invalid hostname", + spec: PortSpec{ + Hostname: "app", + ContainerPort: 8080, + Protocol: ProtocolHTTPS, + }, + wantErr: "invalid hostname 'app': must be a valid domain name containing at least one dot", + }, { name: "missing hostname with http", spec: PortSpec{ ContainerPort: 8080, Protocol: ProtocolHTTP, - Mode: PortModeIngress, }, wantErr: "hostname is required with 'http' or 'https' protocols", }, @@ -461,7 +472,6 @@ func TestPortSpec_Validate(t *testing.T) { spec: PortSpec{ ContainerPort: 8080, Protocol: ProtocolHTTPS, - Mode: PortModeIngress, }, wantErr: "hostname is required with 'http' or 'https' protocols", }, @@ -471,7 +481,6 @@ func TestPortSpec_Validate(t *testing.T) { HostIP: netip.MustParseAddr("127.0.0.1"), ContainerPort: 8080, Protocol: ProtocolTCP, - Mode: PortModeIngress, }, wantErr: "host IP cannot be specified in ingress mode", },