diff --git a/database/ancestry.go b/database/ancestry.go new file mode 100644 index 00000000..6ba8336c --- /dev/null +++ b/database/ancestry.go @@ -0,0 +1,96 @@ +// Copyright 2019 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package database + +// Ancestry is a manifest that keeps all layers in an image in order. +type Ancestry struct { + // Name is a globally unique value for a set of layers. This is often the + // sha256 digest of an OCI/Docker manifest. + Name string `json:"name"` + // By contains the processors that are used when computing the + // content of this ancestry. + By []Detector `json:"by"` + // Layers should be ordered and i_th layer is the parent of i+1_th layer in + // the slice. + Layers []AncestryLayer `json:"layers"` +} + +// Valid checks if the ancestry is compliant to spec. +func (a *Ancestry) Valid() bool { + if a == nil { + return false + } + + if a.Name == "" { + return false + } + + for _, d := range a.By { + if !d.Valid() { + return false + } + } + + for _, l := range a.Layers { + if !l.Valid() { + return false + } + } + + return true +} + +// AncestryLayer is a layer with all detected namespaced features. +type AncestryLayer struct { + // Hash is the sha-256 tarsum on the layer's blob content. + Hash string `json:"hash"` + // Features are the features introduced by this layer when it was + // processed. + Features []AncestryFeature `json:"features"` +} + +// Valid checks if the Ancestry Layer is compliant to the spec. +func (l *AncestryLayer) Valid() bool { + if l == nil { + return false + } + + if l.Hash == "" { + return false + } + + return true +} + +// GetFeatures returns the Ancestry's features. +func (l *AncestryLayer) GetFeatures() []NamespacedFeature { + nsf := make([]NamespacedFeature, 0, len(l.Features)) + for _, f := range l.Features { + nsf = append(nsf, f.NamespacedFeature) + } + + return nsf +} + +// AncestryFeature is a namespaced feature with the detectors used to +// find this feature. +type AncestryFeature struct { + NamespacedFeature `json:"namespacedFeature"` + + // FeatureBy is the detector that detected the feature. + FeatureBy Detector `json:"featureBy"` + // NamespaceBy is the detector that detected the namespace. + NamespaceBy Detector `json:"namespaceBy"` +} diff --git a/database/feature.go b/database/feature.go new file mode 100644 index 00000000..ac745b29 --- /dev/null +++ b/database/feature.go @@ -0,0 +1,96 @@ +// Copyright 2019 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package database + +// Feature represents a package detected in a layer but the namespace is not +// determined. +// +// e.g. Name: Libssl1.0, Version: 1.0, VersionFormat: dpkg, Type: binary +// dpkg is the version format of the installer package manager, which in this +// case could be dpkg or apk. +type Feature struct { + Name string `json:"name"` + Version string `json:"version"` + VersionFormat string `json:"versionFormat"` + Type FeatureType `json:"type"` +} + +// NamespacedFeature is a feature with determined namespace and can be affected +// by vulnerabilities. +// +// e.g. OpenSSL 1.0 dpkg Debian:7. +type NamespacedFeature struct { + Feature `json:"feature"` + + Namespace Namespace `json:"namespace"` +} + +// AffectedNamespacedFeature is a namespaced feature affected by the +// vulnerabilities with fixed-in versions for this feature. +type AffectedNamespacedFeature struct { + NamespacedFeature + + AffectedBy []VulnerabilityWithFixedIn +} + +// VulnerabilityWithFixedIn is used for AffectedNamespacedFeature to retrieve +// the affecting vulnerabilities and the fixed-in versions for the feature. +type VulnerabilityWithFixedIn struct { + Vulnerability + + FixedInVersion string +} + +// AffectedFeature is used to determine whether a namespaced feature is affected +// by a Vulnerability. Namespace and Feature Name is unique. Affected Feature is +// bound to vulnerability. +type AffectedFeature struct { + // FeatureType determines which type of package it affects. + FeatureType FeatureType + Namespace Namespace + FeatureName string + // FixedInVersion is known next feature version that's not affected by the + // vulnerability. Empty FixedInVersion means the unaffected version is + // unknown. + FixedInVersion string + // AffectedVersion contains the version range to determine whether or not a + // feature is affected. + AffectedVersion string +} + +// NullableAffectedNamespacedFeature is an affectednamespacedfeature with +// whether it's found in datastore. +type NullableAffectedNamespacedFeature struct { + AffectedNamespacedFeature + + Valid bool +} + +func NewFeature(name string, version string, versionFormat string, featureType FeatureType) *Feature { + return &Feature{name, version, versionFormat, featureType} +} + +func NewBinaryPackage(name string, version string, versionFormat string) *Feature { + return &Feature{name, version, versionFormat, BinaryPackage} +} + +func NewSourcePackage(name string, version string, versionFormat string) *Feature { + return &Feature{name, version, versionFormat, SourcePackage} +} + +func NewNamespacedFeature(namespace *Namespace, feature *Feature) *NamespacedFeature { + // TODO: namespaced feature should use pointer values + return &NamespacedFeature{*feature, *namespace} +} diff --git a/database/layer.go b/database/layer.go new file mode 100644 index 00000000..9ca3c410 --- /dev/null +++ b/database/layer.go @@ -0,0 +1,65 @@ +// Copyright 2019 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package database + +// Layer is a layer with all the detected features and namespaces. +type Layer struct { + // Hash is the sha-256 tarsum on the layer's blob content. + Hash string `json:"hash"` + // By contains a list of detectors scanned this Layer. + By []Detector `json:"by"` + Namespaces []LayerNamespace `json:"namespaces"` + Features []LayerFeature `json:"features"` +} + +func (l *Layer) GetFeatures() []Feature { + features := make([]Feature, 0, len(l.Features)) + for _, f := range l.Features { + features = append(features, f.Feature) + } + + return features +} + +func (l *Layer) GetNamespaces() []Namespace { + namespaces := make([]Namespace, 0, len(l.Namespaces)+len(l.Features)) + for _, ns := range l.Namespaces { + namespaces = append(namespaces, ns.Namespace) + } + for _, f := range l.Features { + if f.PotentialNamespace.Valid() { + namespaces = append(namespaces, f.PotentialNamespace) + } + } + + return namespaces +} + +// LayerNamespace is a namespace with detection information. +type LayerNamespace struct { + Namespace `json:"namespace"` + + // By is the detector found the namespace. + By Detector `json:"by"` +} + +// LayerFeature is a feature with detection information. +type LayerFeature struct { + Feature `json:"feature"` + + // By is the detector found the feature. + By Detector `json:"by"` + PotentialNamespace Namespace `json:"potentialNamespace"` +} diff --git a/database/metadata.go b/database/metadata.go new file mode 100644 index 00000000..44f588cf --- /dev/null +++ b/database/metadata.go @@ -0,0 +1,41 @@ +// Copyright 2019 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package database + +import ( + "database/sql/driver" + "encoding/json" +) + +// MetadataMap is for storing the metadata returned by vulnerability database. +type MetadataMap map[string]interface{} + +func (mm *MetadataMap) Scan(value interface{}) error { + if value == nil { + return nil + } + + // github.com/lib/pq decodes TEXT/VARCHAR fields into strings. + val, ok := value.(string) + if !ok { + panic("got type other than []byte from database") + } + return json.Unmarshal([]byte(val), mm) +} + +func (mm *MetadataMap) Value() (driver.Value, error) { + json, err := json.Marshal(*mm) + return string(json), err +} diff --git a/database/models.go b/database/models.go deleted file mode 100644 index 7dd36dfe..00000000 --- a/database/models.go +++ /dev/null @@ -1,363 +0,0 @@ -// Copyright 2017 clair authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package database - -import ( - "database/sql/driver" - "encoding/json" - "fmt" - "time" - - "github.com/coreos/clair/pkg/pagination" -) - -// Ancestry is a manifest that keeps all layers in an image in order. -type Ancestry struct { - // Name is a globally unique value for a set of layers. This is often the - // sha256 digest of an OCI/Docker manifest. - Name string `json:"name"` - // By contains the processors that are used when computing the - // content of this ancestry. - By []Detector `json:"by"` - // Layers should be ordered and i_th layer is the parent of i+1_th layer in - // the slice. - Layers []AncestryLayer `json:"layers"` -} - -// Valid checks if the ancestry is compliant to spec. -func (a *Ancestry) Valid() bool { - if a == nil { - return false - } - - if a.Name == "" { - return false - } - - for _, d := range a.By { - if !d.Valid() { - return false - } - } - - for _, l := range a.Layers { - if !l.Valid() { - return false - } - } - - return true -} - -// AncestryLayer is a layer with all detected namespaced features. -type AncestryLayer struct { - // Hash is the sha-256 tarsum on the layer's blob content. - Hash string `json:"hash"` - // Features are the features introduced by this layer when it was - // processed. - Features []AncestryFeature `json:"features"` -} - -// Valid checks if the Ancestry Layer is compliant to the spec. -func (l *AncestryLayer) Valid() bool { - if l == nil { - return false - } - - if l.Hash == "" { - return false - } - - return true -} - -// GetFeatures returns the Ancestry's features. -func (l *AncestryLayer) GetFeatures() []NamespacedFeature { - nsf := make([]NamespacedFeature, 0, len(l.Features)) - for _, f := range l.Features { - nsf = append(nsf, f.NamespacedFeature) - } - - return nsf -} - -// AncestryFeature is a namespaced feature with the detectors used to -// find this feature. -type AncestryFeature struct { - NamespacedFeature `json:"namespacedFeature"` - - // FeatureBy is the detector that detected the feature. - FeatureBy Detector `json:"featureBy"` - // NamespaceBy is the detector that detected the namespace. - NamespaceBy Detector `json:"namespaceBy"` -} - -// Layer is a layer with all the detected features and namespaces. -type Layer struct { - // Hash is the sha-256 tarsum on the layer's blob content. - Hash string `json:"hash"` - // By contains a list of detectors scanned this Layer. - By []Detector `json:"by"` - Namespaces []LayerNamespace `json:"namespaces"` - Features []LayerFeature `json:"features"` -} - -func (l *Layer) GetFeatures() []Feature { - features := make([]Feature, 0, len(l.Features)) - for _, f := range l.Features { - features = append(features, f.Feature) - } - - return features -} - -func (l *Layer) GetNamespaces() []Namespace { - namespaces := make([]Namespace, 0, len(l.Namespaces)+len(l.Features)) - for _, ns := range l.Namespaces { - namespaces = append(namespaces, ns.Namespace) - } - for _, f := range l.Features { - if f.PotentialNamespace.Valid() { - namespaces = append(namespaces, f.PotentialNamespace) - } - } - - return namespaces -} - -// LayerNamespace is a namespace with detection information. -type LayerNamespace struct { - Namespace `json:"namespace"` - - // By is the detector found the namespace. - By Detector `json:"by"` -} - -// LayerFeature is a feature with detection information. -type LayerFeature struct { - Feature `json:"feature"` - - // By is the detector found the feature. - By Detector `json:"by"` - PotentialNamespace Namespace `json:"potentialNamespace"` -} - -// Namespace is the contextual information around features. -// -// e.g. Debian:7, NodeJS. -type Namespace struct { - Name string `json:"name"` - VersionFormat string `json:"versionFormat"` -} - -func NewNamespace(name string, versionFormat string) *Namespace { - return &Namespace{name, versionFormat} -} - -func (ns *Namespace) Valid() bool { - if ns.Name == "" || ns.VersionFormat == "" { - return false - } - return true -} - -// Feature represents a package detected in a layer but the namespace is not -// determined. -// -// e.g. Name: Libssl1.0, Version: 1.0, VersionFormat: dpkg, Type: binary -// dpkg is the version format of the installer package manager, which in this -// case could be dpkg or apk. -type Feature struct { - Name string `json:"name"` - Version string `json:"version"` - VersionFormat string `json:"versionFormat"` - Type FeatureType `json:"type"` -} - -func NewFeature(name string, version string, versionFormat string, featureType FeatureType) *Feature { - return &Feature{name, version, versionFormat, featureType} -} - -func NewBinaryPackage(name string, version string, versionFormat string) *Feature { - return &Feature{name, version, versionFormat, BinaryPackage} -} - -func NewSourcePackage(name string, version string, versionFormat string) *Feature { - return &Feature{name, version, versionFormat, SourcePackage} -} - -// NamespacedFeature is a feature with determined namespace and can be affected -// by vulnerabilities. -// -// e.g. OpenSSL 1.0 dpkg Debian:7. -type NamespacedFeature struct { - Feature `json:"feature"` - - Namespace Namespace `json:"namespace"` -} - -func (nf *NamespacedFeature) Key() string { - return fmt.Sprintf("%s-%s-%s-%s-%s-%s", nf.Name, nf.Version, nf.VersionFormat, nf.Type, nf.Namespace.Name, nf.Namespace.VersionFormat) -} - -func NewNamespacedFeature(namespace *Namespace, feature *Feature) *NamespacedFeature { - // TODO: namespaced feature should use pointer values - return &NamespacedFeature{*feature, *namespace} -} - -// AffectedNamespacedFeature is a namespaced feature affected by the -// vulnerabilities with fixed-in versions for this feature. -type AffectedNamespacedFeature struct { - NamespacedFeature - - AffectedBy []VulnerabilityWithFixedIn -} - -// VulnerabilityWithFixedIn is used for AffectedNamespacedFeature to retrieve -// the affecting vulnerabilities and the fixed-in versions for the feature. -type VulnerabilityWithFixedIn struct { - Vulnerability - - FixedInVersion string -} - -// AffectedFeature is used to determine whether a namespaced feature is affected -// by a Vulnerability. Namespace and Feature Name is unique. Affected Feature is -// bound to vulnerability. -type AffectedFeature struct { - // FeatureType determines which type of package it affects. - FeatureType FeatureType - Namespace Namespace - FeatureName string - // FixedInVersion is known next feature version that's not affected by the - // vulnerability. Empty FixedInVersion means the unaffected version is - // unknown. - FixedInVersion string - // AffectedVersion contains the version range to determine whether or not a - // feature is affected. - AffectedVersion string -} - -// VulnerabilityID is an identifier for every vulnerability. Every vulnerability -// has unique namespace and name. -type VulnerabilityID struct { - Name string - Namespace string -} - -// Vulnerability represents CVE or similar vulnerability reports. -type Vulnerability struct { - Name string - Namespace Namespace - - Description string - Link string - Severity Severity - - Metadata MetadataMap -} - -// VulnerabilityWithAffected is a vulnerability with all known affected -// features. -type VulnerabilityWithAffected struct { - Vulnerability - - Affected []AffectedFeature -} - -// PagedVulnerableAncestries is a vulnerability with a page of affected -// ancestries each with a special index attached for streaming purpose. The -// current page number and next page number are for navigate. -type PagedVulnerableAncestries struct { - Vulnerability - - // Affected is a map of special indexes to Ancestries, which the pair - // should be unique in a stream. Every indexes in the map should be larger - // than previous page. - Affected map[int]string - - Limit int - Current pagination.Token - Next pagination.Token - - // End signals the end of the pages. - End bool -} - -// NotificationHook is a message sent to another service to inform of a change -// to a Vulnerability or the Ancestries affected by a Vulnerability. It contains -// the name of a notification that should be read and marked as read via the -// API. -type NotificationHook struct { - Name string - - Created time.Time - Notified time.Time - Deleted time.Time -} - -// VulnerabilityNotification is a notification for vulnerability changes. -type VulnerabilityNotification struct { - NotificationHook - - Old *Vulnerability - New *Vulnerability -} - -// VulnerabilityNotificationWithVulnerable is a notification for vulnerability -// changes with vulnerable ancestries. -type VulnerabilityNotificationWithVulnerable struct { - NotificationHook - - Old *PagedVulnerableAncestries - New *PagedVulnerableAncestries -} - -// MetadataMap is for storing the metadata returned by vulnerability database. -type MetadataMap map[string]interface{} - -// NullableAffectedNamespacedFeature is an affectednamespacedfeature with -// whether it's found in datastore. -type NullableAffectedNamespacedFeature struct { - AffectedNamespacedFeature - - Valid bool -} - -// NullableVulnerability is a vulnerability with whether the vulnerability is -// found in datastore. -type NullableVulnerability struct { - VulnerabilityWithAffected - - Valid bool -} - -func (mm *MetadataMap) Scan(value interface{}) error { - if value == nil { - return nil - } - - // github.com/lib/pq decodes TEXT/VARCHAR fields into strings. - val, ok := value.(string) - if !ok { - panic("got type other than []byte from database") - } - return json.Unmarshal([]byte(val), mm) -} - -func (mm *MetadataMap) Value() (driver.Value, error) { - json, err := json.Marshal(*mm) - return string(json), err -} diff --git a/database/namespace.go b/database/namespace.go new file mode 100644 index 00000000..33a9e2fb --- /dev/null +++ b/database/namespace.go @@ -0,0 +1,34 @@ +// Copyright 2019 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package database + +// Namespace is the contextual information around features. +// +// e.g. Debian:7, NodeJS. +type Namespace struct { + Name string `json:"name"` + VersionFormat string `json:"versionFormat"` +} + +func NewNamespace(name string, versionFormat string) *Namespace { + return &Namespace{name, versionFormat} +} + +func (ns *Namespace) Valid() bool { + if ns.Name == "" || ns.VersionFormat == "" { + return false + } + return true +} diff --git a/database/notification.go b/database/notification.go new file mode 100644 index 00000000..4b41ec25 --- /dev/null +++ b/database/notification.go @@ -0,0 +1,69 @@ +// Copyright 2019 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package database + +import ( + "time" + + "github.com/coreos/clair/pkg/pagination" +) + +// NotificationHook is a message sent to another service to inform of a change +// to a Vulnerability or the Ancestries affected by a Vulnerability. It contains +// the name of a notification that should be read and marked as read via the +// API. +type NotificationHook struct { + Name string + + Created time.Time + Notified time.Time + Deleted time.Time +} + +// VulnerabilityNotification is a notification for vulnerability changes. +type VulnerabilityNotification struct { + NotificationHook + + Old *Vulnerability + New *Vulnerability +} + +// VulnerabilityNotificationWithVulnerable is a notification for vulnerability +// changes with vulnerable ancestries. +type VulnerabilityNotificationWithVulnerable struct { + NotificationHook + + Old *PagedVulnerableAncestries + New *PagedVulnerableAncestries +} + +// PagedVulnerableAncestries is a vulnerability with a page of affected +// ancestries each with a special index attached for streaming purpose. The +// current page number and next page number are for navigate. +type PagedVulnerableAncestries struct { + Vulnerability + + // Affected is a map of special indexes to Ancestries, which the pair + // should be unique in a stream. Every indexes in the map should be larger + // than previous page. + Affected map[int]string + + Limit int + Current pagination.Token + Next pagination.Token + + // End signals the end of the pages. + End bool +} diff --git a/database/vulnerability.go b/database/vulnerability.go new file mode 100644 index 00000000..4eb40c42 --- /dev/null +++ b/database/vulnerability.go @@ -0,0 +1,50 @@ +// Copyright 2019 clair authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package database + +// VulnerabilityID is an identifier for every vulnerability. Every vulnerability +// has unique namespace and name. +type VulnerabilityID struct { + Name string + Namespace string +} + +// Vulnerability represents CVE or similar vulnerability reports. +type Vulnerability struct { + Name string + Namespace Namespace + + Description string + Link string + Severity Severity + + Metadata MetadataMap +} + +// VulnerabilityWithAffected is a vulnerability with all known affected +// features. +type VulnerabilityWithAffected struct { + Vulnerability + + Affected []AffectedFeature +} + +// NullableVulnerability is a vulnerability with whether the vulnerability is +// found in datastore. +type NullableVulnerability struct { + VulnerabilityWithAffected + + Valid bool +}