mirror of
https://github.com/gocsaf/csaf.git
synced 2025-12-22 11:55:40 +01:00
Merge branch 'main' into client-certificate
This commit is contained in:
commit
a71f490999
7 changed files with 236 additions and 24 deletions
57
Makefile
Normal file
57
Makefile
Normal 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.
|
||||||
17
README.md
17
README.md
|
|
@ -10,12 +10,19 @@
|
||||||
- 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
|
||||||
``` bash
|
Makefile supplies the following targets:
|
||||||
cd csaf_distribution
|
- Build For GNU/Linux System: `make build_linux`
|
||||||
go build -v ./cmd/...
|
- Build For Windows System (cross build): `make build_win`
|
||||||
```
|
- 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)
|
||||||
- To configure nginx for client certificate authentication see [docs/client-certificate-setup.md](docs/client-certificate-setup.md)
|
- To configure nginx for client certificate authentication see [docs/client-certificate-setup.md](docs/client-certificate-setup.md)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,12 @@ type reporter interface {
|
||||||
report(*processor, *Domain)
|
report(*processor, *Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errContinue = errors.New("continue")
|
var (
|
||||||
|
// errContinue indicates that the current check should continue.
|
||||||
|
errContinue = errors.New("continue")
|
||||||
|
// errStop indicates that the current check should stop.
|
||||||
|
errStop = errors.New("stop")
|
||||||
|
)
|
||||||
|
|
||||||
type whereType byte
|
type whereType byte
|
||||||
|
|
||||||
|
|
@ -102,8 +107,6 @@ func (wt whereType) String() string {
|
||||||
func newProcessor(opts *options) *processor {
|
func newProcessor(opts *options) *processor {
|
||||||
return &processor{
|
return &processor{
|
||||||
opts: opts,
|
opts: opts,
|
||||||
redirects: map[string]string{},
|
|
||||||
noneTLS: map[string]struct{}{},
|
|
||||||
alreadyChecked: map[string]whereType{},
|
alreadyChecked: map[string]whereType{},
|
||||||
builder: gval.Full(jsonpath.Language()),
|
builder: gval.Full(jsonpath.Language()),
|
||||||
exprs: map[string]gval.Evaluable{},
|
exprs: map[string]gval.Evaluable{},
|
||||||
|
|
@ -111,12 +114,8 @@ func newProcessor(opts *options) *processor {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) clean() {
|
func (p *processor) clean() {
|
||||||
for k := range p.redirects {
|
p.redirects = nil
|
||||||
delete(p.redirects, k)
|
p.noneTLS = nil
|
||||||
}
|
|
||||||
for k := range p.noneTLS {
|
|
||||||
delete(p.noneTLS, k)
|
|
||||||
}
|
|
||||||
for k := range p.alreadyChecked {
|
for k := range p.alreadyChecked {
|
||||||
delete(p.alreadyChecked, k)
|
delete(p.alreadyChecked, k)
|
||||||
}
|
}
|
||||||
|
|
@ -132,16 +131,14 @@ 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
|
||||||
|
|
||||||
domainsLoop:
|
|
||||||
for _, d := range domains {
|
for _, d := range domains {
|
||||||
if err := p.checkDomain(d); err != nil {
|
if err := p.checkDomain(d); err != nil {
|
||||||
if err == errContinue {
|
if err == errContinue || err == errStop {
|
||||||
continue domainsLoop
|
continue
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -167,6 +164,9 @@ func (p *processor) checkDomain(domain string) error {
|
||||||
(*processor).checkMissing,
|
(*processor).checkMissing,
|
||||||
} {
|
} {
|
||||||
if err := check(p, domain); err != nil && err != errContinue {
|
if err := check(p, domain); err != nil && err != errContinue {
|
||||||
|
if err == errStop {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -190,6 +190,9 @@ func (p *processor) jsonPath(expr string, doc interface{}) (interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) checkTLS(u string) {
|
func (p *processor) checkTLS(u string) {
|
||||||
|
if p.noneTLS == nil {
|
||||||
|
p.noneTLS = map[string]struct{}{}
|
||||||
|
}
|
||||||
if x, err := url.Parse(u); err == nil && x.Scheme != "https" {
|
if x, err := url.Parse(u); err == nil && x.Scheme != "https" {
|
||||||
p.noneTLS[u] = struct{}{}
|
p.noneTLS[u] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
@ -212,6 +215,9 @@ func (p *processor) checkRedirect(r *http.Request, via []*http.Request) error {
|
||||||
}
|
}
|
||||||
url := r.URL.String()
|
url := r.URL.String()
|
||||||
p.checkTLS(url)
|
p.checkTLS(url)
|
||||||
|
if p.redirects == nil {
|
||||||
|
p.redirects = map[string]string{}
|
||||||
|
}
|
||||||
p.redirects[url] = path.String()
|
p.redirects[url] = path.String()
|
||||||
|
|
||||||
if len(via) > 10 {
|
if len(via) > 10 {
|
||||||
|
|
@ -241,6 +247,16 @@ func (p *processor) httpClient() *http.Client {
|
||||||
return p.client
|
return p.client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func use(s *[]string) {
|
||||||
|
if *s == nil {
|
||||||
|
*s = []string{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func used(s []string) bool {
|
||||||
|
return s != nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *processor) badIntegrity(format string, args ...interface{}) {
|
func (p *processor) badIntegrity(format string, args ...interface{}) {
|
||||||
p.badIntegrities = append(p.badIntegrities, fmt.Sprintf(format, args...))
|
p.badIntegrities = append(p.badIntegrities, fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
@ -337,6 +353,8 @@ func (p *processor) integrity(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if file is in the right folder.
|
// Check if file is in the right folder.
|
||||||
|
use(&p.badFolders)
|
||||||
|
|
||||||
if date, err := p.jsonPath(
|
if date, err := p.jsonPath(
|
||||||
`$.document.tracking.initial_release_date`, doc); err != nil {
|
`$.document.tracking.initial_release_date`, doc); err != nil {
|
||||||
p.badFolder(
|
p.badFolder(
|
||||||
|
|
@ -353,6 +371,8 @@ func (p *processor) integrity(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check hashes
|
// Check hashes
|
||||||
|
use(&p.badIntegrities)
|
||||||
|
|
||||||
for _, x := range []struct {
|
for _, x := range []struct {
|
||||||
ext string
|
ext string
|
||||||
hash []byte
|
hash []byte
|
||||||
|
|
@ -393,6 +413,8 @@ func (p *processor) integrity(
|
||||||
sigFile := u + ".asc"
|
sigFile := u + ".asc"
|
||||||
p.checkTLS(sigFile)
|
p.checkTLS(sigFile)
|
||||||
|
|
||||||
|
use(&p.badSignatures)
|
||||||
|
|
||||||
if res, err = client.Get(sigFile); err != nil {
|
if res, err = client.Get(sigFile); err != nil {
|
||||||
p.badSignature("Fetching %s failed: %v.", sigFile, err)
|
p.badSignature("Fetching %s failed: %v.", sigFile, err)
|
||||||
continue
|
continue
|
||||||
|
|
@ -490,6 +512,9 @@ func (p *processor) checkIndex(base string, mask whereType) error {
|
||||||
client := p.httpClient()
|
client := p.httpClient()
|
||||||
index := base + "/index.txt"
|
index := base + "/index.txt"
|
||||||
p.checkTLS(index)
|
p.checkTLS(index)
|
||||||
|
|
||||||
|
use(&p.badIndices)
|
||||||
|
|
||||||
res, err := client.Get(index)
|
res, err := client.Get(index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.badIndex("Fetching %s failed: %v", index, err)
|
p.badIndex("Fetching %s failed: %v", index, err)
|
||||||
|
|
@ -526,6 +551,9 @@ func (p *processor) checkChanges(base string, mask whereType) error {
|
||||||
changes := base + "/changes.csv"
|
changes := base + "/changes.csv"
|
||||||
p.checkTLS(changes)
|
p.checkTLS(changes)
|
||||||
res, err := client.Get(changes)
|
res, err := client.Get(changes)
|
||||||
|
|
||||||
|
use(&p.badChanges)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.badChange("Fetching %s failed: %v", changes, err)
|
p.badChange("Fetching %s failed: %v", changes, err)
|
||||||
return errContinue
|
return errContinue
|
||||||
|
|
@ -679,16 +707,18 @@ func (p *processor) checkProviderMetadata(domain string) error {
|
||||||
|
|
||||||
url := "https://" + domain + "/.well-known/csaf/provider-metadata.json"
|
url := "https://" + domain + "/.well-known/csaf/provider-metadata.json"
|
||||||
|
|
||||||
|
use(&p.badProviderMetadatas)
|
||||||
|
|
||||||
res, err := client.Get(url)
|
res, err := client.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.badProviderMetadata("Fetching %s: %v.", url, err)
|
p.badProviderMetadata("Fetching %s: %v.", url, err)
|
||||||
return errContinue
|
return errStop
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
p.badProviderMetadata("Fetching %s failed. Status code: %d (%s)",
|
p.badProviderMetadata("Fetching %s failed. Status code: %d (%s)",
|
||||||
url, res.StatusCode, res.Status)
|
url, res.StatusCode, res.Status)
|
||||||
return errContinue
|
return errStop
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate checksum for later comparison.
|
// Calculate checksum for later comparison.
|
||||||
|
|
@ -700,7 +730,7 @@ func (p *processor) checkProviderMetadata(domain string) error {
|
||||||
return json.NewDecoder(tee).Decode(&p.pmd)
|
return json.NewDecoder(tee).Decode(&p.pmd)
|
||||||
}(); err != nil {
|
}(); err != nil {
|
||||||
p.badProviderMetadata("Decoding JSON failed: %v", err)
|
p.badProviderMetadata("Decoding JSON failed: %v", err)
|
||||||
return errContinue
|
return errStop
|
||||||
}
|
}
|
||||||
|
|
||||||
p.pmd256 = hash.Sum(nil)
|
p.pmd256 = hash.Sum(nil)
|
||||||
|
|
@ -722,6 +752,8 @@ func (p *processor) checkSecurity(domain string) error {
|
||||||
|
|
||||||
client := p.httpClient()
|
client := p.httpClient()
|
||||||
|
|
||||||
|
use(&p.badSecurities)
|
||||||
|
|
||||||
path := "https://" + domain + "/.well-known/security.txt"
|
path := "https://" + domain + "/.well-known/security.txt"
|
||||||
res, err := client.Get(path)
|
res, err := client.Get(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -796,6 +828,8 @@ func (p *processor) checkSecurity(domain string) error {
|
||||||
|
|
||||||
func (p *processor) checkPGPKeys(domain string) error {
|
func (p *processor) checkPGPKeys(domain string) error {
|
||||||
|
|
||||||
|
use(&p.badPGPs)
|
||||||
|
|
||||||
src, err := p.jsonPath("$.pgp_keys", p.pmd)
|
src, err := p.jsonPath("$.pgp_keys", p.pmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.badPGP("No PGP keys found: %v.", err)
|
p.badPGP("No PGP keys found: %v.", err)
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,10 @@ func (bc *baseReporter) requirement(domain *Domain) *Requirement {
|
||||||
|
|
||||||
func (r *tlsReporter) report(p *processor, domain *Domain) {
|
func (r *tlsReporter) report(p *processor, domain *Domain) {
|
||||||
req := r.requirement(domain)
|
req := r.requirement(domain)
|
||||||
|
if p.noneTLS == nil {
|
||||||
|
req.message("No TLS checks performed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
if len(p.noneTLS) == 0 {
|
if len(p.noneTLS) == 0 {
|
||||||
req.message("All tested URLs were https.")
|
req.message("All tested URLs were https.")
|
||||||
return
|
return
|
||||||
|
|
@ -82,6 +86,10 @@ func (r *redirectsReporter) report(p *processor, domain *Domain) {
|
||||||
|
|
||||||
func (r *providerMetadataReport) report(p *processor, domain *Domain) {
|
func (r *providerMetadataReport) report(p *processor, domain *Domain) {
|
||||||
req := r.requirement(domain)
|
req := r.requirement(domain)
|
||||||
|
if !used(p.badProviderMetadatas) {
|
||||||
|
req.message("No provider-metadata.json checked.")
|
||||||
|
return
|
||||||
|
}
|
||||||
if len(p.badProviderMetadatas) == 0 {
|
if len(p.badProviderMetadatas) == 0 {
|
||||||
req.message("No problems with provider metadata.")
|
req.message("No problems with provider metadata.")
|
||||||
return
|
return
|
||||||
|
|
@ -91,6 +99,10 @@ func (r *providerMetadataReport) report(p *processor, domain *Domain) {
|
||||||
|
|
||||||
func (r *securityReporter) report(p *processor, domain *Domain) {
|
func (r *securityReporter) report(p *processor, domain *Domain) {
|
||||||
req := r.requirement(domain)
|
req := r.requirement(domain)
|
||||||
|
if !used(p.badSecurities) {
|
||||||
|
req.message("No security.txt checked.")
|
||||||
|
return
|
||||||
|
}
|
||||||
if len(p.badSecurities) == 0 {
|
if len(p.badSecurities) == 0 {
|
||||||
req.message("No problems with security.txt found.")
|
req.message("No problems with security.txt found.")
|
||||||
return
|
return
|
||||||
|
|
@ -112,6 +124,10 @@ func (r *dnsPathReporter) report(_ *processor, domain *Domain) {
|
||||||
|
|
||||||
func (r *oneFolderPerYearReport) report(p *processor, domain *Domain) {
|
func (r *oneFolderPerYearReport) report(p *processor, domain *Domain) {
|
||||||
req := r.requirement(domain)
|
req := r.requirement(domain)
|
||||||
|
if !used(p.badFolders) {
|
||||||
|
req.message("No checks if file are in right folders were performed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
if len(p.badFolders) == 0 {
|
if len(p.badFolders) == 0 {
|
||||||
req.message("All CSAF files are in the right folders.")
|
req.message("All CSAF files are in the right folders.")
|
||||||
return
|
return
|
||||||
|
|
@ -121,6 +137,10 @@ func (r *oneFolderPerYearReport) report(p *processor, domain *Domain) {
|
||||||
|
|
||||||
func (r *indexReporter) report(p *processor, domain *Domain) {
|
func (r *indexReporter) report(p *processor, domain *Domain) {
|
||||||
req := r.requirement(domain)
|
req := r.requirement(domain)
|
||||||
|
if !used(p.badIndices) {
|
||||||
|
req.message("No index.txt checked.")
|
||||||
|
return
|
||||||
|
}
|
||||||
if len(p.badIndices) == 0 {
|
if len(p.badIndices) == 0 {
|
||||||
req.message("No problems with index.txt found.")
|
req.message("No problems with index.txt found.")
|
||||||
return
|
return
|
||||||
|
|
@ -130,6 +150,10 @@ func (r *indexReporter) report(p *processor, domain *Domain) {
|
||||||
|
|
||||||
func (r *changesReporter) report(p *processor, domain *Domain) {
|
func (r *changesReporter) report(p *processor, domain *Domain) {
|
||||||
req := r.requirement(domain)
|
req := r.requirement(domain)
|
||||||
|
if !used(p.badChanges) {
|
||||||
|
req.message("No changes.csv checked.")
|
||||||
|
return
|
||||||
|
}
|
||||||
if len(p.badChanges) == 0 {
|
if len(p.badChanges) == 0 {
|
||||||
req.message("No problems with changes.csv found.")
|
req.message("No problems with changes.csv found.")
|
||||||
return
|
return
|
||||||
|
|
@ -145,6 +169,10 @@ func (r *directoryListingsReporter) report(_ *processor, domain *Domain) {
|
||||||
|
|
||||||
func (r *integrityReporter) report(p *processor, domain *Domain) {
|
func (r *integrityReporter) report(p *processor, domain *Domain) {
|
||||||
req := r.requirement(domain)
|
req := r.requirement(domain)
|
||||||
|
if !used(p.badIntegrities) {
|
||||||
|
req.message("No checksums checked.")
|
||||||
|
return
|
||||||
|
}
|
||||||
if len(p.badIntegrities) == 0 {
|
if len(p.badIntegrities) == 0 {
|
||||||
req.message("All checksums match.")
|
req.message("All checksums match.")
|
||||||
return
|
return
|
||||||
|
|
@ -154,6 +182,10 @@ func (r *integrityReporter) report(p *processor, domain *Domain) {
|
||||||
|
|
||||||
func (r *signaturesReporter) report(p *processor, domain *Domain) {
|
func (r *signaturesReporter) report(p *processor, domain *Domain) {
|
||||||
req := r.requirement(domain)
|
req := r.requirement(domain)
|
||||||
|
if !used(p.badSignatures) {
|
||||||
|
req.message("No signatures checked.")
|
||||||
|
return
|
||||||
|
}
|
||||||
req.Messages = p.badSignatures
|
req.Messages = p.badSignatures
|
||||||
if len(p.badSignatures) == 0 {
|
if len(p.badSignatures) == 0 {
|
||||||
req.message("All signatures verified.")
|
req.message("All signatures verified.")
|
||||||
|
|
@ -162,6 +194,10 @@ func (r *signaturesReporter) report(p *processor, domain *Domain) {
|
||||||
|
|
||||||
func (r *publicPGPKeyReporter) report(p *processor, domain *Domain) {
|
func (r *publicPGPKeyReporter) report(p *processor, domain *Domain) {
|
||||||
req := r.requirement(domain)
|
req := r.requirement(domain)
|
||||||
|
if !used(p.badPGPs) {
|
||||||
|
req.message("No PGP keys loaded.")
|
||||||
|
return
|
||||||
|
}
|
||||||
req.Messages = p.badPGPs
|
req.Messages = p.badPGPs
|
||||||
if len(p.keys) > 0 {
|
if len(p.keys) > 0 {
|
||||||
req.message(fmt.Sprintf("%d PGP key(s) loaded successfully.", len(p.keys)))
|
req.message(fmt.Sprintf("%d PGP key(s) loaded successfully.", len(p.keys)))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
72
docs/install-server-certificate.md
Normal file
72
docs/install-server-certificate.md
Normal 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`.
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue