// Copyright 2015 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 v1 import ( "bytes" "encoding/json" "errors" "fmt" "time" "github.com/coreos/clair/database" "github.com/coreos/clair/utils/types" "github.com/coreos/pkg/capnslog" "github.com/fernet/fernet-go" ) var log = capnslog.NewPackageLogger("github.com/coreos/clair", "v1") type Error struct { Message string `json:"Layer` } type Layer struct { Name string `json:"Name,omitempty"` Namespace string `json:"Namespace,omitempty"` Path string `json:"Path,omitempty"` ParentName string `json:"ParentName,omitempty"` Format string `json:"Format,omitempty"` IndexedByVersion int `json:"IndexedByVersion,omitempty"` Features []Feature `json:"Features,omitempty"` } func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabilities bool) Layer { layer := Layer{ Name: dbLayer.Name, IndexedByVersion: dbLayer.EngineVersion, } if dbLayer.Parent != nil { layer.ParentName = dbLayer.Parent.Name } if dbLayer.Namespace != nil { layer.Namespace = dbLayer.Namespace.Name } if withFeatures || withVulnerabilities && dbLayer.Features != nil { for _, dbFeatureVersion := range dbLayer.Features { feature := Feature{ Name: dbFeatureVersion.Feature.Name, Namespace: dbFeatureVersion.Feature.Namespace.Name, Version: dbFeatureVersion.Version.String(), AddedBy: dbFeatureVersion.AddedBy.Name, } for _, dbVuln := range dbFeatureVersion.AffectedBy { vuln := Vulnerability{ Name: dbVuln.Name, Namespace: dbVuln.Namespace.Name, Description: dbVuln.Description, Link: dbVuln.Link, Severity: string(dbVuln.Severity), Metadata: dbVuln.Metadata, } if dbVuln.FixedBy != types.MaxVersion { vuln.FixedBy = dbVuln.FixedBy.String() } feature.Vulnerabilities = append(feature.Vulnerabilities, vuln) } layer.Features = append(layer.Features, feature) } } return layer } type Vulnerability struct { Name string `json:"Name,omitempty"` Namespace string `json:"Namespace,omitempty"` Description string `json:"Description,omitempty"` Link string `json:"Link,omitempty"` Severity string `json:"Severity,omitempty"` Metadata map[string]interface{} `json:"Metadata,omitempty"` FixedBy string `json:"FixedBy,omitempty"` FixedIn []Feature `json:"FixedIn,omitempty"` } func (v Vulnerability) DatabaseModel() (database.Vulnerability, error) { severity := types.Priority(v.Severity) if !severity.IsValid() { return database.Vulnerability{}, errors.New("Invalid severity") } var dbFeatures []database.FeatureVersion for _, feature := range v.FixedIn { dbFeature, err := feature.DatabaseModel() if err != nil { return database.Vulnerability{}, err } dbFeatures = append(dbFeatures, dbFeature) } return database.Vulnerability{ Name: v.Name, Namespace: database.Namespace{Name: v.Namespace}, Description: v.Description, Link: v.Link, Severity: severity, Metadata: v.Metadata, FixedIn: dbFeatures, }, nil } func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability, withFixedIn bool) Vulnerability { vuln := Vulnerability{ Name: dbVuln.Name, Namespace: dbVuln.Namespace.Name, Description: dbVuln.Description, Link: dbVuln.Link, Severity: string(dbVuln.Severity), Metadata: dbVuln.Metadata, } if withFixedIn { for _, dbFeatureVersion := range dbVuln.FixedIn { vuln.FixedIn = append(vuln.FixedIn, FeatureFromDatabaseModel(dbFeatureVersion)) } } return vuln } type Feature struct { Name string `json:"Name,omitempty"` Namespace string `json:"Namespace,omitempty"` Version string `json:"Version,omitempty"` Vulnerabilities []Vulnerability `json:"Vulnerabilities,omitempty"` AddedBy string `json:"AddedBy,omitempty"` } func FeatureFromDatabaseModel(dbFeatureVersion database.FeatureVersion) Feature { versionStr := dbFeatureVersion.Version.String() if versionStr == types.MaxVersion.String() { versionStr = "None" } return Feature{ Name: dbFeatureVersion.Feature.Name, Namespace: dbFeatureVersion.Feature.Namespace.Name, Version: versionStr, AddedBy: dbFeatureVersion.AddedBy.Name, } } func (f Feature) DatabaseModel() (database.FeatureVersion, error) { var version types.Version if f.Version == "None" { version = types.MaxVersion } else { var err error version, err = types.NewVersion(f.Version) if err != nil { return database.FeatureVersion{}, err } } return database.FeatureVersion{ Feature: database.Feature{ Name: f.Name, Namespace: database.Namespace{Name: f.Namespace}, }, Version: version, }, nil } type Notification struct { Name string `json:"Name,omitempty"` Created string `json:"Created,omitempty"` Notified string `json:"Notified,omitempty"` Deleted string `json:"Deleted,omitempty"` Limit int `json:"Limit,omitempty"` Page string `json:"Page,omitempty"` NextPage string `json:"NextPage,omitempty"` Old *VulnerabilityWithLayers `json:"Old,omitempty"` New *VulnerabilityWithLayers `json:"New,omitempty"` } func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotification, limit int, page, nextPage database.VulnerabilityNotificationPageNumber, key string) Notification { var oldVuln *VulnerabilityWithLayers if dbNotification.OldVulnerability != nil { v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.OldVulnerability) oldVuln = &v } var newVuln *VulnerabilityWithLayers if dbNotification.NewVulnerability != nil { v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.NewVulnerability) newVuln = &v } var nextPageStr string if nextPage != database.NoVulnerabilityNotificationPage { nextPageStr = pageNumberToToken(nextPage, key) } var created, notified, deleted string if !dbNotification.Created.IsZero() { created = fmt.Sprintf("%d", dbNotification.Created.Unix()) } if !dbNotification.Notified.IsZero() { notified = fmt.Sprintf("%d", dbNotification.Notified.Unix()) } if !dbNotification.Deleted.IsZero() { deleted = fmt.Sprintf("%d", dbNotification.Deleted.Unix()) } // TODO(jzelinskie): implement "changed" key fmt.Println(dbNotification.Deleted.IsZero()) return Notification{ Name: dbNotification.Name, Created: created, Notified: notified, Deleted: deleted, Limit: limit, Page: pageNumberToToken(page, key), NextPage: nextPageStr, Old: oldVuln, New: newVuln, } } type VulnerabilityWithLayers struct { Vulnerability *Vulnerability `json:"Vulnerability,omitempty"` LayersIntroducingVulnerability []string `json:"LayersIntroducingVulnerability,omitempty"` } func VulnerabilityWithLayersFromDatabaseModel(dbVuln database.Vulnerability) VulnerabilityWithLayers { vuln := VulnerabilityFromDatabaseModel(dbVuln, true) var layers []string for _, layer := range dbVuln.LayersIntroducingVulnerability { layers = append(layers, layer.Name) } return VulnerabilityWithLayers{ Vulnerability: &vuln, LayersIntroducingVulnerability: layers, } } type LayerEnvelope struct { Layer *Layer `json:"Layer,omitempty"` Error *Error `json:"Error,omitempty"` } type NamespaceEnvelope struct { Namespaces *[]string `json:"Namespaces,omitempty"` Error *Error `json:"Error,omitempty"` } type VulnerabilityEnvelope struct { Vulnerability *Vulnerability `json:"Vulnerability,omitempty"` Error *Error `json:"Error,omitempty"` } type NotificationEnvelope struct { Notification *Notification `json:"Notification,omitempty"` Error *Error `json:"Error,omitempty"` } type FeatureEnvelope struct { Feature *Feature `json:"Feature,omitempty"` Features *[]Feature `json:"Features,omitempty"` Error *Error `json:"Error,omitempty"` } func tokenToPageNumber(token, key string) (database.VulnerabilityNotificationPageNumber, error) { k, _ := fernet.DecodeKey(key) msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k}) if msg == nil { return database.VulnerabilityNotificationPageNumber{}, errors.New("invalid or expired pagination token") } page := database.VulnerabilityNotificationPageNumber{} err := json.NewDecoder(bytes.NewBuffer(msg)).Decode(&page) return page, err } func pageNumberToToken(page database.VulnerabilityNotificationPageNumber, key string) string { var buf bytes.Buffer err := json.NewEncoder(&buf).Encode(page) if err != nil { log.Fatal("failed to encode VulnerabilityNotificationPageNumber") } k, _ := fernet.DecodeKey(key) tokenBytes, err := fernet.EncryptAndSign(buf.Bytes(), k) if err != nil { log.Fatal("failed to encrypt VulnerabilityNotificationpageNumber") } return string(tokenBytes) }