Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Root URL server config property to replace auth.publicurl #2633

Merged
merged 8 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ The following options can be configured on the server:
internalratelimiter true When set, expensive internal calls are rate-limited to protect the network. Always enabled in strict mode.
loggerformat text Log format (text, json)
strictmode true When set, insecure settings are forbidden.
url Public facing URL of the server (required). Must be HTTPS when strictmode is set.
verbosity info Log level (trace, debug, info, warn, error)
tls.certfile PEM file containing the certificate for the server (also used as client certificate).
tls.certheader Name of the HTTP header that will contain the client certificate when TLS is offloaded.
Expand All @@ -195,7 +196,6 @@ The following options can be configured on the server:
auth.accesstokenlifespan 60 defines how long (in seconds) an access token is valid. Uses default in strict mode.
auth.clockskew 5000 allowed JWT Clock skew in milliseconds
auth.contractvalidators [irma,uzi,dummy,employeeid] sets the different contract validators to use
auth.publicurl public URL which can be reached by a users IRMA client, this should include the scheme and domain: https://example.com. Additional paths should only be added if some sort of url-rewriting is done in a reverse-proxy.
auth.http.timeout 30 HTTP timeout (in seconds) used by the Auth API HTTP client
auth.irma.autoupdateschemas true set if you want automatically update the IRMA schemas every 60 minutes.
auth.irma.schememanager pbdf IRMA schemeManager to use for attributes. Can be either 'pbdf' or 'irma-demo'.
Expand Down Expand Up @@ -227,7 +227,7 @@ The following options can be configured on the server:
http.default.auth.type Whether to enable authentication for the default interface, specify 'token_v2' for bearer token mode or 'token' for legacy bearer token mode.
http.default.cors.origin [] When set, enables CORS from the specified origins on the default HTTP interface.
**JSONLD**
jsonld.contexts.localmapping [https://nuts.nl/credentials/v1=assets/contexts/nuts.ldjson,https://www.w3.org/2018/credentials/v1=assets/contexts/w3c-credentials-v1.ldjson,https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json=assets/contexts/lds-jws2020-v1.ldjson,https://schema.org=assets/contexts/schema-org-v13.ldjson] This setting allows mapping external URLs to local files for e.g. preventing external dependencies. These mappings have precedence over those in remoteallowlist.
jsonld.contexts.localmapping [https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json=assets/contexts/lds-jws2020-v1.ldjson,https://schema.org=assets/contexts/schema-org-v13.ldjson,https://nuts.nl/credentials/v1=assets/contexts/nuts.ldjson,https://www.w3.org/2018/credentials/v1=assets/contexts/w3c-credentials-v1.ldjson] This setting allows mapping external URLs to local files for e.g. preventing external dependencies. These mappings have precedence over those in remoteallowlist.
jsonld.contexts.remoteallowlist [https://schema.org,https://www.w3.org/2018/credentials/v1,https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json] In strict mode, fetching external JSON-LD contexts is not allowed except for context-URLs listed here.
**Network**
network.bootstrapnodes [] List of bootstrap nodes ('<host>:<port>') which the node initially connect to.
Expand Down Expand Up @@ -291,7 +291,7 @@ See :ref:`getting started <configure-node>` on how to set this up correctly.

The incorporated `IRMA server <https://irma.app/docs/irma-server/#production-mode>`_ is automatically changed to production mode.
In fact, running in strict mode is the only way to enable IRMA's production mode.
In addition, it requires ``auth.irma.schememanager=pbdf`` and the ``auth.publicurl`` where the IRMA client can reach the server must be set.
In addition, it requires ``auth.irma.schememanager=pbdf``.

As a general safety precaution ``auth.contractvalidators`` ignores the ``dummy`` option if configured,
requesting an access token from another node on ``/n2n/auth/v1/accesstoken`` does not return any error details,
Expand Down
9 changes: 3 additions & 6 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,14 @@ func (auth *Auth) Configure(config core.ServerConfig) error {
return errors.New("in strictmode the only valid irma-scheme-manager is 'pbdf'")
}

if auth.config.PublicURL == "" {
return errors.New("invalid auth.publicurl: must provide url")
}
var err error
auth.publicURL, err = core.ParsePublicURL(auth.config.PublicURL, config.Strictmode)
auth.publicURL, err = config.ServerURL()
if err != nil {
return fmt.Errorf("invalid auth.publicurl: %w", err)
return err
}

auth.contractNotary = notary.NewNotary(notary.Config{
PublicURL: auth.config.PublicURL,
PublicURL: auth.publicURL.String(),
IrmaConfigPath: path.Join(config.Datadir, "irma"),
IrmaSchemeManager: auth.config.Irma.SchemeManager,
AutoUpdateIrmaSchemas: auth.config.Irma.AutoUpdateSchemas,
Expand Down
52 changes: 2 additions & 50 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ import (

func TestAuth_Configure(t *testing.T) {
tlsServerConfig := *core.NewServerConfig()
tlsServerConfig.URL = "https://nuts.nl"
tlsServerConfig.LegacyTLS.TrustStoreFile = "test/certs/ca.pem"
tlsServerConfig.LegacyTLS.CertKeyFile = "test/certs/example.com.key"
tlsServerConfig.LegacyTLS.CertFile = "test/certs/example.com.pem"

t.Run("ok", func(t *testing.T) {
config := DefaultConfig()
config.ContractValidators = []string{"uzi"}
config.PublicURL = "https://nuts.nl"
ctrl := gomock.NewController(t)
pkiMock := pki.NewMockProvider(ctrl)
pkiMock.EXPECT().AddTruststore(gomock.Any()) // uzi
Expand All @@ -53,28 +53,6 @@ func TestAuth_Configure(t *testing.T) {
require.NoError(t, i.Configure(tlsServerConfig))
})

t.Run("publicUrl", func(t *testing.T) {
t.Run("error - missing", func(t *testing.T) {
authCfg := TestConfig()
authCfg.PublicURL = ""
authCfg.Irma.SchemeManager = "pbdf"
i := testInstance(t, authCfg)
cfg := core.NewServerConfig()
cfg.Strictmode = true
cfg.TLS.CertFile = "certificate.pem"
assert.EqualError(t, i.Configure(*cfg), "invalid auth.publicurl: must provide url")
})
t.Run("error - invalid URL (must be hostname, not IP)", func(t *testing.T) {
authCfg := TestConfig()
authCfg.Irma.SchemeManager = "pbdf"
authCfg.PublicURL = "https://127.0.0.1"
i := testInstance(t, authCfg)
cfg := core.NewServerConfig()
cfg.Strictmode = true
assert.EqualError(t, i.Configure(*cfg), "invalid auth.publicurl: hostname is IP")
})
})

t.Run("error - IRMA config failure", func(t *testing.T) {
authCfg := TestConfig()
authCfg.Irma.SchemeManager = "non-existing"
Expand Down Expand Up @@ -106,6 +84,7 @@ func TestAuth_Configure(t *testing.T) {
i := testInstance(t, authCfg)
serverConfig := core.NewServerConfig()
serverConfig.Strictmode = true
serverConfig.URL = "https://nuts.nl"
err := i.Configure(*serverConfig)
assert.EqualError(t, err, "in strictmode TLS must be enabled")
})
Expand All @@ -118,33 +97,6 @@ func TestAuth_Configure(t *testing.T) {
err := i.Configure(tlsServerConfig)
assert.ErrorIs(t, err, assert.AnError)
})
t.Run("public url", func(t *testing.T) {
type test struct {
strict bool
pURL string
errStr string
}
tt := []test{
{true, "", "invalid auth.publicurl: must provide url"},
{true, ":invalid", "invalid auth.publicurl: parse \":invalid\": missing protocol scheme"},
{true, "https://127.0.0.1", "invalid auth.publicurl: hostname is IP"},
{true, "https://example.com", "invalid auth.publicurl: hostname is RFC2606 reserved"},
{true, "https://localhost", "invalid auth.publicurl: hostname is RFC2606 reserved"},
{true, "http://nuts.nl", "invalid auth.publicurl: scheme must be https"},

{false, "", "invalid auth.publicurl: must provide url"},
{false, ":invalid", "invalid auth.publicurl: parse \":invalid\": missing protocol scheme"},
{false, "something://nuts.nl", "invalid auth.publicurl: scheme must be http or https"},
}
authCfg := TestConfig()
cfg := core.NewServerConfig()
for _, test := range tt {
authCfg.PublicURL = test.pURL
i := testInstance(t, authCfg)
cfg.Strictmode = test.strict
assert.EqualError(t, i.Configure(*cfg), test.errStr, "test config: url=%s; strict=%s", test.pURL, test.strict)
}
})
}

func TestAuth_Name(t *testing.T) {
Expand Down
4 changes: 0 additions & 4 deletions auth/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ import (
"github.com/spf13/pflag"
)

// ConfPublicURL is the config key for the public URL the http/irma server can be discovered
const ConfPublicURL = "auth.publicurl"

// ConfClockSkew is the config key for allowed JWT clockskew (deviance of iat, exp) in milliseconds
const ConfClockSkew = "auth.clockskew"

Expand Down Expand Up @@ -55,7 +52,6 @@ func FlagSet() *pflag.FlagSet {

defs := auth.DefaultConfig()
flags.String(ConfIrmaSchemeManager, defs.Irma.SchemeManager, "IRMA schemeManager to use for attributes. Can be either 'pbdf' or 'irma-demo'.")
flags.String(ConfPublicURL, defs.PublicURL, "public URL which can be reached by a users IRMA client, this should include the scheme and domain: https://example.com. Additional paths should only be added if some sort of url-rewriting is done in a reverse-proxy.")
flags.Bool(ConfAutoUpdateIrmaSchemas, defs.Irma.AutoUpdateSchemas, "set if you want automatically update the IRMA schemas every 60 minutes.")
flags.Int(ConfHTTPTimeout, defs.HTTPTimeout, "HTTP timeout (in seconds) used by the Auth API HTTP client")
flags.Int(ConfClockSkew, defs.ClockSkew, "allowed JWT Clock skew in milliseconds")
Expand Down
1 change: 0 additions & 1 deletion auth/cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ func TestFlagSet(t *testing.T) {
ConfAutoUpdateIrmaSchemas,
ConfIrmaSchemeManager,
ConfPresentationExchangeMappingFile,
ConfPublicURL,
ConfV2APIEnabled,
}, keys)
}
Expand Down
1 change: 0 additions & 1 deletion auth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
type Config struct {
Irma IrmaConfig `koanf:"irma"`
HTTPTimeout int `koanf:"http.timeout"`
PublicURL string `koanf:"publicurl"`
ClockSkew int `koanf:"clockskew"`
ContractValidators []string `koanf:"contractvalidators"`
AccessTokenLifeSpan int `koanf:"accesstokenlifespan"`
Expand Down
1 change: 0 additions & 1 deletion auth/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import (
func TestConfig() Config {
config := DefaultConfig()
config.ContractValidators = []string{"dummy"}
config.PublicURL = "https://nuts.nl"
return config
}

Expand Down
3 changes: 0 additions & 3 deletions auth/test/testconfig.yaml

This file was deleted.

2 changes: 1 addition & 1 deletion charts/nuts-node/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ nuts:
network:
enabletls: true
grpcaddr: :5555
url: https://chart-example.local
auth:
publicurl: https://chart-example.local
contractvalidators:
- irma
- uzi
Expand Down
20 changes: 19 additions & 1 deletion core/server_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ import (
"bytes"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"github.com/knadh/koanf"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/posflag"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
"net/url"
"reflect"
"strings"
)
Expand Down Expand Up @@ -58,7 +60,9 @@ type ServerConfig struct {
Datadir string `koanf:"datadir"`
TLS TLSConfig `koanf:"tls"`
LegacyTLS *NetworkTLSConfig `koanf:"network"`
configMap *koanf.Koanf
// URL contains the base URL for public-facing HTTP services.
URL string `koanf:"url"`
configMap *koanf.Koanf
}

// TLSConfig specifies how TLS should be configured for connections.
Expand Down Expand Up @@ -261,6 +265,7 @@ func FlagSet() *pflag.FlagSet {
flagSet.Bool("strictmode", true, "When set, insecure settings are forbidden.")
flagSet.Bool("internalratelimiter", true, "When set, expensive internal calls are rate-limited to protect the network. Always enabled in strict mode.")
flagSet.String("datadir", "./data", "Directory where the node stores its files.")
flagSet.String("url", "", "Public facing URL of the server (required). Must be HTTPS when strictmode is set.")
flagSet.String("tls.certfile", "", "PEM file containing the certificate for the server (also used as client certificate).")
flagSet.String("tls.certkeyfile", "", "PEM file containing the private key of the server certificate.")
flagSet.String("tls.truststorefile", "truststore.pem", "PEM file containing the trusted CA certificates for authenticating remote servers.")
Expand Down Expand Up @@ -315,6 +320,19 @@ func (ngc *ServerConfig) InjectIntoEngine(e Injectable) error {
return unmarshalRecursive([]string{strings.ToLower(e.Name())}, e.Config(), ngc.configMap)
}

// ServerURL returns the parsed URL of the server
func (ngc *ServerConfig) ServerURL() (*url.URL, error) {
// Validate server URL
if ngc.URL == "" {
return nil, errors.New("'url' must be configured")
}
result, err := ParsePublicURL(ngc.URL, ngc.Strictmode)
if err != nil {
return nil, fmt.Errorf("invalid 'url': %w", err)
}
return result, nil
}

func elemType(ty reflect.Type) (reflect.Type, bool) {
isPtr := ty.Kind() == reflect.Ptr

Expand Down
25 changes: 25 additions & 0 deletions core/server_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,28 @@ func TestTLSConfig_LoadTrustStore(t *testing.T) {
assert.EqualError(t, err, "unable to read trust store (file=test/non-existent.pem): open test/non-existent.pem: no such file or directory")
})
}

func TestServerConfig_ServerURL(t *testing.T) {
t.Run("url", func(t *testing.T) {
cfg := ServerConfig{URL: "https://example.com"}
actual, err := cfg.ServerURL()
assert.NoError(t, err)
assert.Equal(t, "https://example.com", actual.String())
})
t.Run("public URL can be http when not in strict mode", func(t *testing.T) {
cfg := ServerConfig{URL: "http://nuts.nl"}
actual, err := cfg.ServerURL()
assert.NoError(t, err)
assert.Equal(t, "http://nuts.nl", actual.String())
})
t.Run("url is required", func(t *testing.T) {
cfg := ServerConfig{}
_, err := cfg.ServerURL()
assert.EqualError(t, err, "'url' must be configured")
})
t.Run("url is invalid", func(t *testing.T) {
cfg := ServerConfig{URL: "nuts.nl"}
_, err := cfg.ServerURL()
assert.EqualError(t, err, "invalid 'url': url must contain scheme and host")
})
}
Loading
Loading