diff --git a/README.rst b/README.rst index 14908033ac..eab088dd05 100644 --- a/README.rst +++ b/README.rst @@ -246,6 +246,7 @@ If your use case does not use these features, you can ignore this table. auth.contractvalidators [irma,dummy,employeeid] sets the different contract validators to use 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'. + auth.irma.cors.origin [] sets the allowed CORS origins for the IRMA server **Events** events.nats.hostname 0.0.0.0 Hostname for the NATS server events.nats.port 4222 Port where the NATS server listens on diff --git a/auth/auth.go b/auth/auth.go index 0c934d0982..12f7d07fa2 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -147,6 +147,7 @@ func (auth *Auth) Configure(config core.ServerConfig) error { ContractValidators: auth.config.ContractValidators, ContractValidity: contractValidity, StrictMode: config.Strictmode, + CORSOrigin: auth.config.Irma.CORS.Origin, }, auth.vcr, resolver.DIDKeyResolver{Resolver: auth.vdrInstance.Resolver()}, auth.keyStore, auth.jsonldManager, auth.pkiProvider) auth.tlsConfig, err = auth.pkiProvider.CreateTLSConfig(config.TLS) // returns nil if TLS is disabled diff --git a/auth/cmd/cmd.go b/auth/cmd/cmd.go index a76ba6fbf1..5a2abf01e5 100644 --- a/auth/cmd/cmd.go +++ b/auth/cmd/cmd.go @@ -35,6 +35,9 @@ const ConfAutoUpdateIrmaSchemas = "auth.irma.autoupdateschemas" // ConfIrmaSchemeManager allows selecting an IRMA scheme manager. During development this can ben irma-demo. Production should be pdfb const ConfIrmaSchemeManager = "auth.irma.schememanager" +// ConfIrmaCorsOrigin is the config key for the allowed CORS origins for the IRMA server +const ConfIrmaCorsOrigin = "auth.irma.cors.origin" + // ConfHTTPTimeout defines a timeout (in seconds) which is used by the Auth API HTTP client const ConfHTTPTimeout = "auth.http.timeout" @@ -51,6 +54,7 @@ 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.Bool(ConfAutoUpdateIrmaSchemas, defs.Irma.AutoUpdateSchemas, "set if you want automatically update the IRMA schemas every 60 minutes.") + flags.StringSlice(ConfIrmaCorsOrigin, defs.Irma.CORS.Origin, "sets the allowed CORS origins for the IRMA server") 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") flags.Int(ConfAccessTokenLifeSpan, defs.AccessTokenLifeSpan, "defines how long (in seconds) an access token is valid. Uses default in strict mode.") diff --git a/auth/cmd/cmd_test.go b/auth/cmd/cmd_test.go index edbc38f45b..5fe44f4c19 100644 --- a/auth/cmd/cmd_test.go +++ b/auth/cmd/cmd_test.go @@ -48,6 +48,7 @@ func TestFlagSet(t *testing.T) { ConfContractValidators, ConfHTTPTimeout, ConfAutoUpdateIrmaSchemas, + ConfIrmaCorsOrigin, ConfIrmaSchemeManager, }, keys) } diff --git a/auth/config.go b/auth/config.go index 71cbc7d75c..0f30bd3c95 100644 --- a/auth/config.go +++ b/auth/config.go @@ -44,8 +44,15 @@ type AuthorizationEndpointConfig struct { } type IrmaConfig struct { - SchemeManager string `koanf:"schememanager"` - AutoUpdateSchemas bool `koanf:"autoupdateschemas"` + SchemeManager string `koanf:"schememanager"` + AutoUpdateSchemas bool `koanf:"autoupdateschemas"` + CORS CORSConfig `koanf:"cors"` +} + +// CORSConfig contains configuration for Cross Origin Resource Sharing. +type CORSConfig struct { + // Origin specifies the AllowOrigin option. If no origins are given CORS is considered to be disabled. + Origin []string `koanf:"origin"` } // DefaultConfig returns an instance of Config with the default values. diff --git a/auth/services/irma/factory.go b/auth/services/irma/factory.go index 1533e4e3e7..423ddfcafd 100644 --- a/auth/services/irma/factory.go +++ b/auth/services/irma/factory.go @@ -48,6 +48,8 @@ type Config struct { // Use the IRMA server in production mode. Without this the IRMA app needs to be in "developer mode" // https://irma.app/docs/irma-app/#developer-mode Production bool + // CORS configuration + CORSOrigin []string } // NewSignerAndVerifier creates a new IRMA signer and verifier. @@ -64,6 +66,7 @@ func NewSignerAndVerifier(cfg Config) (*Signer, *Verifier, error) { return &Signer{ sessionHandler: irmaServer, schemeManager: cfg.IrmaSchemeManager, + cors: cfg.CORSOrigin, }, &Verifier{ IrmaConfig: irmaConfig, Templates: contract.StandardContractTemplates, diff --git a/auth/services/irma/factory_test.go b/auth/services/irma/factory_test.go index 84fd943a4f..d1d5c44939 100644 --- a/auth/services/irma/factory_test.go +++ b/auth/services/irma/factory_test.go @@ -64,8 +64,10 @@ func TestNewSignerAndVerifier(t *testing.T) { IrmaSchemeManager: "empty", AutoUpdateIrmaSchemas: false, Production: false, + CORSOrigin: []string{"*"}, }) require.NoError(t, err) assert.NotNil(t, signer) assert.NotNil(t, verifier) + assert.Equal(t, []string{"*"}, signer.cors) } diff --git a/auth/services/irma/signer.go b/auth/services/irma/signer.go index e0c30530b1..936b73c75e 100644 --- a/auth/services/irma/signer.go +++ b/auth/services/irma/signer.go @@ -24,6 +24,7 @@ import ( "encoding/json" "fmt" "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" "github.com/nuts-foundation/nuts-node/core" "github.com/sirupsen/logrus" "net/http" @@ -46,6 +47,7 @@ import ( type Signer struct { sessionHandler signingSessionHandler schemeManager string + cors []string } // SessionPtr should be made private when v0 is removed @@ -132,6 +134,20 @@ func (v Signer) Routes(router core.EchoRouter) { for _, method := range methods { router.Add(method, IrmaMountPath+"/*", irmaEchoHandler) } + skipper := func(c echo.Context) bool { + return !strings.HasPrefix(c.Path(), IrmaMountPath) + } + + // enable CORS for the IRMA endpoints + if len(v.cors) > 0 { + // print warning if CORS is enabled for all origins + for _, origin := range v.cors { + if strings.TrimSpace(origin) == "*" { + log.Logger().Warnf("Enabling wildcard CORS for IRMA/Yivi endpoints is not recommended") + } + } + router.Use(middleware.CORSWithConfig(middleware.CORSConfig{AllowOrigins: v.cors, Skipper: skipper})) + } } // SigningSessionStatus returns the current status of a certain session. diff --git a/auth/services/notary/notary.go b/auth/services/notary/notary.go index 752505df17..6d89d412a7 100644 --- a/auth/services/notary/notary.go +++ b/auth/services/notary/notary.go @@ -61,6 +61,7 @@ type Config struct { IrmaSchemeManager string ContractValidators []string ContractValidity time.Duration + CORSOrigin []string } func (c Config) hasContractValidator(cv string) bool { @@ -164,6 +165,7 @@ func (n *notary) Configure() error { AutoUpdateIrmaSchemas: n.config.AutoUpdateIrmaSchemas, // Deduce IRMA production mode from the nuts strict-mode Production: n.config.StrictMode, + CORSOrigin: n.config.CORSOrigin, } signer, verifier, err := irma.NewSignerAndVerifier(cfg) if err != nil { diff --git a/docs/pages/deployment/server_options_didnuts.rst b/docs/pages/deployment/server_options_didnuts.rst index 3569e6a9a5..5011c94994 100755 --- a/docs/pages/deployment/server_options_didnuts.rst +++ b/docs/pages/deployment/server_options_didnuts.rst @@ -16,6 +16,7 @@ auth.contractvalidators [irma,dummy,employeeid] sets the different contract validators to use 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'. + auth.irma.cors.origin [] sets the allowed CORS origins for the IRMA server **Events** events.nats.hostname 0.0.0.0 Hostname for the NATS server events.nats.port 4222 Port where the NATS server listens on diff --git a/docs/pages/release_notes.rst b/docs/pages/release_notes.rst index 81195b94da..f0e611330e 100644 --- a/docs/pages/release_notes.rst +++ b/docs/pages/release_notes.rst @@ -99,7 +99,7 @@ The HTTP interface has been reworked to make deployments simpler and more secure - No more dynamic binding of endpoints to ports, endpoints are now bound to the internal interface (``8081``) or the public interface (``8080``). - Server-side TLS for HTTP has been dropped, since the Nuts node is always expected to be deployed behind a reverse proxy/ingress that handles TLS termination. - API authentication is now only applied to ``/internal`` endpoints, since those are the only API endpoints that should be protected with authentication. -- CORS support has been removed. As it is only required by user authentication endpoints that are considered to be deprecated, CORS headers can be set by a reverse proxy if still required. +- CORS configuration for IRMA/Yivi has been moved to the `auth.irma.cors.origin` config parameter. Port configuration ------------------