1
0
Fork 0
mirror of https://github.com/gocsaf/csaf.git synced 2025-12-22 18:15:42 +01:00

Merge branch 'main' into improve_tlp_error_message

This commit is contained in:
Sascha L. Teichmann 2022-02-17 10:34:03 +01:00
commit cffc7aaa66
10 changed files with 345 additions and 49 deletions

57
Makefile Normal file
View file

@ -0,0 +1,57 @@
# This file is Free Software under the MIT License
# without warranty, see README.md and LICENSES/MIT.txt for details.
#
# SPDX-License-Identifier: MIT
#
# SPDX-FileCopyrightText: 2021 German Federal Office for Information Security (BSI) <https://www.bsi.bund.de>
# Software-Engineering: 2021 Intevation GmbH <https://intevation.de>
#
# Makefile to build csaf_distribution components
SHELL = /bin/bash
BUILD = go build
MKDIR = mkdir -p
.PHONY: build build_linux build_win tag_checked_out mostlyclean
all:
@echo choose a target from: build build_linux build_win mostlyclean
@echo prepend \`make BUILDTAG=1\` to checkout the highest git tag before building
@echo or set BUILDTAG to a specific tag
# Build all binaries
build: build_linux build_win
# if BUILDTAG == 1 set it to the highest git tag
ifeq ($(strip $(BUILDTAG)),1)
override BUILDTAG = $(shell git tag --sort=-version:refname | head -n 1)
endif
ifdef BUILDTAG
# add the git tag checkout to the requirements of our build targets
build_linux build_win: tag_checked_out
endif
tag_checked_out:
$(if $(strip $(BUILDTAG)),,$(error no git tag found))
git checkout -q tags/${BUILDTAG}
@echo Don\'t forget that we are in checked out tag $(BUILDTAG) now.
# Build binaries and place them under bin-$(GOOS)-$(GOARCH)
# Using 'Target-specific Variable Values' to specify the build target system
GOARCH = amd64
build_linux: GOOS = linux
build_win: GOOS = windows
build_linux build_win:
$(eval BINDIR = bin-$(GOOS)-$(GOARCH)/ )
$(MKDIR) $(BINDIR)
env GOARCH=$(GOARCH) GOOS=$(GOOS) $(BUILD) -o $(BINDIR) -v ./cmd/...
# Remove bin-*-* directories
mostlyclean:
rm -rf ./bin-*-*
@echo Files in \`go env GOCACHE\` remain.

View file

@ -2,23 +2,29 @@
**WIP**: A proof of concept for a CSAF trusted provider, checker and aggregator. **WIP**: A proof of concept for a CSAF trusted provider, checker and aggregator.
## Setup ## Setup
- A recent version of **Go** (1.17+) should be installed. [Go installation](https://go.dev/doc/install) - A recent version of **Go** (1.17+) should be installed. [Go installation](https://go.dev/doc/install)
- Clone the repository `git clone https://github.com/csaf-poc/csaf_distribution.git ` - Clone the repository `git clone https://github.com/csaf-poc/csaf_distribution.git `
- Build Go components - Build Go components Makefile supplies the following targets:
``` bash - Build For GNU/Linux System: `make build_linux`
cd csaf_distribution - Build For Windows System (cross build): `make build_win`
go build -v ./cmd/... - Build For both linux and windows: `make build`
``` - Build from a specific github tag by passing the intended tag to the `BUILDTAG` variable.
E.g. `make BUILDTAG=v1.0.0 build` or `make BUILDTAG=1 build_linux`.
The special value `1` means checking out the highest github tag for the build.
- Remove the generated binaries und their directories: `make mostlyclean`
- [Install](http://nginx.org/en/docs/install.html) **nginx** Binaries will be placed in directories named like `bin-linux-amd64/` and `bin-windows-amd64/`.
- [Install](https://nginx.org/en/docs/install.html) **nginx**
- To install server certificate on nginx see [docs/install-server-certificate.md](docs/install-server-certificate.md)
- To configure nginx see [docs/provider-setup.md](docs/provider-setup.md) - To configure nginx see [docs/provider-setup.md](docs/provider-setup.md)
## csaf_uploader ## csaf_uploader
csaf_uploader is a command line tool that uploads CSAF documents to the trusted provider (CSAF_Provider). csaf_uploader is a command line tool that uploads CSAF documents to the trusted provider (CSAF_Provider).
Following options are supported: Following options are supported:
@ -27,13 +33,14 @@ Following options are supported:
| -a, --action=[upload\|create] | Action to perform (default: upload) | | -a, --action=[upload\|create] | Action to perform (default: upload) |
| -u, --url=URL | URL of the CSAF provider (default:https:<span></span>//localhost/cgi-bin/csaf_provider.go) | | -u, --url=URL | URL of the CSAF provider (default:https:<span></span>//localhost/cgi-bin/csaf_provider.go) |
| -t, --tlp=[csaf\|white\|green\|amber\|red] | TLP of the feed (default: csaf) | | -t, --tlp=[csaf\|white\|green\|amber\|red] | TLP of the feed (default: csaf) |
| -x, --external-signed | CASF files are signed externally. | | -x, --external-signed | CASF files are signed externally. Assumes .asc files beside CSAF files |
| -k, --key=KEY-FILE | OpenPGP key to sign the CSAF files | | -k, --key=KEY-FILE | OpenPGP key to sign the CSAF files |
| -p, --password=PASSWORD | Authentication password for accessing the CSAF provider | | -p, --password=PASSWORD | Authentication password for accessing the CSAF provider |
| -P, --passphrase=PASSPHRASE | Passphrase to unlock the OpenPGP key | | -P, --passphrase=PASSPHRASE | Passphrase to unlock the OpenPGP key |
| -i, --password-interactive | Enter password interactively | | -i, --password-interactive | Enter password interactively |
| -I, --passphrase-interacive | Enter passphrase interactively | | -I, --passphrase-interacive | Enter passphrase interactively |
| -c, --config=INI-FILE | Path to config ini file | | -c, --config=INI-FILE | Path to config ini file |
| --insecure | Do not check TSL certificates from provider |
| -h, --help | Show help | | -h, --help | Show help |
E.g. creating the initial directiories and files E.g. creating the initial directiories and files

View file

@ -20,6 +20,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"net/url" "net/url"
"regexp" "regexp"
@ -43,6 +44,7 @@ type processor struct {
redirects map[string]string redirects map[string]string
noneTLS map[string]struct{} noneTLS map[string]struct{}
alreadyChecked map[string]whereType alreadyChecked map[string]whereType
pmdURL string
pmd256 []byte pmd256 []byte
pmd interface{} pmd interface{}
keys []*crypto.KeyRing keys []*crypto.KeyRing
@ -119,6 +121,7 @@ func (p *processor) clean() {
for k := range p.alreadyChecked { for k := range p.alreadyChecked {
delete(p.alreadyChecked, k) delete(p.alreadyChecked, k)
} }
p.pmdURL = ""
p.pmd256 = nil p.pmd256 = nil
p.pmd = nil p.pmd = nil
p.keys = nil p.keys = nil
@ -131,6 +134,7 @@ func (p *processor) clean() {
p.badIndices = nil p.badIndices = nil
p.badChanges = nil p.badChanges = nil
} }
func (p *processor) run(reporters []reporter, domains []string) (*Report, error) { func (p *processor) run(reporters []reporter, domains []string) (*Report, error) {
var report Report var report Report
@ -607,7 +611,7 @@ func (p *processor) checkChanges(base string, mask whereType) error {
func (p *processor) processROLIEFeeds(domain string, feeds [][]csaf.Feed) error { func (p *processor) processROLIEFeeds(domain string, feeds [][]csaf.Feed) error {
base, err := url.Parse("https://" + domain + "/.well-known/csaf/") base, err := url.Parse(p.pmdURL)
if err != nil { if err != nil {
return err return err
} }
@ -654,7 +658,10 @@ func (p *processor) checkCSAFs(domain string) error {
} }
// No rolie feeds // No rolie feeds
base := "https://" + domain + "/.well-known/csaf" base, err := basePath(p.pmdURL)
if err != nil {
return err
}
if err := p.checkIndex(base, indexMask); err != nil && err != errContinue { if err := p.checkIndex(base, indexMask); err != nil && err != errContinue {
return err return err
@ -701,36 +708,117 @@ func (p *processor) checkMissing(string) error {
return nil return nil
} }
func (p *processor) checkProviderMetadata(domain string) error { var providerMetadataLocations = [...]string{
".well-known/csaf",
"security/data/csaf",
"advisories/csaf",
"security/csaf",
}
// locateProviderMetadata searches for provider-metadata.json at various
// locations mentioned in "7.1.7 Requirement 7: provider-metadata.json".
func (p *processor) locateProviderMetadata(
domain string,
found func(string, io.Reader) error,
) error {
client := p.httpClient() client := p.httpClient()
url := "https://" + domain + "/.well-known/csaf/provider-metadata.json" tryURL := func(url string) (bool, error) {
use(&p.badProviderMetadatas)
res, err := client.Get(url) res, err := client.Get(url)
if err != nil || res.StatusCode != http.StatusOK ||
res.Header.Get("Content-Type") != "application/json" {
// ignore this as it is expected.
return false, nil
}
if err := func() error {
defer res.Body.Close()
return found(url, res.Body)
}(); err != nil {
return false, err
}
return true, nil
}
for _, loc := range providerMetadataLocations {
url := "https://" + domain + "/" + loc
ok, err := tryURL(url)
if err != nil { if err != nil {
p.badProviderMetadata("Fetching %s: %v.", url, err) if err == errContinue {
return errStop continue
}
return err
}
if ok {
return nil
}
}
// Read from security.txt
path := "https://" + domain + "/.well-known/security.txt"
res, err := client.Get(path)
if err != nil {
return err
} }
if res.StatusCode != http.StatusOK { if res.StatusCode != http.StatusOK {
p.badProviderMetadata("Fetching %s failed. Status code: %d (%s)", return err
url, res.StatusCode, res.Status)
return errStop
} }
loc, err := func() (string, error) {
defer res.Body.Close()
return extractProviderURL(res.Body)
}()
if err != nil {
log.Printf("error: %v\n", err)
return nil
}
if loc != "" {
if _, err = tryURL(loc); err == errContinue {
err = nil
}
}
return err
}
func extractProviderURL(r io.Reader) (string, error) {
sc := bufio.NewScanner(r)
const csaf = "CSAF:"
for sc.Scan() {
line := sc.Text()
if strings.HasPrefix(line, csaf) {
line = strings.TrimSpace(line[len(csaf):])
if !strings.HasPrefix(line, "https://") {
return "", errors.New("CASF: found in security.txt, but does not start with https://")
}
return line, nil
}
}
if err := sc.Err(); err != nil {
return "", err
}
return "", nil
}
func (p *processor) checkProviderMetadata(domain string) error {
use(&p.badProviderMetadatas)
found := func(url string, content io.Reader) error {
// Calculate checksum for later comparison. // Calculate checksum for later comparison.
hash := sha256.New() hash := sha256.New()
if err := func() error { tee := io.TeeReader(content, hash)
defer res.Body.Close() if err := json.NewDecoder(tee).Decode(&p.pmd); err != nil {
tee := io.TeeReader(res.Body, hash) p.badProviderMetadata("%s: Decoding JSON failed: %v", url, err)
return json.NewDecoder(tee).Decode(&p.pmd) return errContinue
}(); err != nil {
p.badProviderMetadata("Decoding JSON failed: %v", err)
return errStop
} }
p.pmd256 = hash.Sum(nil) p.pmd256 = hash.Sum(nil)
@ -740,10 +828,23 @@ func (p *processor) checkProviderMetadata(domain string) error {
return err return err
} }
if len(errors) > 0 { if len(errors) > 0 {
p.badProviderMetadata("Validating against JSON schema failed:") p.badProviderMetadata("%s: Validating against JSON schema failed:", url)
for _, msg := range errors { for _, msg := range errors {
p.badProviderMetadata(strings.ReplaceAll(msg, `%`, `%%`)) p.badProviderMetadata(strings.ReplaceAll(msg, `%`, `%%`))
} }
return errStop
}
p.pmdURL = url
return nil
}
if err := p.locateProviderMetadata(domain, found); err != nil {
return err
}
if p.pmdURL == "" {
p.badProviderMetadata("No provider-metadata.json found.")
return errStop
} }
return nil return nil
} }
@ -851,7 +952,7 @@ func (p *processor) checkPGPKeys(domain string) error {
client := p.httpClient() client := p.httpClient()
base, err := url.Parse("https://" + domain + "/.well-known/csaf/provider-metadata.json") base, err := url.Parse(p.pmdURL)
if err != nil { if err != nil {
return err return err
} }

View file

@ -29,6 +29,8 @@ import (
const dateFormat = time.RFC3339 const dateFormat = time.RFC3339
// cleanFileName removes the "/" "\" charachters and replace the two or more
// occurences of "." with only one from the passed string.
func cleanFileName(s string) string { func cleanFileName(s string) string {
s = strings.ReplaceAll(s, `/`, ``) s = strings.ReplaceAll(s, `/`, ``)
s = strings.ReplaceAll(s, `\`, ``) s = strings.ReplaceAll(s, `\`, ``)
@ -37,6 +39,10 @@ func cleanFileName(s string) string {
return s return s
} }
// loadCSAF loads the csaf file from the request, calls the "UploadLimter" function to
// set the upload limit size of the file and the "cleanFileName" to refine
// the filename. It returns the filename, file content in a buffer of bytes
// and an error.
func (c *controller) loadCSAF(r *http.Request) (string, []byte, error) { func (c *controller) loadCSAF(r *http.Request) (string, []byte, error) {
file, handler, err := r.FormFile("csaf") file, handler, err := r.FormFile("csaf")
if err != nil { if err != nil {
@ -123,6 +129,8 @@ func (c *controller) tlpParam(r *http.Request) (tlp, error) {
return "", fmt.Errorf("unsupported TLP type '%s'", t) return "", fmt.Errorf("unsupported TLP type '%s'", t)
} }
// create calls the "ensureFolders" functions to create the directories and files.
// It returns a struct by success, otherwise an error.
func (c *controller) create(*http.Request) (interface{}, error) { func (c *controller) create(*http.Request) (interface{}, error) {
if err := ensureFolders(c.cfg); err != nil { if err := ensureFolders(c.cfg); err != nil {
return nil, err return nil, err

View file

@ -21,14 +21,16 @@ import (
) )
const ( const (
// The environment name, that contains the path to the config file.
configEnv = "CSAF_CONFIG" configEnv = "CSAF_CONFIG"
defaultConfigPath = "/usr/lib/casf/config.toml" defaultConfigPath = "/usr/lib/casf/config.toml" // Default path to the config file.
defaultFolder = "/var/www/" defaultFolder = "/var/www/" // Default folder path.
defaultWeb = "/var/www/html" defaultWeb = "/var/www/html" // Default web path.
defaultOpenPGPURL = "https://openpgp.circl.lu/pks/lookup?op=get&search=${FINGERPRINT}" defaultOpenPGPURL = "https://openpgp.circl.lu/pks/lookup?op=get&search=${FINGERPRINT}" // Default OpenPGP URL.
defaultUploadLimit = 50 * 1024 * 1024 defaultUploadLimit = 50 * 1024 * 1024 // Default limit size of the uploaded file.
) )
// configs contains the config values for the provider.
type config struct { type config struct {
Password *string `toml:"password"` Password *string `toml:"password"`
Key string `toml:"key"` Key string `toml:"key"`
@ -56,6 +58,7 @@ const (
tlpRed tlp = "red" tlpRed tlp = "red"
) )
// valid returns true if the checked tlp matches one of the defined tlps.
func (t tlp) valid() bool { func (t tlp) valid() bool {
switch t { switch t {
case tlpCSAF, tlpWhite, tlpGreen, tlpAmber, tlpRed: case tlpCSAF, tlpWhite, tlpGreen, tlpAmber, tlpRed:
@ -73,6 +76,8 @@ func (t *tlp) UnmarshalText(text []byte) error {
return fmt.Errorf("invalid config TLP value: %v", string(text)) return fmt.Errorf("invalid config TLP value: %v", string(text))
} }
// uploadLimiter returns a reader that reads from a given r reader but stops
// with EOF after the defined bytes in the "UploadLimit" config option.
func (cfg *config) uploadLimiter(r io.Reader) io.Reader { func (cfg *config) uploadLimiter(r io.Reader) io.Reader {
// Zero or less means no upload limit. // Zero or less means no upload limit.
if cfg.UploadLimit == nil || *cfg.UploadLimit < 1 { if cfg.UploadLimit == nil || *cfg.UploadLimit < 1 {
@ -100,6 +105,8 @@ func (cfg *config) modelTLPs() []csaf.TLPLabel {
return tlps return tlps
} }
// loadCryptoKey loads the armored data into the key stored in the file specified by the
// "key" config value and return it with nil, otherwise an error.
func (cfg *config) loadCryptoKey() (*crypto.Key, error) { func (cfg *config) loadCryptoKey() (*crypto.Key, error) {
f, err := os.Open(cfg.Key) f, err := os.Open(cfg.Key)
if err != nil { if err != nil {
@ -109,11 +116,18 @@ func (cfg *config) loadCryptoKey() (*crypto.Key, error) {
return crypto.NewKeyFromArmoredReader(f) return crypto.NewKeyFromArmoredReader(f)
} }
// checkPassword compares the given hashed password with the plaintext in the "password" config value.
// It returns true if these matches or if the "password" config value is not set, otherwise false.
func (cfg *config) checkPassword(hash string) bool { func (cfg *config) checkPassword(hash string) bool {
return cfg.Password == nil || return cfg.Password == nil ||
bcrypt.CompareHashAndPassword([]byte(hash), []byte(*cfg.Password)) == nil bcrypt.CompareHashAndPassword([]byte(hash), []byte(*cfg.Password)) == nil
} }
// loadConfig extracts the config values from the config file. The path to the
// file is taken either from environment variable "CSAF_CONFIG" or from the
// defined default path in "defaultConfigPath".
// Default values are set in case some are missing in the file.
// It returns these values in a struct and nil if there is no error.
func loadConfig() (*config, error) { func loadConfig() (*config, error) {
path := os.Getenv(configEnv) path := os.Getenv(configEnv)
if path == "" { if path == "" {

View file

@ -37,11 +37,14 @@ func asMultiError(err error) multiError {
return multiError([]string{err.Error()}) return multiError([]string{err.Error()})
} }
// controller contains the config values and the html templates.
type controller struct { type controller struct {
cfg *config cfg *config
tmpl *template.Template tmpl *template.Template
} }
// newController assigns the given configs to a controller variable and parses the html template
// if the config value "NoWebUI" is true. It returns the controller variable and nil, otherwise error.
func newController(cfg *config) (*controller, error) { func newController(cfg *config) (*controller, error) {
c := controller{cfg: cfg} c := controller{cfg: cfg}
@ -56,6 +59,8 @@ func newController(cfg *config) (*controller, error) {
return &c, nil return &c, nil
} }
// bind binds the paths with the corresponding http.handler and wraps it with the respective middleware,
// according to the "NoWebUI" config value.
func (c *controller) bind(pim *pathInfoMux) { func (c *controller) bind(pim *pathInfoMux) {
if !c.cfg.NoWebUI { if !c.cfg.NoWebUI {
pim.handleFunc("/", c.auth(c.index)) pim.handleFunc("/", c.auth(c.index))
@ -66,6 +71,9 @@ func (c *controller) bind(pim *pathInfoMux) {
pim.handleFunc("/api/create", c.auth(api(c.create))) 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.
func (c *controller) auth( func (c *controller) auth(
fn func(http.ResponseWriter, *http.Request), fn func(http.ResponseWriter, *http.Request),
) func(http.ResponseWriter, *http.Request) { ) func(http.ResponseWriter, *http.Request) {
@ -83,6 +91,9 @@ func (c *controller) auth(
} }
} }
// render sets the headers for the response. It applies the given template "tmpl" to
// the given object "arg" and writes the output to http.ResponseWriter.
// It logs a warning in case of error.
func (c *controller) render(rw http.ResponseWriter, tmpl string, arg interface{}) { func (c *controller) render(rw http.ResponseWriter, tmpl string, arg interface{}) {
rw.Header().Set("Content-type", "text/html; charset=utf-8") rw.Header().Set("Content-type", "text/html; charset=utf-8")
rw.Header().Set("X-Content-Type-Options", "nosniff") rw.Header().Set("X-Content-Type-Options", "nosniff")
@ -91,17 +102,24 @@ func (c *controller) render(rw http.ResponseWriter, tmpl string, arg interface{}
} }
} }
// failed constructs the error messages by calling "asMultiError" and calls "render"
// function to render the passed template and error object.
func (c *controller) failed(rw http.ResponseWriter, tmpl string, err error) { func (c *controller) failed(rw http.ResponseWriter, tmpl string, err error) {
result := map[string]interface{}{"Error": asMultiError(err)} result := map[string]interface{}{"Error": asMultiError(err)}
c.render(rw, tmpl, result) c.render(rw, tmpl, result)
} }
// index calls the "render" function and passes the "index.html" and c.cfg to it.
func (c *controller) index(rw http.ResponseWriter, r *http.Request) { func (c *controller) index(rw http.ResponseWriter, r *http.Request) {
c.render(rw, "index.html", map[string]interface{}{ c.render(rw, "index.html", map[string]interface{}{
"Config": c.cfg, "Config": c.cfg,
}) })
} }
// web executes the given function "fn", calls the "render" function and passes
// the result content from "fn", the given template and the http.ResponseWriter to it
// in case of no error occurred, otherwise calls the "failed" function and passes the given
// template and the error from "fn".
func (c *controller) web( func (c *controller) web(
fn func(*http.Request) (interface{}, error), fn func(*http.Request) (interface{}, error),
tmpl string, tmpl string,
@ -116,6 +134,8 @@ func (c *controller) web(
} }
} }
// writeJSON sets the header for the response and writes the JSON encoding of the given "content".
// It logs out an error message in case of an error.
func writeJSON(rw http.ResponseWriter, content interface{}, code int) { func writeJSON(rw http.ResponseWriter, content interface{}, code int) {
rw.Header().Set("Content-type", "application/json; charset=utf-8") rw.Header().Set("Content-type", "application/json; charset=utf-8")
rw.Header().Set("X-Content-Type-Options", "nosniff") rw.Header().Set("X-Content-Type-Options", "nosniff")

View file

@ -18,6 +18,8 @@ import (
"github.com/csaf-poc/csaf_distribution/util" "github.com/csaf-poc/csaf_distribution/util"
) )
// ensureFolders initializes the paths and call functions to create
// the directories and files.
func ensureFolders(c *config) error { func ensureFolders(c *config) error {
wellknown := filepath.Join(c.Web, ".well-known") wellknown := filepath.Join(c.Web, ".well-known")
@ -38,6 +40,8 @@ func ensureFolders(c *config) error {
return createSecurity(c, wellknown) return createSecurity(c, wellknown)
} }
// createWellknown creates ".well-known" directory if not exist and returns nil.
// An error is returned if the it is not a directory.
func createWellknown(wellknown string) error { func createWellknown(wellknown string) error {
st, err := os.Stat(wellknown) st, err := os.Stat(wellknown)
if err != nil { if err != nil {
@ -52,6 +56,10 @@ func createWellknown(wellknown string) error {
return nil return nil
} }
// createFeedFolders creates the feed folders according to the tlp values
// in the "tlps" config option if they do not already exist.
// No creation for the "csaf" option will be done.
// It creates also symbolic links to feed folders.
func createFeedFolders(c *config, wellknown string) error { func createFeedFolders(c *config, wellknown string) error {
for _, t := range c.TLPs { for _, t := range c.TLPs {
if t == tlpCSAF { if t == tlpCSAF {
@ -75,6 +83,8 @@ func createFeedFolders(c *config, wellknown string) error {
return nil return nil
} }
// createSecurity creats the "security.txt" file if does not exist
// and writes the CSAF field inside the file.
func createSecurity(c *config, wellknown string) error { func createSecurity(c *config, wellknown string) error {
security := filepath.Join(wellknown, "security.txt") security := filepath.Join(wellknown, "security.txt")
if _, err := os.Stat(security); err != nil { if _, err := os.Stat(security); err != nil {
@ -93,6 +103,7 @@ func createSecurity(c *config, wellknown string) error {
return nil return nil
} }
// createProviderMetadata creates the provider-metadata.json file if does not exist.
func createProviderMetadata(c *config, wellknownCSAF string) error { func createProviderMetadata(c *config, wellknownCSAF string) error {
path := filepath.Join(wellknownCSAF, "provider-metadata.json") path := filepath.Join(wellknownCSAF, "provider-metadata.json")
_, err := os.Stat(path) _, err := os.Stat(path)

View file

@ -121,9 +121,15 @@ func (cs *compiledSchema) validate(doc interface{}) ([]string, error) {
res := make([]string, 0, len(errs)) res := make([]string, 0, len(errs))
for i := range errs { for i := range errs {
if e := &errs[i]; e.InstanceLocation != "" && e.Error != "" { e := &errs[i]
res = append(res, e.InstanceLocation+": "+e.Error) if e.Error == "" {
continue
} }
loc := e.InstanceLocation
if loc == "" {
loc = e.AbsoluteKeywordLocation
}
res = append(res, loc+": "+e.Error)
} }
return res, nil return res, nil

View file

@ -0,0 +1,72 @@
# Configure TLS Certificate for HTTPS
## Get a webserver TLS certificate
There are three ways to get a TLS certificate for your HTTPS server:
1. Get it from a certificate provider who will run a certificate
authority (CA) and also offers
[extended validation](https://en.wikipedia.org/wiki/Extended_Validation_Certificate) (EV)
for the certificate. This will cost a fee.
If possible, create the private key yourself,
then send a Certificate Signing Request (CSR).
Overall follow the documentation of the CA operator.
2. Get a domain validated TLS certificate via
[Let's encrypt](https://letsencrypt.org/) without a fee.
See their instruction, e.g.
[certbot for nignx on Ubuntu](https://certbot.eff.org/instructions?ws=nginx&os=ubuntufocal).
3. Run your own little CA. Which has the major drawback that someone
will have to import the root certificate in the webbrowsers manually.
Suitable for development purposes.
To decide between 1. and 2. you will need to weight the extra
efforts and costs of the level of extended validation against
a bit of extra trust for the security advisories
that will be served under the domain.
## Install the files for ngnix
Place the certificates on the server machine.
This includes the certificate for your webserver, the intermediate
certificates and the root certificate. The latter may already be on your
machine as part of the trust anchors for webbrowsers.
Follow the [nginx documentation](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)
to further configure TLS with your private key and the certificates.
We recommend to
* restrict the TLS protocol version and ciphers following a current
recommendation (e.g. [BSI-TR-02102-2](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TG02102/BSI-TR-02102-2.html)).
### Example configuration
Assuming the relevant server block is in `/etc/nginx/sites-enabled/default`,
change the `listen` configuration and add options so nginx
finds your your private key and the certificate chain.
```nginx
server {
listen 443 ssl http2 default_server; # ipv4
listen [::]:443 ssl http2 default_server; # ipv6
server_name www.example.com
ssl_certificate /etc/ssl/{domainName}.pem; # or bundle.crt
ssl_certificate_key /etc/ssl/{domainName}.key";
ssl_protocols TLSv1.2 TLSv1.3;
# Other Config
# ...
}
```
Replace `{domainName}` with the name for your certificate in the example.
Reload or restart nginx to apply the changes (e.g. `systemctl reload nginx`
on Debian or Ubuntu.)
Technical hints:
* When allowing or requiring `TLSv1.3` webbrowsers like
Chromium (seen with version 98) may have higher requirements
on the server certificates they allow,
otherwise they do not connect with `ERR_SSL_KEY_USAGE_INCOMPATIBLE`.

View file

@ -7,7 +7,7 @@ The following instructions are for an Debian 11 server setup.
```(shell) ```(shell)
apt-get install nginx fcgiwrap apt-get install nginx fcgiwrap
cp /usr/share/doc/fcgiwrap/examples/nginx.conf /etc/nginx/fcgiwrap.conf cp /usr/share/doc/fcgiwrap/examples/nginx.conf /etc/nginx/fcgiwrap.conf
systemctl status fcgiwrap.servic systemctl status fcgiwrap.service
systemctl status fcgiwrap.socket systemctl status fcgiwrap.socket
systemctl is-enabled fcgiwrap.service systemctl is-enabled fcgiwrap.service
systemctl is-enabled fcgiwrap.socket systemctl is-enabled fcgiwrap.socket