mirror of
https://github.com/gocsaf/csaf.git
synced 2025-12-22 11:55:40 +01:00
uploader: use the TOML config file infrastructure, too. (#439)
* Make uploader use the TOML config file intrastructure, too. * Improve method naming a bit. * Improve method naming a bit. * Add forgotten struct tags for TOML * Add version to command line only parameters in uploader documentation * Be explicit about supported options in config file. * allow interactive flags in config file. --------- Co-authored-by: JanHoefelmeyer <Jan Höfelmeyer jhoefelmeyer@intevation.de>
This commit is contained in:
parent
5c935901ab
commit
f2657bb51a
4 changed files with 507 additions and 458 deletions
190
cmd/csaf_uploader/config.go
Normal file
190
cmd/csaf_uploader/config.go
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
// 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: 2023 German Federal Office for Information Security (BSI) <https://www.bsi.bund.de>
|
||||
// Software-Engineering: 2023 Intevation GmbH <https://intevation.de>
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/csaf-poc/csaf_distribution/v2/internal/certs"
|
||||
"github.com/csaf-poc/csaf_distribution/v2/internal/options"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultURL = "https://localhost/cgi-bin/csaf_provider.go"
|
||||
defaultAction = "upload"
|
||||
defaultTLP = "csaf"
|
||||
)
|
||||
|
||||
// The supported flag config of the uploader command line
|
||||
type config struct {
|
||||
//lint:ignore SA5008 We are using choice twice: upload, create.
|
||||
Action string `short:"a" long:"action" choice:"upload" choice:"create" description:"Action to perform" toml:"action"`
|
||||
URL string `short:"u" long:"url" description:"URL of the CSAF provider" value-name:"URL" toml:"url"`
|
||||
//lint:ignore SA5008 We are using choice many times: csaf, white, green, amber, red.
|
||||
TLP string `short:"t" long:"tlp" choice:"csaf" choice:"white" choice:"green" choice:"amber" choice:"red" description:"TLP of the feed" toml:"tlp"`
|
||||
ExternalSigned bool `short:"x" long:"external-signed" description:"CSAF files are signed externally. Assumes .asc files beside CSAF files." toml:"external_signed"`
|
||||
NoSchemaCheck bool `short:"s" long:"no-schema-check" description:"Do not check files against CSAF JSON schema locally." toml:"no_schema_check"`
|
||||
|
||||
Key *string `short:"k" long:"key" description:"OpenPGP key to sign the CSAF files" value-name:"KEY-FILE" toml:"key"`
|
||||
Password *string `short:"p" long:"password" description:"Authentication password for accessing the CSAF provider" value-name:"PASSWORD" toml:"password"`
|
||||
Passphrase *string `short:"P" long:"passphrase" description:"Passphrase to unlock the OpenPGP key" value-name:"PASSPHRASE" toml:"passphrase"`
|
||||
ClientCert *string `long:"client-cert" description:"TLS client certificate file (PEM encoded data)" value-name:"CERT-FILE.crt" toml:"client_cert"`
|
||||
ClientKey *string `long:"client-key" description:"TLS client private key file (PEM encoded data)" value-name:"KEY-FILE.pem" toml:"client_key"`
|
||||
ClientPassphrase *string `long:"client-passphrase" description:"Optional passphrase for the client cert (limited, experimental, see downloader doc)" value-name:"PASSPHRASE" toml:"client_passphrase"`
|
||||
|
||||
PasswordInteractive bool `short:"i" long:"password-interactive" description:"Enter password interactively" toml:"password_interactive"`
|
||||
PassphraseInteractive bool `short:"I" long:"passphrase-interactive" description:"Enter OpenPGP key passphrase interactively" toml:"passphrase_interactive"`
|
||||
|
||||
Insecure bool `long:"insecure" description:"Do not check TLS certificates from provider" toml:"insecure"`
|
||||
|
||||
Config string `short:"c" long:"config" description:"Path to config TOML file" value-name:"TOML-FILE" toml:"-"`
|
||||
Version bool `long:"version" description:"Display version of the binary" toml:"-"`
|
||||
|
||||
clientCerts []tls.Certificate
|
||||
cachedAuth string
|
||||
keyRing *crypto.KeyRing
|
||||
}
|
||||
|
||||
// iniPaths are the potential file locations of the the config file.
|
||||
var configPaths = []string{
|
||||
"~/.config/csaf/uploader.toml",
|
||||
"~/.csaf_uploader.toml",
|
||||
"csaf_uploader.toml",
|
||||
}
|
||||
|
||||
// parseArgsConfig parses the command line and if need a config file.
|
||||
func parseArgsConfig() ([]string, *config, error) {
|
||||
p := options.Parser[config]{
|
||||
DefaultConfigLocations: configPaths,
|
||||
ConfigLocation: func(cfg *config) string { return cfg.Config },
|
||||
Usage: "[OPTIONS] advisories...",
|
||||
HasVersion: func(cfg *config) bool { return cfg.Version },
|
||||
SetDefaults: func(cfg *config) {
|
||||
cfg.URL = defaultURL
|
||||
cfg.Action = defaultAction
|
||||
cfg.TLP = defaultTLP
|
||||
},
|
||||
// Re-establish default values if not set.
|
||||
EnsureDefaults: func(cfg *config) {
|
||||
if cfg.URL == "" {
|
||||
cfg.URL = defaultURL
|
||||
}
|
||||
if cfg.Action == "" {
|
||||
cfg.Action = defaultAction
|
||||
}
|
||||
if cfg.TLP == "" {
|
||||
cfg.TLP = defaultTLP
|
||||
}
|
||||
},
|
||||
}
|
||||
return p.Parse()
|
||||
}
|
||||
|
||||
// prepareCertificates loads the client side certificates used by the HTTP client.
|
||||
func (cfg *config) prepareCertificates() error {
|
||||
cert, err := certs.LoadCertificate(
|
||||
cfg.ClientCert, cfg.ClientKey, cfg.ClientPassphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.clientCerts = cert
|
||||
return nil
|
||||
}
|
||||
|
||||
// readInteractive prints a message to command line and retrieves the password from it.
|
||||
func readInteractive(prompt string, pw **string) error {
|
||||
fmt.Print(prompt)
|
||||
p, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ps := string(p)
|
||||
*pw = &ps
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareInteractive prompts for interactive passwords.
|
||||
func (cfg *config) prepareInteractive() error {
|
||||
if cfg.PasswordInteractive {
|
||||
if err := readInteractive("Enter auth password: ", &cfg.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if cfg.PassphraseInteractive {
|
||||
if err := readInteractive("Enter OpenPGP passphrase: ", &cfg.Passphrase); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadOpenPGPKey loads an OpenPGP key.
|
||||
func loadOpenPGPKey(filename string) (*crypto.Key, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return crypto.NewKeyFromArmoredReader(f)
|
||||
}
|
||||
|
||||
// prepareOpenPGPKey loads the configured OpenPGP key.
|
||||
func (cfg *config) prepareOpenPGPKey() error {
|
||||
if cfg.Action != "upload" || cfg.Key == nil {
|
||||
return nil
|
||||
}
|
||||
if cfg.ExternalSigned {
|
||||
return errors.New("refused to sign external signed files")
|
||||
}
|
||||
key, err := loadOpenPGPKey(*cfg.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.Passphrase != nil {
|
||||
if key, err = key.Unlock([]byte(*cfg.Passphrase)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cfg.keyRing, err = crypto.NewKeyRing(key)
|
||||
return err
|
||||
}
|
||||
|
||||
// preparePassword pre-calculates the auth header.
|
||||
func (cfg *config) preparePassword() error {
|
||||
if cfg.Password != nil {
|
||||
hash, err := bcrypt.GenerateFromPassword(
|
||||
[]byte(*cfg.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.cachedAuth = string(hash)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepare prepares internal state of a loaded configuration.
|
||||
func (cfg *config) prepare() error {
|
||||
for _, prepare := range []func(*config) error{
|
||||
(*config).prepareCertificates,
|
||||
(*config).prepareInteractive,
|
||||
(*config).prepareOpenPGPKey,
|
||||
(*config).preparePassword,
|
||||
} {
|
||||
if err := prepare(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue