mirror of
https://github.com/gocsaf/csaf.git
synced 2025-12-22 11:55:40 +01:00
commit
b21cef4677
8 changed files with 319 additions and 203 deletions
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
|
"github.com/csaf-poc/csaf_distribution/csaf"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -13,7 +15,7 @@ const (
|
||||||
defaultConfigPath = "/usr/lib/casf/config.toml"
|
defaultConfigPath = "/usr/lib/casf/config.toml"
|
||||||
defaultFolder = "/var/www/"
|
defaultFolder = "/var/www/"
|
||||||
defaultWeb = "/var/www/html"
|
defaultWeb = "/var/www/html"
|
||||||
defaultPGPURL = "http://pgp.mit.edu/pks/lookup?search=${KEY}&op=index"
|
defaultOpenPGPURL = "https://openpgp.circl.lu/pks/lookup?search=${KEY}&op=index"
|
||||||
)
|
)
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
|
|
@ -22,9 +24,11 @@ type config struct {
|
||||||
Web string `toml:"web"`
|
Web string `toml:"web"`
|
||||||
TLPs []tlp `toml:"tlps"`
|
TLPs []tlp `toml:"tlps"`
|
||||||
UploadSignature bool `toml:"upload_signature"`
|
UploadSignature bool `toml:"upload_signature"`
|
||||||
PGPURL string `toml:"pgp_url"`
|
OpenPGPURL string `toml:"openpgp_url"`
|
||||||
Domain string `toml:"domain"`
|
Domain string `toml:"domain"`
|
||||||
NoPassphrase bool `toml:"no_passphrase"`
|
NoPassphrase bool `toml:"no_passphrase"`
|
||||||
|
DynamicProviderMetaData bool `toml:"dynamic_provider_metadata"`
|
||||||
|
Publisher *csaf.Publisher `toml:"publisher"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type tlp string
|
type tlp string
|
||||||
|
|
@ -54,8 +58,27 @@ 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))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *config) GetPGPURL(key string) string {
|
func (cfg *config) GetOpenPGPURL(key string) string {
|
||||||
return strings.ReplaceAll(cfg.PGPURL, "${KEY}", key)
|
return strings.ReplaceAll(cfg.OpenPGPURL, "${KEY}", "0x"+key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *config) modelTLPs() []csaf.TLPLabel {
|
||||||
|
tlps := make([]csaf.TLPLabel, 0, len(cfg.TLPs))
|
||||||
|
for _, t := range cfg.TLPs {
|
||||||
|
if t != tlpCSAF {
|
||||||
|
tlps = append(tlps, csaf.TLPLabel(strings.ToUpper(string(t))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tlps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *config) loadCryptoKey() (*crypto.Key, error) {
|
||||||
|
f, err := os.Open(cfg.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return crypto.NewKeyFromArmoredReader(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfig() (*config, error) {
|
func loadConfig() (*config, error) {
|
||||||
|
|
@ -86,8 +109,16 @@ func loadConfig() (*config, error) {
|
||||||
cfg.TLPs = []tlp{tlpCSAF, tlpWhite, tlpGreen, tlpAmber, tlpRed}
|
cfg.TLPs = []tlp{tlpCSAF, tlpWhite, tlpGreen, tlpAmber, tlpRed}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.PGPURL == "" {
|
if cfg.OpenPGPURL == "" {
|
||||||
cfg.PGPURL = defaultPGPURL
|
cfg.OpenPGPURL = defaultOpenPGPURL
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Publisher == nil {
|
||||||
|
cfg.Publisher = &csaf.Publisher{
|
||||||
|
Category: func(c csaf.Category) *csaf.Category { return &c }(csaf.CSAFCategoryVendor),
|
||||||
|
Name: func(s string) *string { return &s }("ACME"),
|
||||||
|
Namespace: func(s string) *string { return &s }("https://example.com"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
|
|
|
||||||
|
|
@ -112,74 +112,66 @@ func loadCSAF(r *http.Request) (string, []byte, error) {
|
||||||
return cleanFileName(handler.Filename), buf.Bytes(), nil
|
return cleanFileName(handler.Filename), buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) loadCryptoKey() (*crypto.Key, error) {
|
func (c *controller) handleSignature(
|
||||||
f, err := os.Open(c.cfg.Key)
|
r *http.Request,
|
||||||
if err != nil {
|
data []byte,
|
||||||
return nil, err
|
) (string, *crypto.Key, error) {
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
return crypto.NewKeyFromArmoredReader(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *controller) handleSignature(r *http.Request, data []byte) (string, string, error) {
|
|
||||||
|
|
||||||
// Either way ... we need the key.
|
// Either way ... we need the key.
|
||||||
key, err := c.loadCryptoKey()
|
key, err := c.cfg.loadCryptoKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fingerprint := key.GetFingerprint()
|
|
||||||
|
|
||||||
// Was the signature given via request?
|
// Was the signature given via request?
|
||||||
if c.cfg.UploadSignature {
|
if c.cfg.UploadSignature {
|
||||||
sigText := r.FormValue("signature")
|
sigText := r.FormValue("signature")
|
||||||
if sigText == "" {
|
if sigText == "" {
|
||||||
return "", "", errors.New("missing signature in request")
|
return "", nil, errors.New("missing signature in request")
|
||||||
}
|
}
|
||||||
|
|
||||||
pgpSig, err := crypto.NewPGPSignatureFromArmored(sigText)
|
pgpSig, err := crypto.NewPGPSignatureFromArmored(sigText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use as public key
|
// Use as public key
|
||||||
signRing, err := crypto.NewKeyRing(key)
|
signRing, err := crypto.NewKeyRing(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := signRing.VerifyDetached(
|
if err := signRing.VerifyDetached(
|
||||||
crypto.NewPlainMessage(data),
|
crypto.NewPlainMessage(data),
|
||||||
pgpSig, crypto.GetUnixTime(),
|
pgpSig, crypto.GetUnixTime(),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return "", "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return sigText, fingerprint, nil
|
return sigText, key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign ourself
|
// Sign ourself
|
||||||
|
|
||||||
if passwd := r.FormValue("passphrase"); !c.cfg.NoPassphrase && passwd != "" {
|
if passwd := r.FormValue("passphrase"); !c.cfg.NoPassphrase && passwd != "" {
|
||||||
if key, err = key.Unlock([]byte(passwd)); err != nil {
|
if key, err = key.Unlock([]byte(passwd)); err != nil {
|
||||||
return "", "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use as private key
|
// Use as private key
|
||||||
signRing, err := crypto.NewKeyRing(key)
|
signRing, err := crypto.NewKeyRing(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sig, err := signRing.SignDetached(crypto.NewPlainMessage(data))
|
sig, err := signRing.SignDetached(crypto.NewPlainMessage(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
armored, err := sig.GetArmored()
|
armored, err := sig.GetArmored()
|
||||||
return armored, fingerprint, err
|
return armored, key, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *controller) upload(rw http.ResponseWriter, r *http.Request) {
|
func (c *controller) upload(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -217,12 +209,15 @@ func (c *controller) upload(rw http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
armored, fingerprint, err := c.handleSignature(r, data)
|
armored, key, err := c.handleSignature(r, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.failed(rw, "upload.html", err)
|
c.failed(rw, "upload.html", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var warnings []string
|
||||||
|
warn := func(msg string) { warnings = append(warnings, msg) }
|
||||||
|
|
||||||
if err := doTransaction(
|
if err := doTransaction(
|
||||||
c.cfg, t,
|
c.cfg, t,
|
||||||
func(folder string, pmd *csaf.ProviderMetadata) error {
|
func(folder string, pmd *csaf.ProviderMetadata) error {
|
||||||
|
|
@ -336,13 +331,23 @@ func (c *controller) upload(rw http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take over publisher
|
// Take over publisher
|
||||||
// TODO: Check for conflicts.
|
switch {
|
||||||
|
case pmd.Publisher == nil:
|
||||||
|
warn("Publisher in provider metadata is not initialized. Forgot to configure?")
|
||||||
|
if c.cfg.DynamicProviderMetaData {
|
||||||
|
warn("Taking publisher from CSAF")
|
||||||
pmd.Publisher = ex.publisher
|
pmd.Publisher = ex.publisher
|
||||||
|
}
|
||||||
|
case !pmd.Publisher.Equals(ex.publisher):
|
||||||
|
warn("Publishers in provider metadata and CSAF do not match.")
|
||||||
|
}
|
||||||
|
|
||||||
pmd.SetPGP(fingerprint, c.cfg.GetPGPURL(fingerprint))
|
keyID, fingerprint := key.GetHexKeyID(), key.GetFingerprint()
|
||||||
|
pmd.SetPGP(fingerprint, c.cfg.GetOpenPGPURL(keyID))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
},
|
||||||
|
); err != nil {
|
||||||
c.failed(rw, "upload.html", err)
|
c.failed(rw, "upload.html", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -350,6 +355,7 @@ func (c *controller) upload(rw http.ResponseWriter, r *http.Request) {
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
"Name": newCSAF,
|
"Name": newCSAF,
|
||||||
"ReleaseDate": ex.currentReleaseDate.Format(dateFormat),
|
"ReleaseDate": ex.currentReleaseDate.Format(dateFormat),
|
||||||
|
"Warnings": warnings,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.render(rw, "upload.html", result)
|
c.render(rw, "upload.html", result)
|
||||||
|
|
|
||||||
108
cmd/csaf_provider/create.go
Normal file
108
cmd/csaf_provider/create.go
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/csaf-poc/csaf_distribution/csaf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ensureFolders(c *config) error {
|
||||||
|
|
||||||
|
wellknown := filepath.Join(c.Web, ".well-known")
|
||||||
|
wellknownCSAF := filepath.Join(wellknown, "csaf")
|
||||||
|
|
||||||
|
if err := createWellknown(wellknownCSAF); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := createFeedFolders(c, wellknownCSAF); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := createProviderMetadata(c, wellknownCSAF); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return createSecurity(c, wellknown)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createWellknown(wellknown string) error {
|
||||||
|
st, err := os.Stat(wellknown)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return os.MkdirAll(wellknown, 0755)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !st.IsDir() {
|
||||||
|
return errors.New(".well-known/csaf is not a directory")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFeedFolders(c *config, wellknown string) error {
|
||||||
|
for _, t := range c.TLPs {
|
||||||
|
if t == tlpCSAF {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tlpLink := filepath.Join(wellknown, string(t))
|
||||||
|
if _, err := filepath.EvalSymlinks(tlpLink); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
tlpFolder := filepath.Join(c.Folder, string(t))
|
||||||
|
if tlpFolder, err = mkUniqDir(tlpFolder); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = os.Symlink(tlpFolder, tlpLink); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSecurity(c *config, wellknown string) error {
|
||||||
|
security := filepath.Join(wellknown, "security.txt")
|
||||||
|
if _, err := os.Stat(security); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
f, err := os.Create(security)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(
|
||||||
|
f, "CSAF: %s/.well-known/csaf/provider-metadata.json\n",
|
||||||
|
c.Domain)
|
||||||
|
return f.Close()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createProviderMetadata(c *config, wellknownCSAF string) error {
|
||||||
|
path := filepath.Join(wellknownCSAF, "provider-metadata.json")
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pm := csaf.NewProviderMetadataDomain(c.Domain, c.modelTLPs())
|
||||||
|
pm.Publisher = c.Publisher
|
||||||
|
|
||||||
|
// Set OpenPGP key.
|
||||||
|
key, err := c.loadCryptoKey()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keyID, fingerprint := key.GetHexKeyID(), key.GetFingerprint()
|
||||||
|
pm.SetPGP(fingerprint, c.GetOpenPGPURL(keyID))
|
||||||
|
|
||||||
|
return saveToFile(path, pm)
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -16,81 +15,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ensureFolders(c *config) error {
|
|
||||||
|
|
||||||
wellknown, err := createWellknown(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := createFeedFolders(c, wellknown); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return createSecurity(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createSecurity(c *config) error {
|
|
||||||
security := filepath.Join(c.Web, "security.txt")
|
|
||||||
if _, err := os.Stat(security); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
f, err := os.Create(security)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(
|
|
||||||
f, "CSAF: %s/.well-known/csaf/provider-metadata.json\n",
|
|
||||||
c.Domain)
|
|
||||||
return f.Close()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createFeedFolders(c *config, wellknown string) error {
|
|
||||||
for _, t := range c.TLPs {
|
|
||||||
if t == tlpCSAF {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tlpLink := filepath.Join(wellknown, string(t))
|
|
||||||
if _, err := filepath.EvalSymlinks(tlpLink); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
tlpFolder := filepath.Join(c.Folder, string(t))
|
|
||||||
if tlpFolder, err = mkUniqDir(tlpFolder); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = os.Symlink(tlpFolder, tlpLink); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createWellknown(c *config) (string, error) {
|
|
||||||
wellknown := filepath.Join(c.Web, ".well-known", "csaf")
|
|
||||||
|
|
||||||
st, err := os.Stat(wellknown)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(wellknown, 0755); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !st.IsDir() {
|
|
||||||
return "", errors.New(".well-known/csaf is not a directory")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return wellknown, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func deepCopy(dst, src string) error {
|
func deepCopy(dst, src string) error {
|
||||||
|
|
||||||
stack := []string{dst, src}
|
stack := []string{dst, src}
|
||||||
|
|
@ -214,16 +138,12 @@ func writeHashedFile(fname, name string, data []byte, armored string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type saver interface {
|
func saveToFile(fname string, wt io.WriterTo) error {
|
||||||
Save(io.Writer) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveToFile(fname string, s saver) error {
|
|
||||||
f, err1 := os.Create(fname)
|
f, err1 := os.Create(fname)
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
err1 = s.Save(f)
|
_, err1 = wt.WriteTo(f)
|
||||||
err2 := f.Close()
|
err2 := f.Close()
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
return err1
|
return err1
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,16 @@
|
||||||
<tr><td>CSAF file:</td><td><tt>{{ .Name }}</tt></td></tr>
|
<tr><td>CSAF file:</td><td><tt>{{ .Name }}</tt></td></tr>
|
||||||
<tr><td>Release date:</td><td><tt>{{ .ReleaseDate }}</tt></td></tr>
|
<tr><td>Release date:</td><td><tt>{{ .ReleaseDate }}</tt></td></tr>
|
||||||
</table>
|
</table>
|
||||||
|
{{ if .Warnings }}
|
||||||
|
<p>
|
||||||
|
Warning(s):
|
||||||
|
<ul>
|
||||||
|
{{ range .Warnings }}
|
||||||
|
<li>{{ . }}</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<br>
|
<br>
|
||||||
<a href="/cgi-bin/csaf_provider.go/">Back</a>:
|
<a href="/cgi-bin/csaf_provider.go/">Back</a>:
|
||||||
|
|
|
||||||
|
|
@ -3,49 +3,10 @@ package main
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/csaf-poc/csaf_distribution/csaf"
|
"github.com/csaf-poc/csaf_distribution/csaf"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newProviderMetadata(cfg *config) *csaf.ProviderMetadata {
|
|
||||||
|
|
||||||
pmd := csaf.NewProviderMetadata(
|
|
||||||
cfg.Domain + "/.wellknown/csaf/provider-metadata.json")
|
|
||||||
|
|
||||||
// Register feeds.
|
|
||||||
|
|
||||||
var feeds []csaf.Feed
|
|
||||||
|
|
||||||
for _, t := range cfg.TLPs {
|
|
||||||
if t == tlpCSAF {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
ts = string(t)
|
|
||||||
feedName = "csaf-feed-tlp-" + ts + ".json"
|
|
||||||
feedURL = csaf.JSONURL(
|
|
||||||
cfg.Domain + "/.well-known/csaf/" + ts + "/" + feedName)
|
|
||||||
tlpLabel = csaf.TLPLabel(strings.ToUpper(ts))
|
|
||||||
)
|
|
||||||
feeds = append(feeds, csaf.Feed{
|
|
||||||
Summary: "TLP:" + string(tlpLabel) + " advisories",
|
|
||||||
TLPLabel: &tlpLabel,
|
|
||||||
URL: &feedURL,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(feeds) > 0 {
|
|
||||||
pmd.Distributions = []csaf.Distribution{{
|
|
||||||
Rolie: []csaf.ROLIE{{
|
|
||||||
Feeds: feeds,
|
|
||||||
}},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTransaction(
|
func doTransaction(
|
||||||
cfg *config,
|
cfg *config,
|
||||||
t tlp,
|
t tlp,
|
||||||
|
|
@ -60,7 +21,7 @@ func doTransaction(
|
||||||
f, err := os.Open(metadata)
|
f, err := os.Open(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return newProviderMetadata(cfg), nil
|
return csaf.NewProviderMetadataDomain(cfg.Domain, cfg.modelTLPs()), nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -98,14 +59,15 @@ func doTransaction(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write back provider metadata.
|
// Write back provider metadata if its dynamic.
|
||||||
|
if cfg.DynamicProviderMetaData {
|
||||||
newMetaName, newMetaFile, err := mkUniqFile(metadata)
|
newMetaName, newMetaFile, err := mkUniqFile(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.RemoveAll(newDir)
|
os.RemoveAll(newDir)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pmd.Save(newMetaFile); err != nil {
|
if _, err := pmd.WriteTo(newMetaFile); err != nil {
|
||||||
newMetaFile.Close()
|
newMetaFile.Close()
|
||||||
os.Remove(newMetaName)
|
os.Remove(newMetaName)
|
||||||
os.RemoveAll(newDir)
|
os.RemoveAll(newDir)
|
||||||
|
|
@ -122,6 +84,7 @@ func doTransaction(
|
||||||
os.RemoveAll(newDir)
|
os.RemoveAll(newDir)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Switch directories.
|
// Switch directories.
|
||||||
symlink := filepath.Join(newDir, string(t))
|
symlink := filepath.Join(newDir, string(t))
|
||||||
|
|
|
||||||
106
csaf/models.go
106
csaf/models.go
|
|
@ -102,11 +102,11 @@ var csafCategoryPattern = alternativesUnmarshal(
|
||||||
|
|
||||||
// Publisher is the publisher of the feed.
|
// Publisher is the publisher of the feed.
|
||||||
type Publisher struct {
|
type Publisher struct {
|
||||||
Category *Category `json:"category"` // required
|
Category *Category `json:"category" toml:"category"` // required
|
||||||
Name *string `json:"name"` // required
|
Name *string `json:"name" toml:"name"` // required
|
||||||
Namespace *string `json:"namespace"` // required
|
Namespace *string `json:"namespace" toml:"namespace"` // required
|
||||||
ContactDetails string `json:"contact_details,omitempty"`
|
ContactDetails string `json:"contact_details,omitempty" toml:"contact_details"`
|
||||||
IssuingAuthority string `json:"issuing_authority,omitempty"`
|
IssuingAuthority string `json:"issuing_authority,omitempty" toml:"issuing_authority"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MetadataVersion is the metadata version of the feed.
|
// MetadataVersion is the metadata version of the feed.
|
||||||
|
|
@ -148,7 +148,7 @@ type ProviderMetadata struct {
|
||||||
MetadataVersion *MetadataVersion `json:"metadata_version"` // required
|
MetadataVersion *MetadataVersion `json:"metadata_version"` // required
|
||||||
MirrorOnCSAFAggregators *bool `json:"mirror_on_CSAF_aggregators"` // required
|
MirrorOnCSAFAggregators *bool `json:"mirror_on_CSAF_aggregators"` // required
|
||||||
PGPKeys []PGPKey `json:"pgp_keys,omitempty"`
|
PGPKeys []PGPKey `json:"pgp_keys,omitempty"`
|
||||||
Publisher *Publisher `json:"publisher"` // required
|
Publisher *Publisher `json:"publisher,omitempty"` // required
|
||||||
Role *MetadataRole `json:"role"` // required
|
Role *MetadataRole `json:"role"` // required
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,20 +283,47 @@ func (r *ROLIE) Validate() error {
|
||||||
|
|
||||||
// Validate checks if the publisher is valid.
|
// Validate checks if the publisher is valid.
|
||||||
// Returns an error if the validation fails otherwise nil.
|
// Returns an error if the validation fails otherwise nil.
|
||||||
func (cp *Publisher) Validate() error {
|
func (p *Publisher) Validate() error {
|
||||||
switch {
|
switch {
|
||||||
case cp == nil:
|
case p == nil:
|
||||||
return errors.New("publisher is mandatory")
|
return errors.New("publisher is mandatory")
|
||||||
case cp.Category == nil:
|
case p.Category == nil:
|
||||||
return errors.New("publisher.category is mandatory")
|
return errors.New("publisher.category is mandatory")
|
||||||
case cp.Name == nil:
|
case p.Name == nil:
|
||||||
return errors.New("publisher.name is mandatory")
|
return errors.New("publisher.name is mandatory")
|
||||||
case cp.Namespace == nil:
|
case p.Namespace == nil:
|
||||||
return errors.New("publisher.namespace is mandatory")
|
return errors.New("publisher.namespace is mandatory")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func strPtrEquals(a, b *string) bool {
|
||||||
|
switch {
|
||||||
|
case a == nil:
|
||||||
|
return b == nil
|
||||||
|
case b == nil:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return *a == *b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equals checks if the publisher is equal to other componentwise.
|
||||||
|
func (p *Publisher) Equals(o *Publisher) bool {
|
||||||
|
switch {
|
||||||
|
case p == nil:
|
||||||
|
return o == nil
|
||||||
|
case o == nil:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return strPtrEquals((*string)(p.Category), (*string)(o.Category)) &&
|
||||||
|
strPtrEquals(p.Name, o.Name) &&
|
||||||
|
strPtrEquals(p.Namespace, o.Namespace) &&
|
||||||
|
p.ContactDetails == o.ContactDetails &&
|
||||||
|
p.IssuingAuthority == o.IssuingAuthority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate checks if the PGPKey is valid.
|
// Validate checks if the PGPKey is valid.
|
||||||
// Returns an error if the validation fails otherwise nil.
|
// Returns an error if the validation fails otherwise nil.
|
||||||
func (pk *PGPKey) Validate() error {
|
func (pk *PGPKey) Validate() error {
|
||||||
|
|
@ -384,11 +411,60 @@ func NewProviderMetadata(canonicalURL string) *ProviderMetadata {
|
||||||
return pm
|
return pm
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save saves a metadata provider to a writer.
|
// NewProviderMetadataDomain creates a new provider with the given URL
|
||||||
func (pmd *ProviderMetadata) Save(w io.Writer) error {
|
// and tlps feeds.
|
||||||
enc := json.NewEncoder(w)
|
func NewProviderMetadataDomain(domain string, tlps []TLPLabel) *ProviderMetadata {
|
||||||
|
|
||||||
|
pm := NewProviderMetadata(
|
||||||
|
domain + "/.well-known/csaf/provider-metadata.json")
|
||||||
|
|
||||||
|
if len(tlps) == 0 {
|
||||||
|
return pm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register feeds.
|
||||||
|
|
||||||
|
feeds := make([]Feed, len(tlps))
|
||||||
|
|
||||||
|
for i, t := range tlps {
|
||||||
|
lt := strings.ToLower(string(t))
|
||||||
|
feed := "csaf-feed-tlp-" + lt + ".json"
|
||||||
|
url := JSONURL(domain + "/.well-known/csaf/" + lt + "/" + feed)
|
||||||
|
|
||||||
|
feeds[i] = Feed{
|
||||||
|
Summary: "TLP:" + string(t) + " advisories",
|
||||||
|
TLPLabel: &t,
|
||||||
|
URL: &url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pm.Distributions = []Distribution{{
|
||||||
|
Rolie: []ROLIE{{
|
||||||
|
Feeds: feeds,
|
||||||
|
}},
|
||||||
|
}}
|
||||||
|
|
||||||
|
return pm
|
||||||
|
}
|
||||||
|
|
||||||
|
type nWriter struct {
|
||||||
|
io.Writer
|
||||||
|
n int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nw *nWriter) Write(p []byte) (int, error) {
|
||||||
|
n, err := nw.Writer.Write(p)
|
||||||
|
nw.n += int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo saves a metadata provider to a writer.
|
||||||
|
func (pmd *ProviderMetadata) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
nw := nWriter{w, 0}
|
||||||
|
enc := json.NewEncoder(&nw)
|
||||||
enc.SetIndent("", " ")
|
enc.SetIndent("", " ")
|
||||||
return enc.Encode(pmd)
|
err := enc.Encode(pmd)
|
||||||
|
return nw.n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadProviderMetadata loads a metadata provider from a reader.
|
// LoadProviderMetadata loads a metadata provider from a reader.
|
||||||
|
|
|
||||||
|
|
@ -68,11 +68,13 @@ func LoadROLIEFeed(r io.Reader) (*ROLIEFeed, error) {
|
||||||
return &rf, nil
|
return &rf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save saves a ROLIE feed to a writer.
|
// WriteTo saves a ROLIE feed to a writer.
|
||||||
func (rf *ROLIEFeed) Save(w io.Writer) error {
|
func (rf *ROLIEFeed) WriteTo(w io.Writer) (int64, error) {
|
||||||
enc := json.NewEncoder(w)
|
nw := nWriter{w, 0}
|
||||||
|
enc := json.NewEncoder(&nw)
|
||||||
enc.SetIndent("", " ")
|
enc.SetIndent("", " ")
|
||||||
return enc.Encode(rf)
|
err := enc.Encode(rf)
|
||||||
|
return nw.n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// EntryByID looks up an entry by its ID.
|
// EntryByID looks up an entry by its ID.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue