diff --git a/cmd/csaf_provider/config.go b/cmd/csaf_provider/config.go index 2b07ab6..bf87625 100644 --- a/cmd/csaf_provider/config.go +++ b/cmd/csaf_provider/config.go @@ -48,6 +48,7 @@ type config struct { TLPs []tlp `toml:"tlps"` UploadSignature bool `toml:"upload_signature"` CanonicalURLPrefix string `toml:"canonical_url_prefix"` + CertificateAndPassword bool `toml:"certificate_and_password"` NoPassphrase bool `toml:"no_passphrase"` NoValidation bool `toml:"no_validation"` NoWebUI bool `toml:"no_web_ui"` diff --git a/cmd/csaf_provider/controller.go b/cmd/csaf_provider/controller.go index 3ce1005..0f6e264 100644 --- a/cmd/csaf_provider/controller.go +++ b/cmd/csaf_provider/controller.go @@ -72,36 +72,57 @@ func (c *controller) bind(pim *pathInfoMux) { pim.handleFunc("/api/create", c.auth(api(c.create))) } -// auth wraps the given http.HandlerFunc and returns an new one after authenticating the -// password contained in the header "X-CSAF-PROVIDER-AUTH" with the "password" config value -// if set, otherwise returns the given http.HandlerFunc. +// authenticate checks if the incoming request confirms with the +// configured authentication mechanism. +func (c *controller) authenticate(r *http.Request) bool { + + verify := os.Getenv("SSL_CLIENT_VERIFY") + log.Printf("SSL_CLIENT_VERIFY: %s\n", verify) + if verify == "SUCCESS" || strings.HasPrefix(verify, "FAILED") { + // potentially we want to see the Issuer when there is a problem + // but it is not clear if we get this far in case of "FAILED". + // docs (accessed 2022-03-31 when 1.20.2 was current stable): + // https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_verify + log.Printf("SSL_CLIENT_I_DN: %s\n", os.Getenv("SSL_CLIENT_I_DN")) + } + + checkCert := func() bool { + return verify == "SUCCESS" && (c.cfg.Issuer == nil || *c.cfg.Issuer == os.Getenv("SSL_CLIENT_I_DN")) + } + + checkPassword := func() bool { + return c.cfg.checkPassword(r.Header.Get("X-CSAF-PROVIDER-AUTH")) + } + + if c.cfg.CertificateAndPassword { + if c.cfg.Password == nil { + log.Println("No password set, declining access.") + return false + } + log.Printf("user: %s\n", os.Getenv("SSL_CLIENT_S_DN")) + return checkPassword() && checkCert() + } + + switch { + case checkCert(): + log.Printf("user: %s\n", os.Getenv("SSL_CLIENT_S_DN")) + case c.cfg.Password == nil: + log.Println("No password set, declining access.") + return false + default: + return checkPassword() + } + return true +} + +// auth is a middleware to decorate endpoints with authentication. func (c *controller) auth( fn func(http.ResponseWriter, *http.Request), ) func(http.ResponseWriter, *http.Request) { return func(rw http.ResponseWriter, r *http.Request) { - - verify := os.Getenv("SSL_CLIENT_VERIFY") - log.Printf("SSL_CLIENT_VERIFY: %s\n", verify) - if verify == "SUCCESS" || strings.HasPrefix(verify, "FAILED") { - // potentially we want to see the Issuer when there is a problem - // but it is not clear if we get this far in case of "FAILED". - // docs (accessed 2022-03-31 when 1.20.2 was current stable): - // https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_verify - log.Printf("SSL_CLIENT_I_DN: %s\n", os.Getenv("SSL_CLIENT_I_DN")) - } - - switch { - case verify == "SUCCESS" && (c.cfg.Issuer == nil || *c.cfg.Issuer == os.Getenv("SSL_CLIENT_I_DN")): - log.Printf("user: %s\n", os.Getenv("SSL_CLIENT_S_DN")) - case c.cfg.Password == nil: - log.Println("No password set, declining access.") + if !c.authenticate(r) { http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden) return - default: - if pa := r.Header.Get("X-CSAF-PROVIDER-AUTH"); !c.cfg.checkPassword(pa) { - http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden) - return - } } fn(rw, r) } diff --git a/docs/csaf_provider.md b/docs/csaf_provider.md index 8fde69d..d755398 100644 --- a/docs/csaf_provider.md +++ b/docs/csaf_provider.md @@ -15,6 +15,7 @@ Following options are supported in the config file: - web: Specify the web folder. Default: `/var/www/html`. - upload_signature: Send signature with the request, an additional input-field in the web interface will be shown to let user enter an ascii armored signature. Default: `false`. - canonical_url_prefix: start of the URL where contents shall be accessible from the internet. Default: `https://$SERVER_NAME`. + - certificate_and_password: Require password and a valid Client Certificate for write access. Default: false - no_passphrase: Let the user send the request without having to send a password. If set to true, the input-field in the web interface will be omitted. Default: `false`. - no_validation: Skip validation of the uploaded CSAF document against the JSON schema. Default: `false`. - no_web_ui: Disable the web interface. Default: `false`.