From bbe8408a143e7c0290c8ee91ac9d2b1935b21486 Mon Sep 17 00:00:00 2001 From: Orpheus Lummis Date: Wed, 12 Apr 2023 09:03:37 -0400 Subject: [PATCH] fix: API address parameter validation (#1311) --- api/http/server.go | 8 ++++++-- config/config.go | 36 ++++++++++++++++++++++++++++-------- config/config_test.go | 38 +++++++++++++++++++++++++++++++++++++- config/errors.go | 4 ++++ go.mod | 6 +++--- go.sum | 12 ++++++------ 6 files changed, 84 insertions(+), 20 deletions(-) diff --git a/api/http/server.go b/api/http/server.go index e8062ccdb2..d7e2b4f47c 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -138,10 +138,14 @@ func WithAddress(addr string) func(*Server) { // If the address is not localhost, we check to see if it's a valid IP address. // If it's not a valid IP, we assume that it's a domain name to be used with TLS. if !strings.HasPrefix(addr, "localhost:") && !strings.HasPrefix(addr, ":") { - ip := net.ParseIP(addr) + host, _, err := net.SplitHostPort(addr) + if err != nil { + host = addr + } + ip := net.ParseIP(host) if ip == nil { s.Addr = httpPort - s.options.domain = immutable.Some(addr) + s.options.domain = immutable.Some(host) } } } diff --git a/config/config.go b/config/config.go index 2e67eadc49..dbcfc54ab5 100644 --- a/config/config.go +++ b/config/config.go @@ -57,6 +57,7 @@ import ( ma "github.com/multiformats/go-multiaddr" "github.com/spf13/pflag" "github.com/spf13/viper" + "golang.org/x/net/idna" badgerds "github.com/sourcenetwork/defradb/datastore/badger/v3" "github.com/sourcenetwork/defradb/logging" @@ -296,18 +297,37 @@ func (apicfg *APIConfig) validate() error { if apicfg.Address == "" { return ErrInvalidDatabaseURL } - ip := net.ParseIP(apicfg.Address) - if strings.HasPrefix(apicfg.Address, "localhost") || strings.HasPrefix(apicfg.Address, ":") || ip != nil { - _, err := net.ResolveTCPAddr("tcp", apicfg.Address) - if err != nil { - return NewErrInvalidDatabaseURL(err) - } - } else if ip == nil { - return ErrInvalidDatabaseURL + + if apicfg.Address == "localhost" || net.ParseIP(apicfg.Address) != nil { //nolint:goconst + return ErrMissingPortNumber + } + + if isValidDomainName(apicfg.Address) { + return nil + } + + host, _, err := net.SplitHostPort(apicfg.Address) + if err != nil { + return NewErrInvalidDatabaseURL(err) } + if host == "localhost" { + return nil + } + if net.ParseIP(host) == nil { + return ErrNoPortWithDomain + } + return nil } +func isValidDomainName(domain string) bool { + asciiDomain, err := idna.Registration.ToASCII(domain) + if err != nil { + return false + } + return asciiDomain == domain +} + // AddressToURL provides the API address as URL. func (apicfg *APIConfig) AddressToURL() string { if apicfg.TLS { diff --git a/config/config_test.go b/config/config_test.go index 0030346b1a..2337a39eda 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -478,9 +478,45 @@ func TestValidationInvalidLoggingConfig(t *testing.T) { assert.ErrorIs(t, err, ErrInvalidLogLevel) } -func TestValidationAddressBasic(t *testing.T) { +func TestValidationAddressBasicIncomplete(t *testing.T) { cfg := DefaultConfig() cfg.API.Address = "localhost" err := cfg.validate() assert.ErrorIs(t, err, ErrFailedToValidateConfig) } + +func TestValidationAddressLocalhostValid(t *testing.T) { + cfg := DefaultConfig() + cfg.API.Address = "localhost:9876" + err := cfg.validate() + assert.NoError(t, err) +} + +func TestValidationAddress0000Incomplete(t *testing.T) { + cfg := DefaultConfig() + cfg.API.Address = "0.0.0.0" + err := cfg.validate() + assert.ErrorIs(t, err, ErrFailedToValidateConfig) +} + +func TestValidationAddress0000Valid(t *testing.T) { + cfg := DefaultConfig() + cfg.API.Address = "0.0.0.0:9876" + err := cfg.validate() + assert.NoError(t, err) +} + +func TestValidationAddressDomainWithSubdomainValidWithTLSCorrectPortIsInvalid(t *testing.T) { + cfg := DefaultConfig() + cfg.API.Address = "sub.example.com:443" + cfg.API.TLS = true + err := cfg.validate() + assert.ErrorIs(t, err, ErrNoPortWithDomain) +} + +func TestValidationAddressDomainWithSubdomainWrongPortIsInvalid(t *testing.T) { + cfg := DefaultConfig() + cfg.API.Address = "sub.example.com:9876" + err := cfg.validate() + assert.ErrorIs(t, err, ErrNoPortWithDomain) +} diff --git a/config/errors.go b/config/errors.go index 97f25ac6ce..926c8bebf2 100644 --- a/config/errors.go +++ b/config/errors.go @@ -47,6 +47,8 @@ const ( errLoadingConfig string = "failed to load config" errUnableToParseByteSize string = "unable to parse byte size" errInvalidDatastorePath string = "invalid datastore path" + errMissingPortNumber string = "missing port number" + errNoPortWithDomain string = "cannot provide port with domain name" ) var ( @@ -82,6 +84,8 @@ var ( ErrUnableToParseByteSize = errors.New(errUnableToParseByteSize) ErrInvalidLoggerConfig = errors.New(errInvalidLoggerConfig) ErrorInvalidDatastorePath = errors.New(errInvalidDatastorePath) + ErrMissingPortNumber = errors.New(errMissingPortNumber) + ErrNoPortWithDomain = errors.New(errNoPortWithDomain) ) func NewErrFailedToWriteFile(inner error, path string) error { diff --git a/go.mod b/go.mod index 6b55dbd7d2..035b606c7b 100644 --- a/go.mod +++ b/go.mod @@ -52,6 +52,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v0.36.0 go.uber.org/zap v1.24.0 golang.org/x/crypto v0.7.0 + golang.org/x/net v0.9.0 google.golang.org/grpc v1.54.0 ) @@ -181,10 +182,9 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230129154200-a960b3787bd2 // indirect golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.8.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect golang.org/x/tools v0.6.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect diff --git a/go.sum b/go.sum index 00b8c261e8..6a8eda91aa 100644 --- a/go.sum +++ b/go.sum @@ -1151,8 +1151,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1253,8 +1253,8 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1266,8 +1266,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=