1
0
Fork 0
mirror of https://github.com/gocsaf/csaf.git synced 2025-12-22 18:15:42 +01:00
gocsaf/cmd/csaf_uploader/config.go
JanHoefelmeyer 086c4ab48b
Convert a lot of command line arguments to snake case (#498)
* Convert a lot of variables to snake case

* Add snakecase for variables made out of two words that had it in no version yet (for consistency)

* Adjust example files too

---------

Co-authored-by: JanHoefelmeyer <hoefelmeyer.jan@gmail.com>
2023-11-06 14:33:05 +01:00

191 lines
6.5 KiB
Go

// 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"
"golang.org/x/crypto/bcrypt"
"golang.org/x/term"
"github.com/csaf-poc/csaf_distribution/v3/internal/certs"
"github.com/csaf-poc/csaf_distribution/v3/internal/options"
)
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
}