Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions alias.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package claircore

import (
"strings"
"unique"
)

// Alias is an identifier for the same conceptual vulnerability.
// An alias has two parts: the namespace and the name.
//
// The namespace has no format restrictions, but is almost certainly in one of two
// formats:
// - URI (https://example.com/)
// - short prefix (EX)
//
// The name has no format restrictions and is only assumed to be unique within
// the namespace.
type Alias struct {
Space unique.Handle[string]
Name string
}

// String implements [fmt.Stringer].
func (a Alias) String() string {
space := a.Space.Value()

var b strings.Builder
b.WriteString(space)
if strings.Contains(space, "://") { // If URI-ish:
b.WriteByte('#')
} else {
b.WriteByte('-')
}
b.WriteString(a.Name)

return b.String()
}

// Equal reports if two Aliases are the same alias.
func (a Alias) Equal(b Alias) bool {
return a.Space == b.Space && a.Name == b.Name
}

// Valid reports if the receiver is a valid alias.
//
// A invalid alias is one with a missing or empty Space or Name.
func (a Alias) Valid() bool {
return a.Space != unique.Handle[string]{} && a.Space.Value() != "" && a.Name != ""
}
68 changes: 68 additions & 0 deletions alias_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package claircore

import (
"fmt"
"testing"
"unique"
)

func alias(space, name string) Alias {
return Alias{
Space: unique.Make(space),
Name: name,
}
}

func ExampleAlias_uri() {
fmt.Println(alias("https://example.com/", "CVE-2014-0160"))
// Output:
// https://example.com/#CVE-2014-0160
}

func ExampleAlias_cve() {
fmt.Println(alias("CVE", "2014-0160"))
// Output:
// CVE-2014-0160
}

func ExampleAlias_Equal() {
a, b := alias("CVE", "2014-0160"), alias("CVE", "2014-0160")
fmt.Println("equal:", a.Equal(b))
// Output:
// equal: true
}

func TestAlias(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
t.Run("OK", func(t *testing.T) {
a := Alias{Space: unique.Make("TEST"), Name: "1"}
if got, want := a.Valid(), true; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
})
t.Run("Zero", func(t *testing.T) {
a := Alias{}
if got, want := a.Valid(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
})
t.Run("MissingSpace", func(t *testing.T) {
a := Alias{Name: "1"}
if got, want := a.Valid(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
})
t.Run("EmptySpace", func(t *testing.T) {
a := Alias{Space: unique.Make(""), Name: "1"}
if got, want := a.Valid(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
})
t.Run("MissingName", func(t *testing.T) {
a := Alias{Space: unique.Make("TEST")}
if got, want := a.Valid(), false; got != want {
t.Errorf("got: %v, want: %v", got, want)
}
})
})
}
30 changes: 30 additions & 0 deletions datastore/postgres/migrations/matcher/17-aliases.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
CREATE TABLE IF NOT EXISTS alias_namespace (
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
namespace TEXT UNIQUE NOT NULL
);

COMMENT ON TABLE alias_namespace IS 'Contains namespaces for aliases. Usually short IDs like "CVE" or "GHSA".';

CREATE TABLE IF NOT EXISTS alias (
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
namespace INTEGER NOT NULL REFERENCES alias_namespace (id) ON DELETE CASCADE,
name TEXT NOT NULL,
UNIQUE (namespace, name)
);

COMMENT ON TABLE alias IS 'Table for all known aliases of vulnerabilities in the system. All vulnerabilities should have at least one alias.';

CREATE TABLE IF NOT EXISTS vulnerability_alias (
vulnerability INTEGER REFERENCES vuln (id) ON DELETE CASCADE,
alias INTEGER NOT NULL REFERENCES alias (id) ON DELETE CASCADE,
PRIMARY KEY (vulnerability, alias)
);

COMMENT ON TABLE vulnerability_alias IS 'Pivot table linking vulnerabilities to aliases.';

CREATE TABLE IF NOT EXISTS vulnerability_self (
vulnerability INTEGER PRIMARY KEY REFERENCES vuln (id) ON DELETE CASCADE,
self INTEGER NOT NULL REFERENCES alias (id) ON DELETE CASCADE
);

COMMENT ON TABLE vulnerability_self IS 'Indicates the "self" alias for a vulnerability.';
12 changes: 12 additions & 0 deletions vulnerability.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ type Vulnerability struct {
// ArchOperation indicates how the affected Package's "arch" should be
// compared.
ArchOperation ArchOp `json:"arch_op,omitempty"`

// Self is an Alias that is the "identity" for this Vulnerability.
//
// This should be a system-wide, external identifier.
Self Alias
// Aliases is a set of aliases for the same abstract software flaw.
//
// For example, GHSA advisories frequently also reference CVE identifiers.
//
// For instances from a claircore "datastore" implementation, this will also
// include the "Self" alias.
Aliases []Alias
}

// CheckVulnernableFunc takes a vulnerability and an indexRecord and checks if the record is
Expand Down
Loading