Merge pull request #672 from KeyboardNerd/source_package/feature_type

Implement Feature types
master
Sida Chen 5 years ago committed by GitHub
commit 73bc2bc36b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -31,11 +31,6 @@ notifications:
matrix:
include:
- addons:
apt:
packages:
- rpm
postgresql: 9.4
- addons:
apt:
packages:

@ -232,6 +232,9 @@ type Feature struct {
Detector *Detector `protobuf:"bytes,5,opt,name=detector" json:"detector,omitempty"`
// The list of vulnerabilities that affect the feature.
Vulnerabilities []*Vulnerability `protobuf:"bytes,6,rep,name=vulnerabilities" json:"vulnerabilities,omitempty"`
// The feature type indicates if the feature represents a source package or
// binary package.
FeatureType string `protobuf:"bytes,7,opt,name=feature_type,json=featureType" json:"feature_type,omitempty"`
}
func (m *Feature) Reset() { *m = Feature{} }
@ -281,6 +284,13 @@ func (m *Feature) GetVulnerabilities() []*Vulnerability {
return nil
}
func (m *Feature) GetFeatureType() string {
if m != nil {
return m.FeatureType
}
return ""
}
type Layer struct {
// The sha256 tarsum for the layer.
Hash string `protobuf:"bytes,1,opt,name=hash" json:"hash,omitempty"`
@ -1091,89 +1101,90 @@ var _StatusService_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("api/v3/clairpb/clair.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 1336 bytes of a gzipped FileDescriptorProto
// 1350 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0x4b, 0x6f, 0x1b, 0x55,
0x14, 0x66, 0x9c, 0x3a, 0xb6, 0x8f, 0xed, 0xc4, 0xbd, 0x49, 0x13, 0x67, 0xd2, 0x47, 0x32, 0x50,
0x51, 0x0a, 0xb2, 0x85, 0x5b, 0xa4, 0xb6, 0x2c, 0x90, 0x9b, 0x38, 0x21, 0x52, 0x1b, 0xa2, 0x49,
0x1a, 0x09, 0x10, 0x32, 0x37, 0x9e, 0xe3, 0x64, 0x94, 0xf1, 0xcc, 0x30, 0x73, 0x9d, 0xd4, 0xaa,
0xca, 0x82, 0x1d, 0x3b, 0x04, 0x0b, 0x56, 0xfc, 0x00, 0x36, 0x88, 0xff, 0xc0, 0x9e, 0x05, 0x6c,
0x61, 0xc7, 0x82, 0x3f, 0xc0, 0x1e, 0xdd, 0xc7, 0x4c, 0x66, 0x92, 0x49, 0xe2, 0x76, 0xe5, 0x7b,
0xde, 0x8f, 0xfb, 0xdd, 0x73, 0xc6, 0xa0, 0x53, 0xdf, 0x6e, 0x1e, 0xdd, 0x6b, 0xf6, 0x1c, 0x6a,
0x07, 0xfe, 0x9e, 0xfc, 0x6d, 0xf8, 0x81, 0xc7, 0x3c, 0x52, 0xe9, 0x79, 0x01, 0x7a, 0x61, 0x43,
0xf0, 0xf4, 0x5b, 0xfb, 0x9e, 0xb7, 0xef, 0x60, 0x53, 0xc8, 0xf6, 0x86, 0xfd, 0x26, 0xb3, 0x07,
0x18, 0x32, 0x3a, 0xf0, 0xa5, 0xba, 0x7e, 0x5d, 0x29, 0x70, 0x8f, 0xd4, 0x75, 0x3d, 0x46, 0x99,
0xed, 0xb9, 0xa1, 0x94, 0x1a, 0x3f, 0xe6, 0xa0, 0xba, 0x3b, 0x74, 0x5c, 0x0c, 0xe8, 0x9e, 0xed,
0xd8, 0x6c, 0x44, 0x08, 0x5c, 0x71, 0xe9, 0x00, 0xeb, 0xda, 0x92, 0x76, 0xa7, 0x64, 0x8a, 0x33,
0xb9, 0x0d, 0x53, 0xfc, 0x37, 0xf4, 0x69, 0x0f, 0xbb, 0x42, 0x9a, 0x13, 0xd2, 0x6a, 0xcc, 0xdd,
0xe4, 0x6a, 0x4b, 0x50, 0xb6, 0x30, 0xec, 0x05, 0xb6, 0xcf, 0x43, 0xd4, 0x27, 0x84, 0x4e, 0x92,
0xc5, 0x9d, 0x3b, 0xb6, 0x7b, 0x58, 0xbf, 0x22, 0x9d, 0xf3, 0x33, 0xd1, 0xa1, 0x18, 0xe2, 0x11,
0x06, 0x36, 0x1b, 0xd5, 0xf3, 0x82, 0x1f, 0xd3, 0x5c, 0x36, 0x40, 0x46, 0x2d, 0xca, 0x68, 0x7d,
0x52, 0xca, 0x22, 0x9a, 0x2c, 0x40, 0xb1, 0x6f, 0x3f, 0x47, 0xab, 0xbb, 0x37, 0xaa, 0x17, 0x84,
0xac, 0x20, 0xe8, 0xc7, 0x23, 0xf2, 0x18, 0xae, 0xd2, 0x7e, 0x1f, 0x7b, 0x0c, 0xad, 0xee, 0x11,
0x06, 0x21, 0x2f, 0xb8, 0x5e, 0x5c, 0x9a, 0xb8, 0x53, 0x6e, 0x5d, 0x6b, 0x24, 0xdb, 0xd7, 0x58,
0x43, 0xca, 0x86, 0x01, 0x9a, 0xb5, 0x48, 0x7f, 0x57, 0xa9, 0x1b, 0xbf, 0x6b, 0x50, 0x5c, 0x45,
0x86, 0x3d, 0xe6, 0x05, 0x99, 0x4d, 0xa9, 0x43, 0x41, 0xf9, 0x56, 0xdd, 0x88, 0x48, 0xd2, 0x82,
0xbc, 0xc5, 0x46, 0x3e, 0x8a, 0x0e, 0x4c, 0xb5, 0xae, 0xa7, 0x43, 0x46, 0x4e, 0x1b, 0xab, 0x3b,
0x23, 0x1f, 0x4d, 0xa9, 0x6a, 0x7c, 0x09, 0x79, 0x41, 0x93, 0x45, 0x98, 0x5f, 0xed, 0xec, 0x74,
0x56, 0x76, 0x3e, 0x31, 0xbb, 0xab, 0xdd, 0x9d, 0x4f, 0xb7, 0x3a, 0xdd, 0x8d, 0xcd, 0xdd, 0xf6,
0x93, 0x8d, 0xd5, 0xda, 0x1b, 0xe4, 0x06, 0x2c, 0x9c, 0x16, 0x6e, 0xb6, 0x9f, 0x76, 0xb6, 0xb7,
0xda, 0x2b, 0x9d, 0x9a, 0x96, 0x65, 0xbb, 0xd6, 0x69, 0xef, 0x3c, 0x33, 0x3b, 0xb5, 0x9c, 0xb1,
0x0d, 0xa5, 0xcd, 0xe8, 0xba, 0x32, 0x0b, 0x6a, 0x41, 0xd1, 0x52, 0xb9, 0x89, 0x8a, 0xca, 0xad,
0xb9, 0xec, 0xcc, 0xcd, 0x58, 0xcf, 0xf8, 0x2e, 0x07, 0x05, 0xd5, 0xc3, 0x4c, 0x9f, 0x1f, 0x40,
0x29, 0xc6, 0x88, 0x72, 0x3a, 0x9f, 0x76, 0x1a, 0xe7, 0x64, 0x9e, 0x68, 0x26, 0x7b, 0x3b, 0x91,
0xee, 0xed, 0x6d, 0x98, 0x52, 0xc7, 0x6e, 0xdf, 0x0b, 0x06, 0x94, 0x29, 0x2c, 0x55, 0x15, 0x77,
0x4d, 0x30, 0x53, 0xb5, 0xe4, 0xc7, 0xab, 0x85, 0x74, 0x60, 0xfa, 0x28, 0xf1, 0x14, 0x6c, 0x0c,
0xeb, 0x93, 0x02, 0x33, 0x8b, 0x69, 0xd3, 0xd4, 0x7b, 0x31, 0x4f, 0xdb, 0x18, 0x8b, 0x90, 0x7f,
0x42, 0x47, 0x28, 0x40, 0x73, 0x40, 0xc3, 0x83, 0xa8, 0x1f, 0xfc, 0x6c, 0x7c, 0xab, 0x41, 0x79,
0x85, 0x7b, 0xd9, 0x66, 0x94, 0x0d, 0x43, 0x72, 0x1f, 0x4a, 0x51, 0xfc, 0xb0, 0xae, 0x89, 0x68,
0xe7, 0x25, 0x7a, 0xa2, 0x48, 0x56, 0xa1, 0xe6, 0xd0, 0x90, 0x75, 0x87, 0xbe, 0x45, 0x19, 0x76,
0xf9, 0x93, 0x57, 0xcd, 0xd5, 0x1b, 0xf2, 0xb9, 0x37, 0xa2, 0x79, 0xd0, 0xd8, 0x89, 0xe6, 0x81,
0x39, 0xc5, 0x6d, 0x9e, 0x09, 0x13, 0xce, 0x34, 0x1e, 0x02, 0x59, 0x47, 0xd6, 0x76, 0x7b, 0x18,
0xb2, 0x60, 0x64, 0xe2, 0x57, 0x43, 0x0c, 0x19, 0x79, 0x13, 0xaa, 0x54, 0xb1, 0xba, 0x89, 0xeb,
0xac, 0x44, 0x4c, 0x7e, 0x5f, 0xc6, 0xaf, 0x13, 0x30, 0x93, 0xb2, 0x0d, 0x7d, 0xcf, 0x0d, 0x91,
0xac, 0x41, 0x31, 0xd2, 0x13, 0x76, 0xe5, 0xd6, 0xdd, 0x74, 0x35, 0x19, 0x46, 0x8d, 0x98, 0x11,
0xdb, 0x92, 0xf7, 0x61, 0x32, 0x14, 0x0d, 0x52, 0x65, 0x2d, 0xa4, 0xbd, 0x24, 0x3a, 0x68, 0x2a,
0x45, 0xfd, 0x6b, 0xa8, 0x46, 0x8e, 0x64, 0xfb, 0xdf, 0x81, 0xbc, 0xc3, 0x0f, 0x2a, 0x91, 0x99,
0xb4, 0x0b, 0xa1, 0x63, 0x4a, 0x0d, 0x3e, 0x2f, 0x64, 0x73, 0xd1, 0xea, 0xf6, 0x25, 0x9a, 0x79,
0xe4, 0x8b, 0xe6, 0x45, 0xa4, 0xaf, 0x18, 0xa1, 0xfe, 0x93, 0x06, 0xc5, 0x28, 0x81, 0xcc, 0xa7,
0x90, 0xba, 0xea, 0xdc, 0xb8, 0x57, 0xbd, 0x0e, 0x93, 0x22, 0xc7, 0xb0, 0x3e, 0x21, 0x4c, 0x9a,
0xe3, 0xf7, 0x53, 0x96, 0xa8, 0xcc, 0x8d, 0xbf, 0x73, 0x30, 0xb3, 0xe5, 0x85, 0xaf, 0x75, 0xdf,
0x64, 0x0e, 0x26, 0xd5, 0x6b, 0x93, 0xa3, 0x4e, 0x51, 0x64, 0xe5, 0x54, 0x76, 0xef, 0xa6, 0xb3,
0xcb, 0x88, 0x27, 0x78, 0xa9, 0xcc, 0xf4, 0xdf, 0x34, 0x28, 0xc5, 0xdc, 0xac, 0x57, 0xc3, 0x79,
0x3e, 0x65, 0x07, 0x2a, 0xb8, 0x38, 0x13, 0x13, 0x0a, 0x07, 0x48, 0xad, 0x93, 0xd8, 0x0f, 0x5e,
0x21, 0x76, 0xe3, 0x63, 0x69, 0xda, 0x71, 0xb9, 0x34, 0x72, 0xa4, 0x3f, 0x82, 0x4a, 0x52, 0x40,
0x6a, 0x30, 0x71, 0x88, 0x23, 0x95, 0x0a, 0x3f, 0x92, 0x59, 0xc8, 0x1f, 0x51, 0x67, 0x18, 0x2d,
0x40, 0x49, 0x3c, 0xca, 0x3d, 0xd0, 0x8c, 0x0d, 0x98, 0x4d, 0x87, 0x54, 0x4f, 0xe2, 0x04, 0xca,
0xda, 0x98, 0x50, 0x36, 0x7e, 0xd1, 0x60, 0x6e, 0x1d, 0xd9, 0xa6, 0xc7, 0xec, 0xbe, 0xdd, 0x13,
0xfb, 0x3a, 0xba, 0xad, 0xfb, 0x30, 0xe7, 0x39, 0x56, 0x37, 0x39, 0x73, 0x46, 0x5d, 0x9f, 0xee,
0x47, 0xd7, 0x36, 0xeb, 0x39, 0x56, 0x6a, 0x3e, 0x6d, 0xd1, 0x7d, 0x0e, 0xbd, 0x39, 0x17, 0x8f,
0xb3, 0xac, 0x64, 0x19, 0xb3, 0x2e, 0x1e, 0x9f, 0xb5, 0x9a, 0x85, 0xbc, 0x63, 0x0f, 0x6c, 0x26,
0x46, 0x70, 0xde, 0x94, 0x44, 0x0c, 0xed, 0x2b, 0x27, 0xd0, 0x36, 0xfe, 0xca, 0xc1, 0xfc, 0x99,
0x84, 0x55, 0xfd, 0xbb, 0x50, 0x71, 0x13, 0x7c, 0xd5, 0x85, 0xd6, 0x19, 0x18, 0x67, 0x19, 0x37,
0x52, 0xcc, 0x94, 0x1f, 0xfd, 0x5f, 0x0d, 0x2a, 0x49, 0xf1, 0x79, 0x3b, 0xba, 0x17, 0x20, 0x65,
0x68, 0x45, 0x3b, 0x5a, 0x91, 0xfc, 0xcb, 0x42, 0xba, 0x43, 0x4b, 0xad, 0x98, 0x98, 0xe6, 0x56,
0x16, 0x3a, 0xc8, 0xad, 0x64, 0x95, 0x11, 0x49, 0x1e, 0xc2, 0x84, 0xe7, 0x58, 0x6a, 0xa3, 0xbc,
0x7d, 0x0a, 0x70, 0x74, 0x1f, 0xe3, 0xde, 0x3b, 0xa8, 0x80, 0x60, 0x63, 0x68, 0x72, 0x1b, 0x6e,
0xea, 0xe2, 0xb1, 0xf8, 0x8a, 0x79, 0x15, 0x53, 0x17, 0x8f, 0x8d, 0x3f, 0x72, 0xb0, 0x70, 0xae,
0x0a, 0x59, 0x86, 0x4a, 0x6f, 0x18, 0x04, 0xe8, 0xb2, 0x24, 0x10, 0xca, 0x8a, 0x27, 0x6e, 0x72,
0x11, 0x4a, 0x2e, 0x3e, 0x67, 0xc9, 0x2b, 0x2f, 0x72, 0xc6, 0x05, 0xd7, 0xdc, 0x86, 0x6a, 0x0a,
0x2e, 0xa2, 0x13, 0x97, 0xac, 0xc2, 0xb4, 0x05, 0xf9, 0x1c, 0x80, 0xc6, 0x69, 0xd6, 0xf3, 0xe2,
0x91, 0x7e, 0x38, 0x66, 0xe1, 0x8d, 0x0d, 0xd7, 0xc2, 0xe7, 0x68, 0xb5, 0x13, 0x53, 0xc8, 0x4c,
0xb8, 0xd3, 0x3f, 0x82, 0x99, 0x0c, 0x15, 0x5e, 0x8c, 0xcd, 0xd9, 0xa2, 0x0b, 0x79, 0x53, 0x12,
0x31, 0x34, 0x72, 0x09, 0xcc, 0xde, 0x83, 0x1b, 0x4f, 0x69, 0x70, 0x98, 0x84, 0x50, 0x3b, 0x34,
0x91, 0x5a, 0xd1, 0x53, 0xcb, 0xc0, 0x93, 0xb1, 0x04, 0x37, 0xcf, 0x33, 0x92, 0x88, 0x35, 0x08,
0xd4, 0xd6, 0x91, 0xa9, 0x07, 0x2d, 0x3d, 0x19, 0x6b, 0x70, 0x35, 0xc1, 0x7b, 0xed, 0xb9, 0xd0,
0xfa, 0x4f, 0x83, 0xe9, 0xa8, 0xda, 0x6d, 0x0c, 0x8e, 0xec, 0x1e, 0x92, 0x21, 0x94, 0x13, 0x3b,
0x80, 0x2c, 0x5d, 0xb0, 0x1e, 0x44, 0x32, 0xfa, 0xf2, 0xa5, 0x0b, 0xc4, 0x58, 0xfe, 0xe6, 0xcf,
0x7f, 0x7e, 0xc8, 0x2d, 0x92, 0x85, 0x66, 0xb4, 0x04, 0x9a, 0x2f, 0x52, 0x3b, 0xe2, 0x25, 0x39,
0x84, 0x4a, 0x72, 0xda, 0x91, 0xe5, 0x4b, 0x87, 0xaf, 0x6e, 0x5c, 0xa4, 0xa2, 0x22, 0xcf, 0x8a,
0xc8, 0x53, 0x46, 0x29, 0x8e, 0xfc, 0x48, 0xbb, 0xdb, 0xfa, 0x39, 0x07, 0x33, 0xc9, 0x96, 0x47,
0xb5, 0xbf, 0x84, 0xe9, 0x53, 0x83, 0x83, 0xbc, 0x75, 0xc9, 0x5c, 0x91, 0xa9, 0xdc, 0x1e, 0x6b,
0xfa, 0x18, 0x37, 0x44, 0x36, 0xf3, 0xe4, 0x5a, 0x33, 0x39, 0x79, 0xc2, 0xe6, 0x0b, 0xd9, 0x83,
0xef, 0x35, 0x98, 0xcb, 0x46, 0x03, 0x39, 0xb5, 0x07, 0x2f, 0x04, 0x9a, 0xfe, 0xde, 0x78, 0xca,
0xe9, 0xa4, 0xee, 0x66, 0x27, 0xd5, 0x72, 0xa1, 0x2a, 0x51, 0x13, 0x35, 0xe9, 0x0b, 0x28, 0xc5,
0xe0, 0x23, 0x37, 0xcf, 0x14, 0x9e, 0x42, 0xaa, 0x7e, 0xeb, 0x5c, 0xb9, 0x8a, 0x3e, 0x2d, 0xa2,
0x97, 0x48, 0xa1, 0x29, 0x31, 0xf9, 0xf8, 0x26, 0xcc, 0xf4, 0xbc, 0x41, 0xda, 0xcc, 0xdf, 0xfb,
0xac, 0xa0, 0xfe, 0xb9, 0xee, 0x4d, 0x8a, 0x0f, 0xd1, 0x7b, 0xff, 0x07, 0x00, 0x00, 0xff, 0xff,
0xcb, 0x5c, 0xce, 0x34, 0xd2, 0x0e, 0x00, 0x00,
0x1a, 0x09, 0x10, 0x1a, 0x6e, 0x3c, 0xc7, 0xc9, 0x28, 0xe3, 0x19, 0x33, 0x73, 0x9d, 0xd4, 0xaa,
0xca, 0x82, 0x1d, 0x5b, 0x58, 0xb0, 0xe2, 0x07, 0xb0, 0x41, 0x48, 0xfc, 0x04, 0xf6, 0x2c, 0x60,
0x0b, 0x3b, 0x16, 0xfc, 0x01, 0xf6, 0xe8, 0x3e, 0x66, 0x32, 0x93, 0x4c, 0x12, 0xb7, 0x2b, 0xdf,
0x7b, 0xde, 0x8f, 0xef, 0x9e, 0x33, 0x06, 0x9d, 0x0e, 0x9c, 0xe6, 0xd1, 0xbd, 0x66, 0xd7, 0xa5,
0x4e, 0x30, 0xd8, 0x93, 0xbf, 0x8d, 0x41, 0xe0, 0x33, 0x9f, 0x54, 0xba, 0x7e, 0x80, 0x7e, 0xd8,
0x10, 0x34, 0xfd, 0xd6, 0xbe, 0xef, 0xef, 0xbb, 0xd8, 0x14, 0xbc, 0xbd, 0x61, 0xaf, 0xc9, 0x9c,
0x3e, 0x86, 0x8c, 0xf6, 0x07, 0x52, 0x5c, 0xbf, 0xae, 0x04, 0xb8, 0x45, 0xea, 0x79, 0x3e, 0xa3,
0xcc, 0xf1, 0xbd, 0x50, 0x72, 0x8d, 0x1f, 0x72, 0x50, 0xdd, 0x1d, 0xba, 0x1e, 0x06, 0x74, 0xcf,
0x71, 0x1d, 0x36, 0x22, 0x04, 0xae, 0x78, 0xb4, 0x8f, 0x75, 0x6d, 0x49, 0xbb, 0x53, 0x32, 0xc5,
0x99, 0xdc, 0x86, 0x29, 0xfe, 0x1b, 0x0e, 0x68, 0x17, 0x2d, 0xc1, 0xcd, 0x09, 0x6e, 0x35, 0xa6,
0x6e, 0x72, 0xb1, 0x25, 0x28, 0xdb, 0x18, 0x76, 0x03, 0x67, 0xc0, 0x5d, 0xd4, 0x27, 0x84, 0x4c,
0x92, 0xc4, 0x8d, 0xbb, 0x8e, 0x77, 0x58, 0xbf, 0x22, 0x8d, 0xf3, 0x33, 0xd1, 0xa1, 0x18, 0xe2,
0x11, 0x06, 0x0e, 0x1b, 0xd5, 0xf3, 0x82, 0x1e, 0xdf, 0x39, 0xaf, 0x8f, 0x8c, 0xda, 0x94, 0xd1,
0xfa, 0xa4, 0xe4, 0x45, 0x77, 0xb2, 0x00, 0xc5, 0x9e, 0xf3, 0x1c, 0x6d, 0x6b, 0x6f, 0x54, 0x2f,
0x08, 0x5e, 0x41, 0xdc, 0x1f, 0x8f, 0xc8, 0x63, 0xb8, 0x4a, 0x7b, 0x3d, 0xec, 0x32, 0xb4, 0xad,
0x23, 0x0c, 0x42, 0x9e, 0x70, 0xbd, 0xb8, 0x34, 0x71, 0xa7, 0xdc, 0xba, 0xd6, 0x48, 0x96, 0xaf,
0xb1, 0x86, 0x94, 0x0d, 0x03, 0x34, 0x6b, 0x91, 0xfc, 0xae, 0x12, 0x37, 0x7e, 0xd7, 0xa0, 0xb8,
0x8a, 0x0c, 0xbb, 0xcc, 0x0f, 0x32, 0x8b, 0x52, 0x87, 0x82, 0xb2, 0xad, 0xaa, 0x11, 0x5d, 0x49,
0x0b, 0xf2, 0x36, 0x1b, 0x0d, 0x50, 0x54, 0x60, 0xaa, 0x75, 0x3d, 0xed, 0x32, 0x32, 0xda, 0x58,
0xdd, 0x19, 0x0d, 0xd0, 0x94, 0xa2, 0xc6, 0x97, 0x90, 0x17, 0x77, 0xb2, 0x08, 0xf3, 0xab, 0x9d,
0x9d, 0xce, 0xca, 0xce, 0x27, 0xa6, 0xb5, 0x6a, 0xed, 0x7c, 0xba, 0xd5, 0xb1, 0x36, 0x36, 0x77,
0xdb, 0x4f, 0x36, 0x56, 0x6b, 0x6f, 0x90, 0x1b, 0xb0, 0x70, 0x9a, 0xb9, 0xd9, 0x7e, 0xda, 0xd9,
0xde, 0x6a, 0xaf, 0x74, 0x6a, 0x5a, 0x96, 0xee, 0x5a, 0xa7, 0xbd, 0xf3, 0xcc, 0xec, 0xd4, 0x72,
0xc6, 0x36, 0x94, 0x36, 0xa3, 0x76, 0x65, 0x26, 0xd4, 0x82, 0xa2, 0xad, 0x62, 0x13, 0x19, 0x95,
0x5b, 0x73, 0xd9, 0x91, 0x9b, 0xb1, 0x9c, 0xf1, 0x6b, 0x0e, 0x0a, 0xaa, 0x86, 0x99, 0x36, 0x3f,
0x80, 0x52, 0x8c, 0x11, 0x65, 0x74, 0x3e, 0x6d, 0x34, 0x8e, 0xc9, 0x3c, 0x91, 0x4c, 0xd6, 0x76,
0x22, 0x5d, 0xdb, 0xdb, 0x30, 0xa5, 0x8e, 0x56, 0xcf, 0x0f, 0xfa, 0x94, 0x29, 0x2c, 0x55, 0x15,
0x75, 0x4d, 0x10, 0x53, 0xb9, 0xe4, 0xc7, 0xcb, 0x85, 0x74, 0x60, 0xfa, 0x28, 0xf1, 0x14, 0x1c,
0x0c, 0xeb, 0x93, 0x02, 0x33, 0x8b, 0x69, 0xd5, 0xd4, 0x7b, 0x31, 0x4f, 0xeb, 0x90, 0x65, 0xa8,
0xf4, 0x64, 0x45, 0x2c, 0x01, 0x02, 0x89, 0xcd, 0xb2, 0xa2, 0xf1, 0x1e, 0x1b, 0x8b, 0x90, 0x7f,
0x42, 0x47, 0x28, 0x70, 0x75, 0x40, 0xc3, 0x83, 0xa8, 0x64, 0xfc, 0x6c, 0x7c, 0xab, 0x41, 0x79,
0x85, 0x3b, 0xda, 0x66, 0x94, 0x0d, 0x43, 0x72, 0x1f, 0x4a, 0x51, 0x88, 0x61, 0x5d, 0x13, 0x01,
0x9d, 0x97, 0xcb, 0x89, 0x20, 0x59, 0x85, 0x9a, 0x4b, 0x43, 0x66, 0x0d, 0x07, 0x36, 0x65, 0x68,
0xf1, 0xa9, 0xa0, 0xea, 0xaf, 0x37, 0xe4, 0x44, 0x68, 0x44, 0x23, 0xa3, 0xb1, 0x13, 0x8d, 0x0c,
0x73, 0x8a, 0xeb, 0x3c, 0x13, 0x2a, 0x9c, 0x68, 0x3c, 0x04, 0xb2, 0x8e, 0xac, 0xed, 0x75, 0x31,
0x64, 0xc1, 0xc8, 0xc4, 0xaf, 0x86, 0x18, 0x32, 0xf2, 0x26, 0x54, 0xa9, 0x22, 0x59, 0x89, 0x8e,
0x57, 0x22, 0x22, 0x6f, 0xa9, 0xf1, 0xcb, 0x04, 0xcc, 0xa4, 0x74, 0xc3, 0x81, 0xef, 0x85, 0x48,
0xd6, 0xa0, 0x18, 0xc9, 0x09, 0xbd, 0x72, 0xeb, 0x6e, 0x3a, 0x9b, 0x0c, 0xa5, 0x46, 0x4c, 0x88,
0x75, 0xc9, 0xfb, 0x30, 0x19, 0x8a, 0x02, 0xa9, 0xb4, 0x16, 0xd2, 0x56, 0x12, 0x15, 0x34, 0x95,
0xa0, 0xfe, 0x35, 0x54, 0x23, 0x43, 0xb2, 0xfc, 0xef, 0x40, 0xde, 0xe5, 0x07, 0x15, 0xc8, 0x4c,
0xda, 0x84, 0x90, 0x31, 0xa5, 0x04, 0x1f, 0x29, 0xb2, 0xb8, 0x68, 0x5b, 0xaa, 0x95, 0xdc, 0xf3,
0x45, 0x23, 0x25, 0x92, 0x57, 0x84, 0x50, 0xff, 0x51, 0x83, 0x62, 0x14, 0x40, 0xe6, 0x6b, 0x49,
0xb5, 0x3a, 0x37, 0x6e, 0xab, 0xd7, 0x61, 0x52, 0xc4, 0x18, 0xd6, 0x27, 0x84, 0x4a, 0x73, 0xfc,
0x7a, 0xca, 0x14, 0x95, 0xba, 0xf1, 0x77, 0x0e, 0x66, 0xb6, 0xfc, 0xf0, 0xb5, 0xfa, 0x4d, 0xe6,
0x60, 0x52, 0x3d, 0x48, 0x39, 0x0d, 0xd5, 0x8d, 0xac, 0x9c, 0x8a, 0xee, 0xdd, 0x74, 0x74, 0x19,
0xfe, 0x04, 0x2d, 0x15, 0x99, 0xfe, 0x9b, 0x06, 0xa5, 0x98, 0x9a, 0xf5, 0x6a, 0x38, 0x6d, 0x40,
0xd9, 0x81, 0x72, 0x2e, 0xce, 0xc4, 0x84, 0xc2, 0x01, 0x52, 0xfb, 0xc4, 0xf7, 0x83, 0x57, 0xf0,
0xdd, 0xf8, 0x58, 0xaa, 0x76, 0x3c, 0xce, 0x8d, 0x0c, 0xe9, 0x8f, 0xa0, 0x92, 0x64, 0x90, 0x1a,
0x4c, 0x1c, 0xe2, 0x48, 0x85, 0xc2, 0x8f, 0x64, 0x16, 0xf2, 0x47, 0xd4, 0x1d, 0x46, 0x3b, 0x52,
0x5e, 0x1e, 0xe5, 0x1e, 0x68, 0xc6, 0x06, 0xcc, 0xa6, 0x5d, 0xaa, 0x27, 0x71, 0x02, 0x65, 0x6d,
0x4c, 0x28, 0x1b, 0x3f, 0x6b, 0x30, 0xb7, 0x8e, 0x6c, 0xd3, 0x67, 0x4e, 0xcf, 0xe9, 0x8a, 0x95,
0x1e, 0x75, 0xeb, 0x3e, 0xcc, 0xf9, 0xae, 0x6d, 0x25, 0xc7, 0xd2, 0xc8, 0x1a, 0xd0, 0xfd, 0xa8,
0x6d, 0xb3, 0xbe, 0x6b, 0xa7, 0x46, 0xd8, 0x16, 0xdd, 0xe7, 0xd0, 0x9b, 0xf3, 0xf0, 0x38, 0x4b,
0x4b, 0xa6, 0x31, 0xeb, 0xe1, 0xf1, 0x59, 0xad, 0x59, 0xc8, 0xbb, 0x4e, 0xdf, 0x61, 0x62, 0x4a,
0xe7, 0x4d, 0x79, 0x89, 0xa1, 0x7d, 0xe5, 0x04, 0xda, 0xc6, 0x5f, 0x39, 0x98, 0x3f, 0x13, 0xb0,
0xca, 0x7f, 0x17, 0x2a, 0x5e, 0x82, 0xae, 0xaa, 0xd0, 0x3a, 0x03, 0xe3, 0x2c, 0xe5, 0x46, 0x8a,
0x98, 0xb2, 0xa3, 0xff, 0xab, 0x41, 0x25, 0xc9, 0x3e, 0x6f, 0x8d, 0x77, 0x03, 0xa4, 0x0c, 0xed,
0x68, 0x8d, 0xab, 0x2b, 0xff, 0xf8, 0x90, 0xe6, 0xd0, 0x56, 0x5b, 0x28, 0xbe, 0x73, 0x2d, 0x1b,
0x5d, 0xe4, 0x5a, 0x32, 0xcb, 0xe8, 0x4a, 0x1e, 0xc2, 0x84, 0xef, 0xda, 0x6a, 0xe9, 0xbc, 0x7d,
0x0a, 0x70, 0x74, 0x1f, 0xe3, 0xda, 0xbb, 0xa8, 0x80, 0xe0, 0x60, 0x68, 0x72, 0x1d, 0xae, 0xea,
0xe1, 0xb1, 0xf8, 0xd0, 0x79, 0x15, 0x55, 0x0f, 0x8f, 0x8d, 0x3f, 0x72, 0xb0, 0x70, 0xae, 0x08,
0x5f, 0x49, 0xdd, 0x61, 0x10, 0xa0, 0xc7, 0x92, 0x40, 0x28, 0x2b, 0x9a, 0xe8, 0xe4, 0x22, 0x94,
0x3c, 0x7c, 0xce, 0x92, 0x2d, 0x2f, 0x72, 0xc2, 0x05, 0x6d, 0x6e, 0x43, 0x35, 0x05, 0x17, 0x51,
0x89, 0x4b, 0xb6, 0x65, 0x5a, 0x83, 0x7c, 0x0e, 0x40, 0xe3, 0x30, 0xeb, 0x79, 0xf1, 0x48, 0x3f,
0x1c, 0x33, 0xf1, 0xc6, 0x86, 0x67, 0xe3, 0x73, 0xb4, 0xdb, 0x89, 0x29, 0x64, 0x26, 0xcc, 0xe9,
0x1f, 0xc1, 0x4c, 0x86, 0x08, 0x4f, 0xc6, 0xe1, 0x64, 0x51, 0x85, 0xbc, 0x29, 0x2f, 0x31, 0x34,
0x72, 0x09, 0xcc, 0xde, 0x83, 0x1b, 0x4f, 0x69, 0x70, 0x98, 0x84, 0x50, 0x3b, 0x34, 0x91, 0xda,
0xd1, 0x53, 0xcb, 0xc0, 0x93, 0xb1, 0x04, 0x37, 0xcf, 0x53, 0x92, 0x88, 0x35, 0x08, 0xd4, 0xd6,
0x91, 0xa9, 0x07, 0x2d, 0x2d, 0x19, 0x6b, 0x70, 0x35, 0x41, 0x7b, 0xed, 0xb9, 0xd0, 0xfa, 0x4f,
0x83, 0xe9, 0x28, 0xdb, 0x6d, 0x0c, 0x8e, 0x9c, 0x2e, 0x92, 0x21, 0x94, 0x13, 0x3b, 0x80, 0x2c,
0x5d, 0xb0, 0x1e, 0x44, 0x30, 0xfa, 0xf2, 0xa5, 0x0b, 0xc4, 0x58, 0xfe, 0xe6, 0xcf, 0x7f, 0xbe,
0xcf, 0x2d, 0x92, 0x85, 0x66, 0xb4, 0x04, 0x9a, 0x2f, 0x52, 0x3b, 0xe2, 0x25, 0x39, 0x84, 0x4a,
0x72, 0xda, 0x91, 0xe5, 0x4b, 0x87, 0xaf, 0x6e, 0x5c, 0x24, 0xa2, 0x3c, 0xcf, 0x0a, 0xcf, 0x53,
0x46, 0x29, 0xf6, 0xfc, 0x48, 0xbb, 0xdb, 0xfa, 0x29, 0x07, 0x33, 0xc9, 0x92, 0x47, 0xb9, 0xbf,
0x84, 0xe9, 0x53, 0x83, 0x83, 0xbc, 0x75, 0xc9, 0x5c, 0x91, 0xa1, 0xdc, 0x1e, 0x6b, 0xfa, 0x18,
0x37, 0x44, 0x34, 0xf3, 0xe4, 0x5a, 0x33, 0x39, 0x79, 0xc2, 0xe6, 0x0b, 0x59, 0x83, 0xef, 0x34,
0x98, 0xcb, 0x46, 0x03, 0x39, 0xb5, 0x07, 0x2f, 0x04, 0x9a, 0xfe, 0xde, 0x78, 0xc2, 0xe9, 0xa0,
0xee, 0x66, 0x07, 0xd5, 0xf2, 0xa0, 0x2a, 0x51, 0x13, 0x15, 0xe9, 0x0b, 0x28, 0xc5, 0xe0, 0x23,
0x37, 0xcf, 0x24, 0x9e, 0x42, 0xaa, 0x7e, 0xeb, 0x5c, 0xbe, 0xf2, 0x3e, 0x2d, 0xbc, 0x97, 0x48,
0xa1, 0x29, 0x31, 0xf9, 0xf8, 0x26, 0xcc, 0x74, 0xfd, 0x7e, 0x5a, 0x6d, 0xb0, 0xf7, 0x59, 0x41,
0xfd, 0xb9, 0xdd, 0x9b, 0x14, 0x1f, 0xa2, 0xf7, 0xfe, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x1f, 0x9e,
0x9e, 0x7b, 0xf5, 0x0e, 0x00, 0x00,
}

@ -80,6 +80,9 @@ message Feature {
Detector detector = 5;
// The list of vulnerabilities that affect the feature.
repeated Vulnerability vulnerabilities = 6;
// The feature type indicates if the feature represents a source package or
// binary package.
string feature_type = 7;
}
message Layer {

@ -330,6 +330,10 @@
"$ref": "#/definitions/clairVulnerability"
},
"description": "The list of vulnerabilities that affect the feature."
},
"feature_type": {
"type": "string",
"description": "The feature type indicates if the feature represents a source package or\nbinary package."
}
}
},

@ -99,6 +99,7 @@ func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotifica
return &noti, nil
}
// VulnerabilityFromDatabaseModel converts database Vulnerability to api Vulnerability.
func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability) (*Vulnerability, error) {
metaString := ""
if dbVuln.Metadata != nil {
@ -119,6 +120,7 @@ func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability) (*Vulnerabili
}, nil
}
// VulnerabilityWithFixedInFromDatabaseModel converts database VulnerabilityWithFixedIn to api Vulnerability.
func VulnerabilityWithFixedInFromDatabaseModel(dbVuln database.VulnerabilityWithFixedIn) (*Vulnerability, error) {
vuln, err := VulnerabilityFromDatabaseModel(dbVuln.Vulnerability)
if err != nil {
@ -145,9 +147,11 @@ func NamespacedFeatureFromDatabaseModel(feature database.AncestryFeature) *Featu
VersionFormat: feature.Namespace.VersionFormat,
Version: version,
Detector: DetectorFromDatabaseModel(feature.FeatureBy),
FeatureType: string(feature.Type),
}
}
// DetectorFromDatabaseModel converts database detector to api detector.
func DetectorFromDatabaseModel(detector database.Detector) *Detector {
return &Detector{
Name: detector.Name,
@ -156,6 +160,7 @@ func DetectorFromDatabaseModel(detector database.Detector) *Detector {
}
}
// DetectorsFromDatabaseModel converts database detectors to api detectors.
func DetectorsFromDatabaseModel(dbDetectors []database.Detector) []*Detector {
detectors := make([]*Detector, 0, len(dbDetectors))
for _, d := range dbDetectors {

@ -17,7 +17,6 @@
package database
import (
"errors"
"fmt"
"time"
@ -27,20 +26,20 @@ import (
var (
// ErrBackendException is an error that occurs when the database backend
// does not work properly (ie. unreachable).
ErrBackendException = errors.New("database: an error occurred when querying the backend")
ErrBackendException = NewStorageError("an error occurred when querying the backend")
// ErrInconsistent is an error that occurs when a database consistency check
// fails (i.e. when an entity which is supposed to be unique is detected
// twice)
ErrInconsistent = errors.New("database: inconsistent database")
ErrInconsistent = NewStorageError("inconsistent database")
// ErrInvalidParameters is an error that occurs when the parameters are not valid.
ErrInvalidParameters = errors.New("database: parameters are not valid")
ErrInvalidParameters = NewStorageError("parameters are not valid")
// ErrMissingEntities is an error that occurs when an associated immutable
// entity doesn't exist in the database. This error can indicate a wrong
// implementation or corrupted database.
ErrMissingEntities = errors.New("database: associated immutable entities are missing in the database")
ErrMissingEntities = NewStorageError("associated immutable entities are missing in the database")
)
// RegistrableComponentConfig is a configuration block that can be used to

@ -1,4 +1,4 @@
// Copyright 2018 clair authors
// 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.
@ -14,13 +14,22 @@
package database
// AffectedFeatureType indicates the type of feature that a vulnerability
// affects.
type AffectedFeatureType string
// StorageError is database error
type StorageError struct {
reason string
original error
}
const (
// AffectSourcePackage indicates the vulnerability affects a source package.
AffectSourcePackage AffectedFeatureType = "source"
// AffectBinaryPackage indicates the vulnerability affects a binary package.
AffectBinaryPackage AffectedFeatureType = "binary"
)
func (e *StorageError) Error() string {
return e.reason
}
// NewStorageErrorWithInternalError creates a new database error
func NewStorageErrorWithInternalError(reason string, originalError error) *StorageError {
return &StorageError{reason, originalError}
}
// NewStorageError creates a new database error
func NewStorageError(reason string) *StorageError {
return &StorageError{reason, nil}
}

@ -0,0 +1,52 @@
// 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"
"fmt"
)
// FeatureType indicates the type of feature that a vulnerability
// affects.
type FeatureType string
const (
SourcePackage FeatureType = "source"
BinaryPackage FeatureType = "binary"
)
var featureTypes = []FeatureType{
SourcePackage,
BinaryPackage,
}
// Scan implements the database/sql.Scanner interface.
func (t *FeatureType) Scan(value interface{}) error {
val := value.(string)
for _, ft := range featureTypes {
if string(ft) == val {
*t = ft
return nil
}
}
panic(fmt.Sprintf("invalid feature type received from database: '%s'", val))
}
// Value implements the database/sql/driver.Valuer interface.
func (t *FeatureType) Value() (driver.Value, error) {
return string(*t), nil
}

@ -155,18 +155,33 @@ type Namespace struct {
VersionFormat string
}
func NewNamespace(name string, versionFormat string) *Namespace {
return &Namespace{name, versionFormat}
}
// Feature represents a package detected in a layer but the namespace is not
// determined.
//
// e.g. Name: Libssl1.0, Version: 1.0, Name: Openssl, Version: 1.0, VersionFormat: dpkg.
// 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
Version string
SourceName string
SourceVersion string
VersionFormat string
Type FeatureType
}
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
@ -179,6 +194,11 @@ type NamespacedFeature struct {
Namespace Namespace
}
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 {
@ -199,10 +219,10 @@ type VulnerabilityWithFixedIn struct {
// by a Vulnerability. Namespace and Feature Name is unique. Affected Feature is
// bound to vulnerability.
type AffectedFeature struct {
// AffectedType determines which type of package it affects.
AffectedType AffectedFeatureType
Namespace Namespace
FeatureName string
// 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.

@ -23,10 +23,11 @@ const (
findAncestryFeatures = `
SELECT namespace.name, namespace.version_format, feature.name,
feature.version, feature.version_format, ancestry_layer.ancestry_index,
feature.version, feature.version_format, feature_type.name, ancestry_layer.ancestry_index,
ancestry_feature.feature_detector_id, ancestry_feature.namespace_detector_id
FROM namespace, feature, namespaced_feature, ancestry_layer, ancestry_feature
FROM namespace, feature, feature_type, namespaced_feature, ancestry_layer, ancestry_feature
WHERE ancestry_layer.ancestry_id = $1
AND feature_type.id = feature.type
AND ancestry_feature.ancestry_layer_id = ancestry_layer.id
AND ancestry_feature.namespaced_feature_id = namespaced_feature.id
AND namespaced_feature.feature_id = feature.id
@ -256,6 +257,7 @@ func (tx *pgSession) findAncestryFeatures(ancestryID int64, detectors detectorMa
&feature.Feature.Name,
&feature.Feature.Version,
&feature.Feature.VersionFormat,
&feature.Feature.Type,
&index,
&featureDetectorID,
&namespaceDetectorID,

@ -25,6 +25,7 @@ import (
"github.com/pborman/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coreos/clair/database"
"github.com/coreos/clair/ext/versionfmt"
@ -65,19 +66,13 @@ func testGenRandomVulnerabilityAndNamespacedFeature(t *testing.T, store database
for i := 0; i < numFeatures; i++ {
version := rand.Intn(numFeatures)
features[i] = database.Feature{
Name: featureName,
VersionFormat: featureVersionFormat,
Version: strconv.Itoa(version),
}
features[i] = *database.NewSourcePackage(featureName, strconv.Itoa(version), featureVersionFormat)
nsFeatures[i] = database.NamespacedFeature{
Namespace: namespace,
Feature: features[i],
}
}
// insert features
if !assert.Nil(t, tx.PersistFeatures(features)) {
t.FailNow()
}
@ -98,6 +93,7 @@ func testGenRandomVulnerabilityAndNamespacedFeature(t *testing.T, store database
{
Namespace: namespace,
FeatureName: featureName,
FeatureType: database.SourcePackage,
AffectedVersion: strconv.Itoa(version),
FixedInVersion: strconv.Itoa(version),
},
@ -117,7 +113,6 @@ func TestConcurrency(t *testing.T) {
t.FailNow()
}
defer store.Close()
start := time.Now()
var wg sync.WaitGroup
wg.Add(100)
@ -137,74 +132,33 @@ func TestConcurrency(t *testing.T) {
fmt.Println("total", time.Since(start))
}
func genRandomNamespaces(t *testing.T, count int) []database.Namespace {
r := make([]database.Namespace, count)
for i := 0; i < count; i++ {
r[i] = database.Namespace{
Name: uuid.New(),
VersionFormat: "dpkg",
}
}
return r
}
func TestCaching(t *testing.T) {
store, err := openDatabaseForTest("Caching", false)
if !assert.Nil(t, err) {
t.FailNow()
}
require.Nil(t, err)
defer store.Close()
nsFeatures, vulnerabilities := testGenRandomVulnerabilityAndNamespacedFeature(t, store)
tx, err := store.Begin()
require.Nil(t, err)
fmt.Printf("%d features, %d vulnerabilities are generated", len(nsFeatures), len(vulnerabilities))
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
tx, err := store.Begin()
if !assert.Nil(t, err) {
t.FailNow()
}
assert.Nil(t, tx.PersistNamespacedFeatures(nsFeatures))
fmt.Println("finished to insert namespaced features")
tx.Commit()
}()
go func() {
defer wg.Done()
tx, err := store.Begin()
if !assert.Nil(t, err) {
t.FailNow()
}
assert.Nil(t, tx.InsertVulnerabilities(vulnerabilities))
fmt.Println("finished to insert vulnerabilities")
tx.Commit()
require.Nil(t, tx.PersistNamespacedFeatures(nsFeatures))
require.Nil(t, tx.Commit())
}()
tx, err = store.Begin()
require.Nil(t, tx.Commit())
wg.Wait()
require.Nil(t, tx.InsertVulnerabilities(vulnerabilities))
require.Nil(t, tx.Commit())
tx, err := store.Begin()
if !assert.Nil(t, err) {
t.FailNow()
}
tx, err = store.Begin()
require.Nil(t, err)
defer tx.Rollback()
// Verify consistency now.
affected, err := tx.FindAffectedNamespacedFeatures(nsFeatures)
if !assert.Nil(t, err) {
t.FailNow()
}
require.Nil(t, err)
for _, ansf := range affected {
if !assert.True(t, ansf.Valid) {
t.FailNow()
}
require.True(t, ansf.Valid)
expectedAffectedNames := []string{}
for _, vuln := range vulnerabilities {
@ -220,7 +174,7 @@ func TestCaching(t *testing.T) {
actualAffectedNames = append(actualAffectedNames, s.Name)
}
assert.Len(t, strutil.Difference(expectedAffectedNames, actualAffectedNames), 0)
assert.Len(t, strutil.Difference(actualAffectedNames, expectedAffectedNames), 0)
require.Len(t, strutil.Difference(expectedAffectedNames, actualAffectedNames), 0, "\nvulns: %#v\nfeature:%#v\nexpected:%#v\nactual:%#v", vulnerabilities, ansf.NamespacedFeature, expectedAffectedNames, actualAffectedNames)
require.Len(t, strutil.Difference(actualAffectedNames, expectedAffectedNames), 0)
}
}

@ -46,6 +46,7 @@ const (
AND nf.feature_id = f.id
AND nf.namespace_id = v.namespace_id
AND vaf.feature_name = f.name
AND vaf.feature_type = f.type
AND vaf.vulnerability_id = v.id
AND v.deleted_at IS NULL`
@ -68,6 +69,11 @@ func (tx *pgSession) PersistFeatures(features []database.Feature) error {
return nil
}
types, err := tx.getFeatureTypeMap()
if err != nil {
return err
}
// Sorting is needed before inserting into database to prevent deadlock.
sort.Slice(features, func(i, j int) bool {
return features[i].Name < features[j].Name ||
@ -78,13 +84,13 @@ func (tx *pgSession) PersistFeatures(features []database.Feature) error {
// TODO(Sida): A better interface for bulk insertion is needed.
keys := make([]interface{}, 0, len(features)*3)
for _, f := range features {
keys = append(keys, f.Name, f.Version, f.VersionFormat)
keys = append(keys, f.Name, f.Version, f.VersionFormat, types.byName[f.Type])
if f.Name == "" || f.Version == "" || f.VersionFormat == "" {
return commonerr.NewBadRequestError("Empty feature name, version or version format is not allowed")
}
}
_, err := tx.Exec(queryPersistFeature(len(features)), keys...)
_, err = tx.Exec(queryPersistFeature(len(features)), keys...)
return handleError("queryPersistFeature", err)
}
@ -240,52 +246,27 @@ func (tx *pgSession) PersistNamespacedFeatures(features []database.NamespacedFea
return nil
}
// FindAffectedNamespacedFeatures looks up cache table and retrieves all
// vulnerabilities associated with the features.
// FindAffectedNamespacedFeatures retrieves vulnerabilities associated with the
// feature.
func (tx *pgSession) FindAffectedNamespacedFeatures(features []database.NamespacedFeature) ([]database.NullableAffectedNamespacedFeature, error) {
if len(features) == 0 {
return nil, nil
}
returnFeatures := make([]database.NullableAffectedNamespacedFeature, len(features))
// featureMap is used to keep track of duplicated features.
featureMap := map[database.NamespacedFeature][]*database.NullableAffectedNamespacedFeature{}
// initialize return value and generate unique feature request queries.
for i, f := range features {
returnFeatures[i] = database.NullableAffectedNamespacedFeature{
AffectedNamespacedFeature: database.AffectedNamespacedFeature{
NamespacedFeature: f,
},
}
featureMap[f] = append(featureMap[f], &returnFeatures[i])
}
// query unique namespaced features
distinctFeatures := []database.NamespacedFeature{}
for f := range featureMap {
distinctFeatures = append(distinctFeatures, f)
}
nsFeatureIDs, err := tx.findNamespacedFeatureIDs(distinctFeatures)
vulnerableFeatures := make([]database.NullableAffectedNamespacedFeature, len(features))
featureIDs, err := tx.findNamespacedFeatureIDs(features)
if err != nil {
return nil, err
}
toQuery := []int64{}
featureIDMap := map[int64][]*database.NullableAffectedNamespacedFeature{}
for i, id := range nsFeatureIDs {
for i, id := range featureIDs {
if id.Valid {
toQuery = append(toQuery, id.Int64)
for _, f := range featureMap[distinctFeatures[i]] {
f.Valid = id.Valid
featureIDMap[id.Int64] = append(featureIDMap[id.Int64], f)
}
vulnerableFeatures[i].Valid = true
vulnerableFeatures[i].NamespacedFeature = features[i]
}
}
rows, err := tx.Query(searchNamespacedFeaturesVulnerabilities, pq.Array(toQuery))
rows, err := tx.Query(searchNamespacedFeaturesVulnerabilities, pq.Array(featureIDs))
if err != nil {
return nil, handleError("searchNamespacedFeaturesVulnerabilities", err)
}
@ -296,6 +277,7 @@ func (tx *pgSession) FindAffectedNamespacedFeatures(features []database.Namespac
featureID int64
vuln database.VulnerabilityWithFixedIn
)
err := rows.Scan(&featureID,
&vuln.Name,
&vuln.Description,
@ -306,16 +288,19 @@ func (tx *pgSession) FindAffectedNamespacedFeatures(features []database.Namespac
&vuln.Namespace.Name,
&vuln.Namespace.VersionFormat,
)
if err != nil {
return nil, handleError("searchNamespacedFeaturesVulnerabilities", err)
}
for _, f := range featureIDMap[featureID] {
f.AffectedBy = append(f.AffectedBy, vuln)
for i, id := range featureIDs {
if id.Valid && id.Int64 == featureID {
vulnerableFeatures[i].AffectedNamespacedFeature.AffectedBy = append(vulnerableFeatures[i].AffectedNamespacedFeature.AffectedBy, vuln)
}
}
}
return returnFeatures, nil
return vulnerableFeatures, nil
}
func (tx *pgSession) findNamespacedFeatureIDs(nfs []database.NamespacedFeature) ([]sql.NullInt64, error) {
@ -323,11 +308,10 @@ func (tx *pgSession) findNamespacedFeatureIDs(nfs []database.NamespacedFeature)
return nil, nil
}
nfsMap := map[database.NamespacedFeature]sql.NullInt64{}
keys := make([]interface{}, 0, len(nfs)*4)
nfsMap := map[database.NamespacedFeature]int64{}
keys := make([]interface{}, 0, len(nfs)*5)
for _, nf := range nfs {
keys = append(keys, nf.Name, nf.Version, nf.VersionFormat, nf.Namespace.Name)
nfsMap[nf] = sql.NullInt64{}
keys = append(keys, nf.Name, nf.Version, nf.VersionFormat, nf.Type, nf.Namespace.Name)
}
rows, err := tx.Query(querySearchNamespacedFeature(len(nfs)), keys...)
@ -337,12 +321,12 @@ func (tx *pgSession) findNamespacedFeatureIDs(nfs []database.NamespacedFeature)
defer rows.Close()
var (
id sql.NullInt64
id int64
nf database.NamespacedFeature
)
for rows.Next() {
err := rows.Scan(&id, &nf.Name, &nf.Version, &nf.VersionFormat, &nf.Namespace.Name)
err := rows.Scan(&id, &nf.Name, &nf.Version, &nf.VersionFormat, &nf.Type, &nf.Namespace.Name)
nf.Namespace.VersionFormat = nf.VersionFormat
if err != nil {
return nil, handleError("searchNamespacedFeature", err)
@ -352,7 +336,11 @@ func (tx *pgSession) findNamespacedFeatureIDs(nfs []database.NamespacedFeature)
ids := make([]sql.NullInt64, len(nfs))
for i, nf := range nfs {
ids[i] = nfsMap[nf]
if id, ok := nfsMap[nf]; ok {
ids[i] = sql.NullInt64{id, true}
} else {
ids[i] = sql.NullInt64{}
}
}
return ids, nil
@ -363,11 +351,17 @@ func (tx *pgSession) findFeatureIDs(fs []database.Feature) ([]sql.NullInt64, err
return nil, nil
}
types, err := tx.getFeatureTypeMap()
if err != nil {
return nil, err
}
fMap := map[database.Feature]sql.NullInt64{}
keys := make([]interface{}, 0, len(fs)*3)
keys := make([]interface{}, 0, len(fs)*4)
for _, f := range fs {
keys = append(keys, f.Name, f.Version, f.VersionFormat)
typeID := types.byName[f.Type]
keys = append(keys, f.Name, f.Version, f.VersionFormat, typeID)
fMap[f] = sql.NullInt64{}
}
@ -382,10 +376,13 @@ func (tx *pgSession) findFeatureIDs(fs []database.Feature) ([]sql.NullInt64, err
f database.Feature
)
for rows.Next() {
err := rows.Scan(&id, &f.Name, &f.Version, &f.VersionFormat)
var typeID int
err := rows.Scan(&id, &f.Name, &f.Version, &f.VersionFormat, &typeID)
if err != nil {
return nil, handleError("querySearchFeatureID", err)
}
f.Type = types.byID[typeID]
fMap[f] = id
}

@ -18,134 +18,53 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coreos/clair/database"
// register dpkg feature lister for testing
_ "github.com/coreos/clair/ext/featurefmt/dpkg"
)
func TestPersistFeatures(t *testing.T) {
datastore, tx := openSessionForTest(t, "PersistFeatures", false)
defer closeTest(t, datastore, tx)
tx, cleanup := createTestPgSession(t, "TestPersistFeatures")
defer cleanup()
f1 := database.Feature{}
f2 := database.Feature{Name: "n", Version: "v", VersionFormat: "vf"}
invalid := database.Feature{}
valid := *database.NewBinaryPackage("mount", "2.31.1-0.4ubuntu3.1", "dpkg")
// empty
assert.Nil(t, tx.PersistFeatures([]database.Feature{}))
// invalid
assert.NotNil(t, tx.PersistFeatures([]database.Feature{f1}))
// duplicated
assert.Nil(t, tx.PersistFeatures([]database.Feature{f2, f2}))
require.NotNil(t, tx.PersistFeatures([]database.Feature{invalid}))
// existing
assert.Nil(t, tx.PersistFeatures([]database.Feature{f2}))
require.Nil(t, tx.PersistFeatures([]database.Feature{valid}))
require.Nil(t, tx.PersistFeatures([]database.Feature{valid}))
fs := listFeatures(t, tx)
assert.Len(t, fs, 1)
assert.Equal(t, f2, fs[0])
features := selectAllFeatures(t, tx)
assert.Equal(t, []database.Feature{valid}, features)
}
func TestPersistNamespacedFeatures(t *testing.T) {
datastore, tx := openSessionForTest(t, "PersistNamespacedFeatures", true)
defer closeTest(t, datastore, tx)
tx, cleanup := createTestPgSessionWithFixtures(t, "TestPersistNamespacedFeatures")
defer cleanup()
// existing features
f1 := database.Feature{
Name: "ourchat",
Version: "0.5",
VersionFormat: "dpkg",
}
f1 := database.NewSourcePackage("ourchat", "0.5", "dpkg")
// non-existing features
f2 := database.Feature{
Name: "fake!",
}
f3 := database.Feature{
Name: "openssl",
Version: "2.0",
VersionFormat: "dpkg",
}
f2 := database.NewSourcePackage("fake!", "", "")
// exising namespace
n1 := database.Namespace{
Name: "debian:7",
VersionFormat: "dpkg",
}
n3 := database.Namespace{
Name: "debian:8",
VersionFormat: "dpkg",
}
n1 := database.NewNamespace("debian:7", "dpkg")
// non-existing namespace
n2 := database.Namespace{
Name: "debian:non",
VersionFormat: "dpkg",
}
n2 := database.NewNamespace("debian:non", "dpkg")
// existing namespaced feature
nf1 := database.NamespacedFeature{
Namespace: n1,
Feature: f1,
}
nf1 := database.NewNamespacedFeature(n1, f1)
// invalid namespaced feature
nf2 := database.NamespacedFeature{
Namespace: n2,
Feature: f2,
}
// new namespaced feature affected by vulnerability
nf3 := database.NamespacedFeature{
Namespace: n3,
Feature: f3,
}
nf2 := database.NewNamespacedFeature(n2, f2)
// namespaced features with namespaces or features not in the database will
// generate error.
assert.Nil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{}))
assert.NotNil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{nf1, nf2}))
assert.NotNil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{*nf1, *nf2}))
// valid case: insert nf3
assert.Nil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{nf1, nf3}))
assert.Nil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{*nf1}))
all := listNamespacedFeatures(t, tx)
assert.Contains(t, all, nf1)
assert.Contains(t, all, nf3)
}
func TestVulnerableFeature(t *testing.T) {
datastore, tx := openSessionForTest(t, "VulnerableFeature", true)
defer closeTest(t, datastore, tx)
f1 := database.Feature{
Name: "openssl",
Version: "1.3",
VersionFormat: "dpkg",
}
n1 := database.Namespace{
Name: "debian:7",
VersionFormat: "dpkg",
}
nf1 := database.NamespacedFeature{
Namespace: n1,
Feature: f1,
}
assert.Nil(t, tx.PersistFeatures([]database.Feature{f1}))
assert.Nil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{nf1}))
assert.Nil(t, tx.CacheAffectedNamespacedFeatures([]database.NamespacedFeature{nf1}))
// ensure the namespaced feature is affected correctly
anf, err := tx.FindAffectedNamespacedFeatures([]database.NamespacedFeature{nf1})
if assert.Nil(t, err) &&
assert.Len(t, anf, 1) &&
assert.True(t, anf[0].Valid) &&
assert.Len(t, anf[0].AffectedBy, 1) {
assert.Equal(t, "CVE-OPENSSL-1-DEB7", anf[0].AffectedBy[0].Name)
}
assert.Contains(t, all, *nf1)
}
func TestFindAffectedNamespacedFeatures(t *testing.T) {
@ -156,6 +75,7 @@ func TestFindAffectedNamespacedFeatures(t *testing.T) {
Name: "openssl",
Version: "1.0",
VersionFormat: "dpkg",
Type: database.SourcePackage,
},
Namespace: database.Namespace{
Name: "debian:7",
@ -173,30 +93,41 @@ func TestFindAffectedNamespacedFeatures(t *testing.T) {
}
func listNamespacedFeatures(t *testing.T, tx *pgSession) []database.NamespacedFeature {
rows, err := tx.Query(`SELECT f.name, f.version, f.version_format, n.name, n.version_format
types, err := tx.getFeatureTypeMap()
if err != nil {
panic(err)
}
rows, err := tx.Query(`SELECT f.name, f.version, f.version_format, f.type, n.name, n.version_format
FROM feature AS f, namespace AS n, namespaced_feature AS nf
WHERE nf.feature_id = f.id AND nf.namespace_id = n.id`)
if err != nil {
t.Error(err)
t.FailNow()
panic(err)
}
nf := []database.NamespacedFeature{}
for rows.Next() {
f := database.NamespacedFeature{}
err := rows.Scan(&f.Name, &f.Version, &f.VersionFormat, &f.Namespace.Name, &f.Namespace.VersionFormat)
var typeID int
err := rows.Scan(&f.Name, &f.Version, &f.VersionFormat, &typeID, &f.Namespace.Name, &f.Namespace.VersionFormat)
if err != nil {
t.Error(err)
t.FailNow()
panic(err)
}
f.Type = types.byID[typeID]
nf = append(nf, f)
}
return nf
}
func listFeatures(t *testing.T, tx *pgSession) []database.Feature {
rows, err := tx.Query("SELECT name, version, version_format FROM feature")
func selectAllFeatures(t *testing.T, tx *pgSession) []database.Feature {
types, err := tx.getFeatureTypeMap()
if err != nil {
panic(err)
}
rows, err := tx.Query("SELECT name, version, version_format, type FROM feature")
if err != nil {
t.FailNow()
}
@ -204,7 +135,9 @@ func listFeatures(t *testing.T, tx *pgSession) []database.Feature {
fs := []database.Feature{}
for rows.Next() {
f := database.Feature{}
err := rows.Scan(&f.Name, &f.Version, &f.VersionFormat)
var typeID int
err := rows.Scan(&f.Name, &f.Version, &f.VersionFormat, &typeID)
f.Type = types.byID[typeID]
if err != nil {
t.FailNow()
}
@ -233,3 +166,33 @@ func assertNamespacedFeatureEqual(t *testing.T, expected []database.NamespacedFe
}
return false
}
func TestFindNamespacedFeatureIDs(t *testing.T) {
tx, cleanup := createTestPgSessionWithFixtures(t, "TestFindNamespacedFeatureIDs")
defer cleanup()
features := []database.NamespacedFeature{}
expectedIDs := []int{}
for id, feature := range realNamespacedFeatures {
features = append(features, feature)
expectedIDs = append(expectedIDs, id)
}
features = append(features, realNamespacedFeatures[1]) // test duplicated
expectedIDs = append(expectedIDs, 1)
namespace := realNamespaces[1]
features = append(features, *database.NewNamespacedFeature(&namespace, database.NewBinaryPackage("not-found", "1.0", "dpkg"))) // test not found feature
ids, err := tx.findNamespacedFeatureIDs(features)
require.Nil(t, err)
require.Len(t, ids, len(expectedIDs)+1)
for i, id := range ids {
if i == len(ids)-1 {
require.False(t, id.Valid)
} else {
require.True(t, id.Valid)
require.Equal(t, expectedIDs[i], int(id.Int64))
}
}
}

@ -0,0 +1,53 @@
// 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 pgsql
import "github.com/coreos/clair/database"
const (
selectAllFeatureTypes = `SELECT id, name FROM feature_type`
)
type featureTypes struct {
byID map[int]database.FeatureType
byName map[database.FeatureType]int
}
func newFeatureTypes() *featureTypes {
return &featureTypes{make(map[int]database.FeatureType), make(map[database.FeatureType]int)}
}
func (tx *pgSession) getFeatureTypeMap() (*featureTypes, error) {
rows, err := tx.Query(selectAllFeatureTypes)
if err != nil {
return nil, err
}
types := newFeatureTypes()
for rows.Next() {
var (
id int
name database.FeatureType
)
if err := rows.Scan(&id, &name); err != nil {
return nil, err
}
types.byID[id] = name
types.byName[name] = id
}
return types, nil
}

@ -0,0 +1,38 @@
// 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 pgsql
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coreos/clair/database"
)
func TestGetFeatureTypeMap(t *testing.T) {
tx, cleanup := createTestPgSession(t, "TestGetFeatureTypeMap")
defer cleanup()
types, err := tx.getFeatureTypeMap()
if err != nil {
require.Nil(t, err, err.Error())
}
require.Equal(t, database.SourcePackage, types.byID[1])
require.Equal(t, database.BinaryPackage, types.byID[2])
require.Equal(t, 1, types.byName[database.SourcePackage])
require.Equal(t, 2, types.byName[database.BinaryPackage])
}

@ -37,9 +37,10 @@ const (
SELECT id FROM layer WHERE hash = $1`
findLayerFeatures = `
SELECT f.name, f.version, f.version_format, lf.detector_id
FROM layer_feature AS lf, feature AS f
SELECT f.name, f.version, f.version_format, t.name, lf.detector_id
FROM layer_feature AS lf, feature AS f, feature_type AS t
WHERE lf.feature_id = f.id
AND t.id = f.type
AND lf.layer_id = $1`
findLayerNamespaces = `
@ -307,7 +308,7 @@ func (tx *pgSession) findLayerFeatures(layerID int64, detectors detectorMap) ([]
detectorID int64
feature database.LayerFeature
)
if err := rows.Scan(&feature.Name, &feature.Version, &feature.VersionFormat, &detectorID); err != nil {
if err := rows.Scan(&feature.Name, &feature.Version, &feature.VersionFormat, &feature.Type, &detectorID); err != nil {
return nil, handleError("findLayerFeatures", err)
}

@ -43,12 +43,12 @@ var persistLayerTests = []struct {
features: []database.LayerFeature{
{realFeatures[1], realDetectors[1]},
},
err: "database: parameters are not valid",
err: "parameters are not valid",
},
{
title: "layer with non-existing feature",
name: "random-forest",
err: "database: associated immutable entities are missing in the database",
err: "associated immutable entities are missing in the database",
by: []database.Detector{realDetectors[2]},
features: []database.LayerFeature{
{fakeFeatures[1], realDetectors[2]},
@ -57,7 +57,7 @@ var persistLayerTests = []struct {
{
title: "layer with non-existing namespace",
name: "random-forest2",
err: "database: associated immutable entities are missing in the database",
err: "associated immutable entities are missing in the database",
by: []database.Detector{realDetectors[1]},
namespaces: []database.LayerNamespace{
{fakeNamespaces[1], realDetectors[1]},
@ -66,7 +66,7 @@ var persistLayerTests = []struct {
{
title: "layer with non-existing detector",
name: "random-forest3",
err: "database: associated immutable entities are missing in the database",
err: "associated immutable entities are missing in the database",
by: []database.Detector{fakeDetector[1]},
},
{

@ -19,7 +19,12 @@ var (
// the ancestry.
entities = MigrationQuery{
Up: []string{
// namespaces
`CREATE TABLE IF NOT EXISTS feature_type (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE);`,
`INSERT INTO feature_type(name) VALUES ('source'), ('binary')`,
`CREATE TABLE IF NOT EXISTS namespace (
id SERIAL PRIMARY KEY,
name TEXT NULL,
@ -27,13 +32,13 @@ var (
UNIQUE (name, version_format));`,
`CREATE INDEX ON namespace(name);`,
// features
`CREATE TABLE IF NOT EXISTS feature (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
version TEXT NOT NULL,
version_format TEXT NOT NULL,
UNIQUE (name, version, version_format));`,
type INT REFERENCES feature_type ON DELETE CASCADE,
UNIQUE (name, version, version_format, type));`,
`CREATE INDEX ON feature(name);`,
`CREATE TABLE IF NOT EXISTS namespaced_feature (
@ -43,17 +48,15 @@ var (
UNIQUE (namespace_id, feature_id));`,
},
Down: []string{
`DROP TABLE IF EXISTS namespace, feature, namespaced_feature CASCADE;`,
`DROP TABLE IF EXISTS namespace, feature, namespaced_feature, feature_type CASCADE;`,
},
}
// detector is analysis extensions used by the worker.
detector = MigrationQuery{
Up: []string{
// Detector Type
`CREATE TYPE detector_type AS ENUM ('namespace', 'feature');`,
// Detector
`CREATE TABLE IF NOT EXISTS detector (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
@ -70,7 +73,6 @@ var (
// layer contains all metadata and scanned features and namespaces.
layer = MigrationQuery{
Up: []string{
// layers
`CREATE TABLE IF NOT EXISTS layer(
id SERIAL PRIMARY KEY,
hash TEXT NOT NULL UNIQUE);`,
@ -107,7 +109,6 @@ var (
// layers.
ancestry = MigrationQuery{
Up: []string{
// ancestry
`CREATE TABLE IF NOT EXISTS ancestry (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE);`,
@ -145,7 +146,6 @@ var (
Up: []string{
`CREATE TYPE severity AS ENUM ('Unknown', 'Negligible', 'Low', 'Medium', 'High', 'Critical', 'Defcon1');`,
// vulnerability
`CREATE TABLE IF NOT EXISTS vulnerability (
id SERIAL PRIMARY KEY,
namespace_id INT REFERENCES Namespace,
@ -159,13 +159,18 @@ var (
`CREATE INDEX ON vulnerability(namespace_id, name);`,
`CREATE INDEX ON vulnerability(namespace_id);`,
// vulnerability_affected_feature is a de-normalized table to store
// the affected features in a independent place other than the
// feature table to reduce table lock issue, and makes it easier for
// decoupling updater and the Clair main logic.
`CREATE TABLE IF NOT EXISTS vulnerability_affected_feature (
id SERIAL PRIMARY KEY,
vulnerability_id INT REFERENCES vulnerability ON DELETE CASCADE,
feature_name TEXT NOT NULL,
feature_type INT NOT NULL REFERENCES feature_type ON DELETE CASCADE,
affected_version TEXT,
fixedin TEXT);`,
`CREATE INDEX ON vulnerability_affected_feature(vulnerability_id, feature_name);`,
`CREATE INDEX ON vulnerability_affected_feature(vulnerability_id, feature_name, feature_type);`,
`CREATE TABLE IF NOT EXISTS vulnerability_affected_namespaced_feature(
id SERIAL PRIMARY KEY,
@ -176,8 +181,8 @@ var (
`CREATE INDEX ON vulnerability_affected_namespaced_feature(namespaced_feature_id);`,
},
Down: []string{
`DROP TYPE IF EXISTS severity;`,
`DROP TABLE IF EXISTS vulnerability, vulnerability_affected_feature, vulnerability_affected_namespaced_feature CASCADE;`,
`DROP TYPE IF EXISTS severity;`,
},
}

@ -0,0 +1,60 @@
// 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 pgsql
import (
"testing"
_ "github.com/lib/pq"
"github.com/remind101/migrate"
"github.com/stretchr/testify/require"
"github.com/coreos/clair/database/pgsql/migrations"
)
var userTableCount = `SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname='public'`
func TestMigration(t *testing.T) {
db, cleanup := createAndConnectTestDB(t, "TestMigration")
defer cleanup()
err := migrate.NewPostgresMigrator(db).Exec(migrate.Up, migrations.Migrations...)
if err != nil {
require.Nil(t, err, err.Error())
}
err = migrate.NewPostgresMigrator(db).Exec(migrate.Down, migrations.Migrations...)
if err != nil {
require.Nil(t, err, err.Error())
}
rows, err := db.Query(userTableCount)
if err != nil {
panic(err)
}
var (
tables []string
table string
)
for rows.Next() {
if err = rows.Scan(&table); err != nil {
panic(err)
}
tables = append(tables, table)
}
require.True(t, len(tables) == 1 && tables[0] == "schema_migrations", "Only `schema_migrations` should be left")
}

@ -211,8 +211,8 @@ func TestInsertVulnerabilityNotifications(t *testing.T) {
}
func TestFindNewNotification(t *testing.T) {
datastore, tx := openSessionForTest(t, "FindNewNotification", true)
defer closeTest(t, datastore, tx)
tx, cleanup := createTestPgSessionWithFixtures(t, "TestFindNewNotification")
defer cleanup()
noti, ok, err := tx.FindNewNotification(time.Now())
if assert.Nil(t, err) && assert.True(t, ok) {
@ -229,7 +229,7 @@ func TestFindNewNotification(t *testing.T) {
assert.Nil(t, err)
assert.False(t, ok)
// can find the notified after a period of time
noti, ok, err = tx.FindNewNotification(time.Now().Add(time.Duration(1000)))
noti, ok, err = tx.FindNewNotification(time.Now().Add(time.Duration(10 * time.Second)))
if assert.Nil(t, err) && assert.True(t, ok) {
assert.Equal(t, "test", noti.Name)
assert.NotEqual(t, time.Time{}, noti.Notified)

@ -109,6 +109,7 @@ func dropTemplateDatabase(url string, name string) {
}
}
func TestMain(m *testing.M) {
fURL, fName := genTemplateDatabase("fixture", true)
nfURL, nfName := genTemplateDatabase("nonfixture", false)

@ -52,21 +52,22 @@ func querySearchNotDeletedVulnerabilityID(count int) string {
func querySearchFeatureID(featureCount int) string {
return fmt.Sprintf(`
SELECT id, name, version, version_format
FROM Feature WHERE (name, version, version_format) IN (%s)`,
queryString(3, featureCount),
SELECT id, name, version, version_format, type
FROM Feature WHERE (name, version, version_format, type) IN (%s)`,
queryString(4, featureCount),
)
}
func querySearchNamespacedFeature(nsfCount int) string {
return fmt.Sprintf(`
SELECT nf.id, f.name, f.version, f.version_format, n.name
FROM namespaced_feature AS nf, feature AS f, namespace AS n
SELECT nf.id, f.name, f.version, f.version_format, t.name, n.name
FROM namespaced_feature AS nf, feature AS f, namespace AS n, feature_type AS t
WHERE nf.feature_id = f.id
AND nf.namespace_id = n.id
AND n.version_format = f.version_format
AND (f.name, f.version, f.version_format, n.name) IN (%s)`,
queryString(4, nsfCount),
AND f.type = t.id
AND (f.name, f.version, f.version_format, t.name, n.name) IN (%s)`,
queryString(5, nsfCount),
)
}
@ -110,10 +111,11 @@ func queryInsertNotifications(count int) string {
func queryPersistFeature(count int) string {
return queryPersist(count,
"feature",
"feature_name_version_version_format_key",
"feature_name_version_version_format_type_key",
"name",
"version",
"version_format")
"version_format",
"type")
}
func queryPersistLayerFeature(count int) string {

@ -4,11 +4,12 @@ INSERT INTO namespace (id, name, version_format) VALUES
(2, 'debian:8', 'dpkg'),
(3, 'fake:1.0', 'rpm');
INSERT INTO feature (id, name, version, version_format) VALUES
(1, 'ourchat', '0.5', 'dpkg'),
(2, 'openssl', '1.0', 'dpkg'),
(3, 'openssl', '2.0', 'dpkg'),
(4, 'fake', '2.0', 'rpm');
INSERT INTO feature (id, name, version, version_format, type) VALUES
(1, 'ourchat', '0.5', 'dpkg', 1),
(2, 'openssl', '1.0', 'dpkg', 1),
(3, 'openssl', '2.0', 'dpkg', 1),
(4, 'fake', '2.0', 'rpm', 1),
(5, 'mount', '2.31.1-0.4ubuntu3.1', 'dpkg', 2);
INSERT INTO namespaced_feature(id, feature_id, namespace_id) VALUES
(1, 1, 1), -- ourchat 0.5, debian:7
@ -112,9 +113,9 @@ INSERT INTO vulnerability (id, namespace_id, name, description, link, severity)
INSERT INTO vulnerability (id, namespace_id, name, description, link, severity, deleted_at) VALUES
(3, 1, 'CVE-DELETED', '', '', 'Unknown', '2017-08-08 17:49:31.668483');
INSERT INTO vulnerability_affected_feature(id, vulnerability_id, feature_name, affected_version, fixedin) VALUES
(1, 1, 'openssl', '2.0', '2.0'),
(2, 1, 'libssl', '1.9-abc', '1.9-abc');
INSERT INTO vulnerability_affected_feature(id, vulnerability_id, feature_name, affected_version, fixedin, feature_type) VALUES
(1, 1, 'openssl', '2.0', '2.0', 1),
(2, 1, 'libssl', '1.9-abc', '1.9-abc', 1);
INSERT INTO vulnerability_affected_namespaced_feature(id, vulnerability_id, namespaced_feature_id, added_by) VALUES
(1, 1, 2, 1);

@ -15,21 +15,34 @@
package pgsql
import (
"database/sql"
"fmt"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/remind101/migrate"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coreos/clair/database"
"github.com/coreos/clair/database/pgsql/migrations"
"github.com/coreos/clair/pkg/pagination"
)
// int keys must be the consistent with the database ID.
var (
realFeatures = map[int]database.Feature{
1: {"ourchat", "0.5", "ourchat", "0.5", "dpkg"},
2: {"openssl", "1.0", "openssl", "1.0", "dpkg"},
3: {"openssl", "2.0", "openssl", "2.0", "dpkg"},
4: {"fake", "2.0", "fake", "2.0", "rpm"},
1: {"ourchat", "0.5", "dpkg", "source"},
2: {"openssl", "1.0", "dpkg", "source"},
3: {"openssl", "2.0", "dpkg", "source"},
4: {"fake", "2.0", "rpm", "source"},
5: {"mount", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
}
realNamespaces = map[int]database.Namespace{
@ -146,6 +159,7 @@ var (
Name: "ourchat",
Version: "0.6",
VersionFormat: "dpkg",
Type: "source",
},
}
@ -260,3 +274,150 @@ func mustMarshalToken(key pagination.Key, v interface{}) pagination.Token {
return token
}
var userDBCount = `SELECT count(datname) FROM pg_database WHERE datistemplate = FALSE AND datname != 'postgres';`
func createAndConnectTestDB(t *testing.T, testName string) (*sql.DB, func()) {
uri := "postgres@127.0.0.1:5432"
connectionTemplate := "postgresql://%s?sslmode=disable"
if envURI := os.Getenv("CLAIR_TEST_PGSQL"); envURI != "" {
uri = envURI
}
db, err := sql.Open("postgres", fmt.Sprintf(connectionTemplate, uri))
if err != nil {
panic(err)
}
testName = strings.ToLower(testName)
dbName := fmt.Sprintf("test_%s_%s", testName, time.Now().UTC().Format("2006_01_02_15_04_05"))
t.Logf("creating temporary database name = %s", dbName)
_, err = db.Exec("CREATE DATABASE " + dbName)
if err != nil {
panic(err)
}
testDB, err := sql.Open("postgres", fmt.Sprintf(connectionTemplate, uri+"/"+dbName))
if err != nil {
panic(err)
}
return testDB, func() {
t.Logf("cleaning up temporary database %s", dbName)
defer db.Close()
if err := testDB.Close(); err != nil {
panic(err)
}
if _, err := db.Exec(`DROP DATABASE ` + dbName); err != nil {
panic(err)
}
// ensure the database is cleaned up
var count int
if err := db.QueryRow(userDBCount).Scan(&count); err != nil {
panic(err)
}
}
}
func createTestPgSQL(t *testing.T, testName string) (*pgSQL, func()) {
connection, cleanup := createAndConnectTestDB(t, testName)
err := migrate.NewPostgresMigrator(connection).Exec(migrate.Up, migrations.Migrations...)
if err != nil {
require.Nil(t, err, err.Error())
}
return &pgSQL{connection, nil, Config{PaginationKey: pagination.Must(pagination.NewKey()).String()}}, cleanup
}
func createTestPgSQLWithFixtures(t *testing.T, testName string) (*pgSQL, func()) {
connection, cleanup := createTestPgSQL(t, testName)
session, err := connection.Begin()
if err != nil {
panic(err)
}
defer session.Rollback()
loadFixtures(session.(*pgSession))
if err = session.Commit(); err != nil {
panic(err)
}
return connection, cleanup
}
func createTestPgSession(t *testing.T, testName string) (*pgSession, func()) {
connection, cleanup := createTestPgSQL(t, testName)
session, err := connection.Begin()
if err != nil {
panic(err)
}
return session.(*pgSession), func() {
session.Rollback()
cleanup()
}
}
func createTestPgSessionWithFixtures(t *testing.T, testName string) (*pgSession, func()) {
tx, cleanup := createTestPgSession(t, testName)
defer func() {
// ensure to cleanup when loadFixtures failed
if r := recover(); r != nil {
cleanup()
}
}()
loadFixtures(tx)
return tx, cleanup
}
func loadFixtures(tx *pgSession) {
_, filename, _, _ := runtime.Caller(0)
fixturePath := filepath.Join(filepath.Dir(filename)) + "/testdata/data.sql"
d, err := ioutil.ReadFile(fixturePath)
if err != nil {
panic(err)
}
_, err = tx.Exec(string(d))
if err != nil {
panic(err)
}
}
func assertVulnerabilityWithAffectedEqual(t *testing.T, expected database.VulnerabilityWithAffected, actual database.VulnerabilityWithAffected) bool {
return assert.Equal(t, expected.Vulnerability, actual.Vulnerability) && assertAffectedFeaturesEqual(t, expected.Affected, actual.Affected)
}
func assertAffectedFeaturesEqual(t *testing.T, expected []database.AffectedFeature, actual []database.AffectedFeature) bool {
if assert.Len(t, actual, len(expected)) {
has := map[database.AffectedFeature]bool{}
for _, i := range expected {
has[i] = false
}
for _, i := range actual {
if visited, ok := has[i]; !ok {
return false
} else if visited {
return false
}
has[i] = true
}
return true
}
return false
}
func genRandomNamespaces(t *testing.T, count int) []database.Namespace {
r := make([]database.Namespace, count)
for i := 0; i < count; i++ {
r[i] = database.Namespace{
Name: fmt.Sprint(rand.Int()),
VersionFormat: "dpkg",
}
}
return r
}

@ -39,15 +39,15 @@ const (
`
insertVulnerabilityAffected = `
INSERT INTO vulnerability_affected_feature(vulnerability_id, feature_name, affected_version, fixedin)
VALUES ($1, $2, $3, $4)
INSERT INTO vulnerability_affected_feature(vulnerability_id, feature_name, affected_version, feature_type, fixedin)
VALUES ($1, $2, $3, $4, $5)
RETURNING ID
`
searchVulnerabilityAffected = `
SELECT vulnerability_id, feature_name, affected_version, fixedin
FROM vulnerability_affected_feature
WHERE vulnerability_id = ANY($1)
SELECT vulnerability_id, feature_name, affected_version, t.name, fixedin
FROM vulnerability_affected_feature AS vaf, feature_type AS t
WHERE t.id = vaf.feature_type AND vulnerability_id = ANY($1)
`
searchVulnerabilityByID = `
@ -58,7 +58,7 @@ const (
searchVulnerabilityPotentialAffected = `
WITH req AS (
SELECT vaf.id AS vaf_id, n.id AS n_id, vaf.feature_name AS name, v.id AS vulnerability_id
SELECT vaf.id AS vaf_id, n.id AS n_id, vaf.feature_name AS name, vaf.feature_type AS type, v.id AS vulnerability_id
FROM vulnerability_affected_feature AS vaf,
vulnerability AS v,
namespace AS n
@ -69,6 +69,7 @@ const (
SELECT req.vulnerability_id, nf.id, f.version, req.vaf_id AS added_by
FROM feature AS f, namespaced_feature AS nf, req
WHERE f.name = req.name
AND f.type = req.type
AND nf.namespace_id = req.n_id
AND nf.feature_id = f.id`
@ -180,7 +181,7 @@ func (tx *pgSession) FindVulnerabilities(vulnerabilities []database.Vulnerabilit
f database.AffectedFeature
)
err := rows.Scan(&id, &f.FeatureName, &f.AffectedVersion, &f.FixedInVersion)
err := rows.Scan(&id, &f.FeatureName, &f.AffectedVersion, &f.FeatureType, &f.FixedInVersion)
if err != nil {
return nil, handleError("searchVulnerabilityAffected", err)
}
@ -220,6 +221,11 @@ func (tx *pgSession) insertVulnerabilityAffected(vulnerabilityIDs []int64, vulne
affectedID int64
)
types, err := tx.getFeatureTypeMap()
if err != nil {
return nil, err
}
//TODO(Sida): Change to bulk insert.
stmt, err := tx.Prepare(insertVulnerabilityAffected)
if err != nil {
@ -231,7 +237,7 @@ func (tx *pgSession) insertVulnerabilityAffected(vulnerabilityIDs []int64, vulne
// affected feature row ID -> affected feature
affectedFeatures := map[int64]database.AffectedFeature{}
for _, f := range vuln.Affected {
err := stmt.QueryRow(vulnerabilityIDs[i], f.FeatureName, f.AffectedVersion, f.FixedInVersion).Scan(&affectedID)
err := stmt.QueryRow(vulnerabilityIDs[i], f.FeatureName, f.AffectedVersion, types.byName[f.FeatureType], f.FixedInVersion).Scan(&affectedID)
if err != nil {
return nil, handleError("insertVulnerabilityAffected", err)
}

@ -106,6 +106,7 @@ func TestCachingVulnerable(t *testing.T) {
Name: "openssl",
Version: "1.0",
VersionFormat: dpkg.ParserName,
Type: database.SourcePackage,
},
Namespace: ns,
}
@ -120,6 +121,7 @@ func TestCachingVulnerable(t *testing.T) {
{
Namespace: ns,
FeatureName: "openssl",
FeatureType: database.SourcePackage,
AffectedVersion: "2.0",
FixedInVersion: "2.1",
},
@ -136,6 +138,7 @@ func TestCachingVulnerable(t *testing.T) {
{
Namespace: ns,
FeatureName: "openssl",
FeatureType: database.SourcePackage,
AffectedVersion: "2.1",
FixedInVersion: "2.2",
},
@ -209,12 +212,14 @@ func TestFindVulnerabilities(t *testing.T) {
Affected: []database.AffectedFeature{
{
FeatureName: "openssl",
FeatureType: database.SourcePackage,
AffectedVersion: "2.0",
FixedInVersion: "2.0",
Namespace: ns,
},
{
FeatureName: "libssl",
FeatureType: database.SourcePackage,
AffectedVersion: "1.9-abc",
FixedInVersion: "1.9-abc",
Namespace: ns,
@ -318,26 +323,3 @@ func TestFindVulnerabilityIDs(t *testing.T) {
}
}
}
func assertVulnerabilityWithAffectedEqual(t *testing.T, expected database.VulnerabilityWithAffected, actual database.VulnerabilityWithAffected) bool {
return assert.Equal(t, expected.Vulnerability, actual.Vulnerability) && assertAffectedFeaturesEqual(t, expected.Affected, actual.Affected)
}
func assertAffectedFeaturesEqual(t *testing.T, expected []database.AffectedFeature, actual []database.AffectedFeature) bool {
if assert.Len(t, actual, len(expected)) {
has := map[database.AffectedFeature]bool{}
for _, i := range expected {
has[i] = false
}
for _, i := range actual {
if visited, ok := has[i]; !ok {
return false
} else if visited {
return false
}
has[i] = true
}
return true
}
return false
}

@ -55,6 +55,7 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
line := scanner.Text()
if len(line) < 2 {
if valid(&pkg) {
pkg.Type = database.BinaryPackage
packages.Add(pkg)
pkg = database.Feature{VersionFormat: dpkg.ParserName}
}
@ -81,6 +82,7 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
// in case of no terminal line
if valid(&pkg) {
pkg.Type = database.BinaryPackage
packages.Add(pkg)
}

@ -28,17 +28,17 @@ func TestAPKFeatureDetection(t *testing.T) {
"valid case",
map[string]string{"lib/apk/db/installed": "apk/testdata/valid"},
[]database.Feature{
{"musl", "1.1.14-r10", "", "", dpkg.ParserName},
{"busybox", "1.24.2-r9", "", "", dpkg.ParserName},
{"alpine-baselayout", "3.0.3-r0", "", "", dpkg.ParserName},
{"alpine-keys", "1.1-r0", "", "", dpkg.ParserName},
{"zlib", "1.2.8-r2", "", "", dpkg.ParserName},
{"libcrypto1.0", "1.0.2h-r1", "", "", dpkg.ParserName},
{"libssl1.0", "1.0.2h-r1", "", "", dpkg.ParserName},
{"apk-tools", "2.6.7-r0", "", "", dpkg.ParserName},
{"scanelf", "1.1.6-r0", "", "", dpkg.ParserName},
{"musl-utils", "1.1.14-r10", "", "", dpkg.ParserName},
{"libc-utils", "0.7-r0", "", "", dpkg.ParserName},
{"apk-tools", "2.6.7-r0", "dpkg", "binary"},
{"musl", "1.1.14-r10", "dpkg", "binary"},
{"libssl1.0", "1.0.2h-r1", "dpkg", "binary"},
{"libc-utils", "0.7-r0", "dpkg", "binary"},
{"busybox", "1.24.2-r9", "dpkg", "binary"},
{"scanelf", "1.1.6-r0", "dpkg", "binary"},
{"alpine-keys", "1.1-r0", "dpkg", "binary"},
{"libcrypto1.0", "1.0.2h-r1", "dpkg", "binary"},
{"zlib", "1.2.8-r2", "dpkg", "binary"},
{"musl-utils", "1.1.14-r10", "dpkg", "binary"},
{"alpine-baselayout", "3.0.3-r0", "dpkg", "binary"},
},
},
} {

@ -37,22 +37,12 @@ var (
type lister struct{}
func init() {
featurefmt.RegisterLister("dpkg", "1.0", &lister{})
}
func valid(pkg *database.Feature) bool {
return pkg.Name != "" && pkg.Version != ""
func (l lister) RequiredFilenames() []string {
return []string{"var/lib/dpkg/status"}
}
func addSourcePackage(pkg *database.Feature) {
if pkg.SourceName == "" {
pkg.SourceName = pkg.Name
}
if pkg.SourceVersion == "" {
pkg.SourceVersion = pkg.Version
}
func init() {
featurefmt.RegisterLister("dpkg", "1.0", &lister{})
}
func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error) {
@ -61,21 +51,45 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
return []database.Feature{}, nil
}
packages := mapset.NewSet()
scanner := bufio.NewScanner(strings.NewReader(string(f)))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
binary, source := parseDpkgDB(scanner)
if binary != nil {
packages.Add(*binary)
}
if source != nil {
packages.Add(*source)
}
}
return database.ConvertFeatureSetToFeatures(packages), nil
}
// parseDpkgDB consumes the status file scanner exactly one package info, until
// EOF or empty space, and generate the parsed packages from it.
func parseDpkgDB(scanner *bufio.Scanner) (binaryPackage *database.Feature, sourcePackage *database.Feature) {
var (
pkg = database.Feature{VersionFormat: dpkg.ParserName}
pkgs = mapset.NewSet()
err error
name string
version string
sourceName string
sourceVersion string
)
scanner := bufio.NewScanner(strings.NewReader(string(f)))
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "Package: ") {
// Package line
// Defines the name of the package
for {
line := strings.TrimSpace(scanner.Text())
if line == "" {
break
}
pkg.Name = strings.TrimSpace(strings.TrimPrefix(line, "Package: "))
pkg.Version = ""
if strings.HasPrefix(line, "Package: ") {
name = strings.TrimSpace(strings.TrimPrefix(line, "Package: "))
} else if strings.HasPrefix(line, "Source: ") {
// Source line (Optional)
// Gives the name of the source package
@ -87,14 +101,9 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
md[dpkgSrcCaptureRegexpNames[i]] = strings.TrimSpace(n)
}
pkg.SourceName = md["name"]
sourceName = md["name"]
if md["version"] != "" {
version := md["version"]
if err = versionfmt.Valid(dpkg.ParserName, version); err != nil {
log.WithError(err).WithField("version", string(line[1])).Warning("could not parse package version. skipping")
} else {
pkg.SourceVersion = version
}
sourceVersion = md["version"]
}
} else if strings.HasPrefix(line, "Version: ") {
// Version line
@ -102,25 +111,43 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
// This version is less important than a version retrieved from a Source line
// because the Debian vulnerabilities often skips the epoch from the Version field
// which is not present in the Source version, and because +bX revisions don't matter
version := strings.TrimPrefix(line, "Version: ")
if err = versionfmt.Valid(dpkg.ParserName, version); err != nil {
log.WithError(err).WithField("version", string(line[1])).Warning("could not parse package version. skipping")
} else {
pkg.Version = version
}
} else if line == "" {
pkg = database.Feature{VersionFormat: dpkg.ParserName}
version = strings.TrimPrefix(line, "Version: ")
}
if valid(&pkg) {
addSourcePackage(&pkg)
pkgs.Add(pkg)
if !scanner.Scan() {
break
}
}
return database.ConvertFeatureSetToFeatures(pkgs), nil
}
if name != "" && version != "" {
if err := versionfmt.Valid(dpkg.ParserName, version); err != nil {
log.WithError(err).WithFields(log.Fields{"name": name, "version": version}).Warning("skipped unparseable package")
} else {
binaryPackage = &database.Feature{name, version, dpkg.ParserName, database.BinaryPackage}
}
}
func (l lister) RequiredFilenames() []string {
return []string{"var/lib/dpkg/status"}
// Source version and names are computed from binary package names and versions
// in dpkg.
// Source package name:
// https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/pkg-format.c#n338
// Source package version:
// https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/pkg-format.c#n355
if sourceName == "" {
sourceName = name
}
if sourceVersion == "" {
sourceVersion = version
}
if sourceName != "" && sourceVersion != "" {
if err := versionfmt.Valid(dpkg.ParserName, version); err != nil {
log.WithError(err).WithFields(log.Fields{"name": name, "version": version}).Warning("skipped unparseable package")
} else {
sourcePackage = &database.Feature{sourceName, sourceVersion, dpkg.ParserName, database.SourcePackage}
}
}
return
}

@ -28,105 +28,168 @@ func TestListFeatures(t *testing.T) {
"valid status file",
map[string]string{"var/lib/dpkg/status": "dpkg/testdata/valid"},
[]database.Feature{
{"adduser", "3.116ubuntu1", "adduser", "3.116ubuntu1", dpkg.ParserName},
{"apt", "1.6.3ubuntu0.1", "apt", "1.6.3ubuntu0.1", dpkg.ParserName},
{"base-files", "10.1ubuntu2.2", "base-files", "10.1ubuntu2.2", dpkg.ParserName},
{"base-passwd", "3.5.44", "base-passwd", "3.5.44", dpkg.ParserName},
{"bash", "4.4.18-2ubuntu1", "bash", "4.4.18-2ubuntu1", dpkg.ParserName},
{"bsdutils", "1:2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName},
{"bzip2", "1.0.6-8.1", "bzip2", "1.0.6-8.1", dpkg.ParserName},
{"coreutils", "8.28-1ubuntu1", "coreutils", "8.28-1ubuntu1", dpkg.ParserName},
{"dash", "0.5.8-2.10", "dash", "0.5.8-2.10", dpkg.ParserName},
{"debconf", "1.5.66", "debconf", "1.5.66", dpkg.ParserName},
{"debianutils", "4.8.4", "debianutils", "4.8.4", dpkg.ParserName},
{"diffutils", "1:3.6-1", "diffutils", "1:3.6-1", dpkg.ParserName},
{"dpkg", "1.19.0.5ubuntu2", "dpkg", "1.19.0.5ubuntu2", dpkg.ParserName},
{"e2fsprogs", "1.44.1-1", "e2fsprogs", "1.44.1-1", dpkg.ParserName},
{"fdisk", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName},
{"findutils", "4.6.0+git+20170828-2", "findutils", "4.6.0+git+20170828-2", dpkg.ParserName},
{"gcc-8-base", "8-20180414-1ubuntu2", "gcc-8", "8-20180414-1ubuntu2", dpkg.ParserName},
{"gpgv", "2.2.4-1ubuntu1.1", "gnupg2", "2.2.4-1ubuntu1.1", dpkg.ParserName},
{"grep", "3.1-2", "grep", "3.1-2", dpkg.ParserName},
{"gzip", "1.6-5ubuntu1", "gzip", "1.6-5ubuntu1", dpkg.ParserName},
{"hostname", "3.20", "hostname", "3.20", dpkg.ParserName},
{"init-system-helpers", "1.51", "init-system-helpers", "1.51", dpkg.ParserName},
{"libacl1", "2.2.52-3build1", "acl", "2.2.52-3build1", dpkg.ParserName},
{"libapt-pkg5.0", "1.6.3ubuntu0.1", "apt", "1.6.3ubuntu0.1", dpkg.ParserName},
{"libattr1", "1:2.4.47-2build1", "attr", "1:2.4.47-2build1", dpkg.ParserName},
{"libaudit-common", "1:2.8.2-1ubuntu1", "audit", "1:2.8.2-1ubuntu1", dpkg.ParserName},
{"libaudit1", "1:2.8.2-1ubuntu1", "audit", "1:2.8.2-1ubuntu1", dpkg.ParserName},
{"libblkid1", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName},
{"libbz2-1.0", "1.0.6-8.1", "bzip2", "1.0.6-8.1", dpkg.ParserName},
{"libc-bin", "2.27-3ubuntu1", "glibc", "2.27-3ubuntu1", dpkg.ParserName},
{"libc6", "2.27-3ubuntu1", "glibc", "2.27-3ubuntu1", dpkg.ParserName},
{"libcap-ng0", "0.7.7-3.1", "libcap-ng", "0.7.7-3.1", dpkg.ParserName},
{"libcom-err2", "1.44.1-1", "e2fsprogs", "1.44.1-1", dpkg.ParserName},
{"libdb5.3", "5.3.28-13.1ubuntu1", "db5.3", "5.3.28-13.1ubuntu1", dpkg.ParserName},
{"libdebconfclient0", "0.213ubuntu1", "cdebconf", "0.213ubuntu1", dpkg.ParserName},
{"libext2fs2", "1.44.1-1", "e2fsprogs", "1.44.1-1", dpkg.ParserName},
{"libfdisk1", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName},
{"libffi6", "3.2.1-8", "libffi", "3.2.1-8", dpkg.ParserName},
{"libgcc1", "1:8-20180414-1ubuntu2", "gcc-8", "8-20180414-1ubuntu2", dpkg.ParserName},
{"libgcrypt20", "1.8.1-4ubuntu1.1", "libgcrypt20", "1.8.1-4ubuntu1.1", dpkg.ParserName},
{"libgmp10", "2:6.1.2+dfsg-2", "gmp", "2:6.1.2+dfsg-2", dpkg.ParserName},
{"libgnutls30", "3.5.18-1ubuntu1", "gnutls28", "3.5.18-1ubuntu1", dpkg.ParserName},
{"libgpg-error0", "1.27-6", "libgpg-error", "1.27-6", dpkg.ParserName},
{"libhogweed4", "3.4-1", "nettle", "3.4-1", dpkg.ParserName},
{"libidn2-0", "2.0.4-1.1build2", "libidn2", "2.0.4-1.1build2", dpkg.ParserName},
{"liblz4-1", "0.0~r131-2ubuntu3", "lz4", "0.0~r131-2ubuntu3", dpkg.ParserName},
{"liblzma5", "5.2.2-1.3", "xz-utils", "5.2.2-1.3", dpkg.ParserName},
{"libmount1", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName},
{"libncurses5", "6.1-1ubuntu1.18.04", "ncurses", "6.1-1ubuntu1.18.04", dpkg.ParserName},
{"libncursesw5", "6.1-1ubuntu1.18.04", "ncurses", "6.1-1ubuntu1.18.04", dpkg.ParserName},
{"libnettle6", "3.4-1", "nettle", "3.4-1", dpkg.ParserName},
{"libp11-kit0", "0.23.9-2", "p11-kit", "0.23.9-2", dpkg.ParserName},
{"libpam-modules", "1.1.8-3.6ubuntu2", "pam", "1.1.8-3.6ubuntu2", dpkg.ParserName},
{"libpam-modules-bin", "1.1.8-3.6ubuntu2", "pam", "1.1.8-3.6ubuntu2", dpkg.ParserName},
{"libpam-runtime", "1.1.8-3.6ubuntu2", "pam", "1.1.8-3.6ubuntu2", dpkg.ParserName},
{"libpam0g", "1.1.8-3.6ubuntu2", "pam", "1.1.8-3.6ubuntu2", dpkg.ParserName},
{"libpcre3", "2:8.39-9", "pcre3", "2:8.39-9", dpkg.ParserName},
{"libprocps6", "2:3.3.12-3ubuntu1.1", "procps", "2:3.3.12-3ubuntu1.1", dpkg.ParserName},
{"libseccomp2", "2.3.1-2.1ubuntu4", "libseccomp", "2.3.1-2.1ubuntu4", dpkg.ParserName},
{"libselinux1", "2.7-2build2", "libselinux", "2.7-2build2", dpkg.ParserName},
{"libsemanage-common", "2.7-2build2", "libsemanage", "2.7-2build2", dpkg.ParserName},
{"libsemanage1", "2.7-2build2", "libsemanage", "2.7-2build2", dpkg.ParserName},
{"libsepol1", "2.7-1", "libsepol", "2.7-1", dpkg.ParserName},
{"libsmartcols1", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName},
{"libss2", "1.44.1-1", "e2fsprogs", "1.44.1-1", dpkg.ParserName},
{"libstdc++6", "8-20180414-1ubuntu2", "gcc-8", "8-20180414-1ubuntu2", dpkg.ParserName},
{"libsystemd0", "237-3ubuntu10.3", "systemd", "237-3ubuntu10.3", dpkg.ParserName},
{"libtasn1-6", "4.13-2", "libtasn1-6", "4.13-2", dpkg.ParserName},
{"libtinfo5", "6.1-1ubuntu1.18.04", "ncurses", "6.1-1ubuntu1.18.04", dpkg.ParserName},
{"libudev1", "237-3ubuntu10.3", "systemd", "237-3ubuntu10.3", dpkg.ParserName},
{"libunistring2", "0.9.9-0ubuntu1", "libunistring", "0.9.9-0ubuntu1", dpkg.ParserName},
{"libuuid1", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName},
{"libzstd1", "1.3.3+dfsg-2ubuntu1", "libzstd", "1.3.3+dfsg-2ubuntu1", dpkg.ParserName},
{"login", "1:4.5-1ubuntu1", "shadow", "1:4.5-1ubuntu1", dpkg.ParserName},
{"lsb-base", "9.20170808ubuntu1", "lsb", "9.20170808ubuntu1", dpkg.ParserName},
{"mawk", "1.3.3-17ubuntu3", "mawk", "1.3.3-17ubuntu3", dpkg.ParserName},
{"mount", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName},
{"ncurses-base", "6.1-1ubuntu1.18.04", "ncurses", "6.1-1ubuntu1.18.04", dpkg.ParserName},
{"ncurses-bin", "6.1-1ubuntu1.18.04", "ncurses", "6.1-1ubuntu1.18.04", dpkg.ParserName},
{"passwd", "1:4.5-1ubuntu1", "shadow", "1:4.5-1ubuntu1", dpkg.ParserName},
{"perl-base", "5.26.1-6ubuntu0.2", "perl", "5.26.1-6ubuntu0.2", dpkg.ParserName},
{"procps", "2:3.3.12-3ubuntu1.1", "procps", "2:3.3.12-3ubuntu1.1", dpkg.ParserName},
{"sed", "4.4-2", "sed", "4.4-2", dpkg.ParserName},
{"sensible-utils", "0.0.12", "sensible-utils", "0.0.12", dpkg.ParserName},
{"sysvinit-utils", "2.88dsf-59.10ubuntu1", "sysvinit", "2.88dsf-59.10ubuntu1", dpkg.ParserName},
{"tar", "1.29b-2", "tar", "1.29b-2", dpkg.ParserName},
{"ubuntu-keyring", "2018.02.28", "ubuntu-keyring", "2018.02.28", dpkg.ParserName},
{"util-linux", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName},
{"zlib1g", "1:1.2.11.dfsg-0ubuntu2", "zlib", "1:1.2.11.dfsg-0ubuntu2", dpkg.ParserName},
{"libapt-pkg5.0", "1.6.3ubuntu0.1", "dpkg", "binary"},
{"perl-base", "5.26.1-6ubuntu0.2", "dpkg", "binary"},
{"libmount1", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"perl", "5.26.1-6ubuntu0.2", "dpkg", "source"},
{"libgnutls30", "3.5.18-1ubuntu1", "dpkg", "binary"},
{"liblzma5", "5.2.2-1.3", "dpkg", "binary"},
{"ncurses-bin", "6.1-1ubuntu1.18.04", "dpkg", "binary"},
{"lsb", "9.20170808ubuntu1", "dpkg", "source"},
{"sed", "4.4-2", "dpkg", "source"},
{"libsystemd0", "237-3ubuntu10.3", "dpkg", "binary"},
{"procps", "2:3.3.12-3ubuntu1.1", "dpkg", "source"},
{"login", "1:4.5-1ubuntu1", "dpkg", "binary"},
{"libunistring2", "0.9.9-0ubuntu1", "dpkg", "binary"},
{"sed", "4.4-2", "dpkg", "binary"},
{"libselinux", "2.7-2build2", "dpkg", "source"},
{"libseccomp", "2.3.1-2.1ubuntu4", "dpkg", "source"},
{"libss2", "1.44.1-1", "dpkg", "binary"},
{"liblz4-1", "0.0~r131-2ubuntu3", "dpkg", "binary"},
{"libsemanage1", "2.7-2build2", "dpkg", "binary"},
{"libtasn1-6", "4.13-2", "dpkg", "source"},
{"libzstd1", "1.3.3+dfsg-2ubuntu1", "dpkg", "binary"},
{"fdisk", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"xz-utils", "5.2.2-1.3", "dpkg", "source"},
{"lsb-base", "9.20170808ubuntu1", "dpkg", "binary"},
{"libpam-modules-bin", "1.1.8-3.6ubuntu2", "dpkg", "binary"},
{"dash", "0.5.8-2.10", "dpkg", "binary"},
{"gnupg2", "2.2.4-1ubuntu1.1", "dpkg", "source"},
{"libfdisk1", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"lz4", "0.0~r131-2ubuntu3", "dpkg", "source"},
{"libpam0g", "1.1.8-3.6ubuntu2", "dpkg", "binary"},
{"libc-bin", "2.27-3ubuntu1", "dpkg", "binary"},
{"libcap-ng", "0.7.7-3.1", "dpkg", "source"},
{"libcom-err2", "1.44.1-1", "dpkg", "binary"},
{"libudev1", "237-3ubuntu10.3", "dpkg", "binary"},
{"debconf", "1.5.66", "dpkg", "binary"},
{"tar", "1.29b-2", "dpkg", "binary"},
{"diffutils", "1:3.6-1", "dpkg", "source"},
{"gcc-8", "8-20180414-1ubuntu2", "dpkg", "source"},
{"e2fsprogs", "1.44.1-1", "dpkg", "source"},
{"bzip2", "1.0.6-8.1", "dpkg", "source"},
{"diffutils", "1:3.6-1", "dpkg", "binary"},
{"grep", "3.1-2", "dpkg", "binary"},
{"libgcc1", "1:8-20180414-1ubuntu2", "dpkg", "binary"},
{"bash", "4.4.18-2ubuntu1", "dpkg", "source"},
{"libtinfo5", "6.1-1ubuntu1.18.04", "dpkg", "binary"},
{"procps", "2:3.3.12-3ubuntu1.1", "dpkg", "binary"},
{"bzip2", "1.0.6-8.1", "dpkg", "binary"},
{"init-system-helpers", "1.51", "dpkg", "binary"},
{"libncursesw5", "6.1-1ubuntu1.18.04", "dpkg", "binary"},
{"init-system-helpers", "1.51", "dpkg", "source"},
{"libpam-modules", "1.1.8-3.6ubuntu2", "dpkg", "binary"},
{"libext2fs2", "1.44.1-1", "dpkg", "binary"},
{"libacl1", "2.2.52-3build1", "dpkg", "binary"},
{"hostname", "3.20", "dpkg", "binary"},
{"libgpg-error", "1.27-6", "dpkg", "source"},
{"acl", "2.2.52-3build1", "dpkg", "source"},
{"apt", "1.6.3ubuntu0.1", "dpkg", "binary"},
{"base-files", "10.1ubuntu2.2", "dpkg", "source"},
{"libgpg-error0", "1.27-6", "dpkg", "binary"},
{"audit", "1:2.8.2-1ubuntu1", "dpkg", "source"},
{"hostname", "3.20", "dpkg", "source"},
{"gzip", "1.6-5ubuntu1", "dpkg", "binary"},
{"libc6", "2.27-3ubuntu1", "dpkg", "binary"},
{"libnettle6", "3.4-1", "dpkg", "binary"},
{"sysvinit-utils", "2.88dsf-59.10ubuntu1", "dpkg", "binary"},
{"debianutils", "4.8.4", "dpkg", "source"},
{"libstdc++6", "8-20180414-1ubuntu2", "dpkg", "binary"},
{"libsepol", "2.7-1", "dpkg", "source"},
{"libpcre3", "2:8.39-9", "dpkg", "binary"},
{"libuuid1", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"systemd", "237-3ubuntu10.3", "dpkg", "source"},
{"tar", "1.29b-2", "dpkg", "source"},
{"ubuntu-keyring", "2018.02.28", "dpkg", "source"},
{"passwd", "1:4.5-1ubuntu1", "dpkg", "binary"},
{"sysvinit", "2.88dsf-59.10ubuntu1", "dpkg", "source"},
{"libidn2-0", "2.0.4-1.1build2", "dpkg", "binary"},
{"libhogweed4", "3.4-1", "dpkg", "binary"},
{"db5.3", "5.3.28-13.1ubuntu1", "dpkg", "source"},
{"sensible-utils", "0.0.12", "dpkg", "source"},
{"dpkg", "1.19.0.5ubuntu2", "dpkg", "source"},
{"libp11-kit0", "0.23.9-2", "dpkg", "binary"},
{"glibc", "2.27-3ubuntu1", "dpkg", "source"},
{"mount", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"libsemanage-common", "2.7-2build2", "dpkg", "binary"},
{"libblkid1", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"libdebconfclient0", "0.213ubuntu1", "dpkg", "binary"},
{"libffi", "3.2.1-8", "dpkg", "source"},
{"pam", "1.1.8-3.6ubuntu2", "dpkg", "source"},
{"bsdutils", "1:2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"libtasn1-6", "4.13-2", "dpkg", "binary"},
{"libaudit-common", "1:2.8.2-1ubuntu1", "dpkg", "binary"},
{"gpgv", "2.2.4-1ubuntu1.1", "dpkg", "binary"},
{"libzstd", "1.3.3+dfsg-2ubuntu1", "dpkg", "source"},
{"base-passwd", "3.5.44", "dpkg", "source"},
{"adduser", "3.116ubuntu1", "dpkg", "binary"},
{"libattr1", "1:2.4.47-2build1", "dpkg", "binary"},
{"libncurses5", "6.1-1ubuntu1.18.04", "dpkg", "binary"},
{"coreutils", "8.28-1ubuntu1", "dpkg", "binary"},
{"base-passwd", "3.5.44", "dpkg", "binary"},
{"ubuntu-keyring", "2018.02.28", "dpkg", "binary"},
{"adduser", "3.116ubuntu1", "dpkg", "source"},
{"libsmartcols1", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"libunistring", "0.9.9-0ubuntu1", "dpkg", "source"},
{"mawk", "1.3.3-17ubuntu3", "dpkg", "source"},
{"coreutils", "8.28-1ubuntu1", "dpkg", "source"},
{"attr", "1:2.4.47-2build1", "dpkg", "source"},
{"gmp", "2:6.1.2+dfsg-2", "dpkg", "source"},
{"libsemanage", "2.7-2build2", "dpkg", "source"},
{"libselinux1", "2.7-2build2", "dpkg", "binary"},
{"libseccomp2", "2.3.1-2.1ubuntu4", "dpkg", "binary"},
{"zlib1g", "1:1.2.11.dfsg-0ubuntu2", "dpkg", "binary"},
{"dash", "0.5.8-2.10", "dpkg", "source"},
{"gnutls28", "3.5.18-1ubuntu1", "dpkg", "source"},
{"libpam-runtime", "1.1.8-3.6ubuntu2", "dpkg", "binary"},
{"libgcrypt20", "1.8.1-4ubuntu1.1", "dpkg", "source"},
{"sensible-utils", "0.0.12", "dpkg", "binary"},
{"p11-kit", "0.23.9-2", "dpkg", "source"},
{"ncurses-base", "6.1-1ubuntu1.18.04", "dpkg", "binary"},
{"e2fsprogs", "1.44.1-1", "dpkg", "binary"},
{"libgcrypt20", "1.8.1-4ubuntu1.1", "dpkg", "binary"},
{"libprocps6", "2:3.3.12-3ubuntu1.1", "dpkg", "binary"},
{"debconf", "1.5.66", "dpkg", "source"},
{"gcc-8-base", "8-20180414-1ubuntu2", "dpkg", "binary"},
{"base-files", "10.1ubuntu2.2", "dpkg", "binary"},
{"libbz2-1.0", "1.0.6-8.1", "dpkg", "binary"},
{"grep", "3.1-2", "dpkg", "source"},
{"bash", "4.4.18-2ubuntu1", "dpkg", "binary"},
{"libgmp10", "2:6.1.2+dfsg-2", "dpkg", "binary"},
{"shadow", "1:4.5-1ubuntu1", "dpkg", "source"},
{"libidn2", "2.0.4-1.1build2", "dpkg", "source"},
{"gzip", "1.6-5ubuntu1", "dpkg", "source"},
{"util-linux", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"libaudit1", "1:2.8.2-1ubuntu1", "dpkg", "binary"},
{"libsepol1", "2.7-1", "dpkg", "binary"},
{"pcre3", "2:8.39-9", "dpkg", "source"},
{"apt", "1.6.3ubuntu0.1", "dpkg", "source"},
{"nettle", "3.4-1", "dpkg", "source"},
{"util-linux", "2.31.1-0.4ubuntu3.1", "dpkg", "source"},
{"libcap-ng0", "0.7.7-3.1", "dpkg", "binary"},
{"debianutils", "4.8.4", "dpkg", "binary"},
{"ncurses", "6.1-1ubuntu1.18.04", "dpkg", "source"},
{"libffi6", "3.2.1-8", "dpkg", "binary"},
{"cdebconf", "0.213ubuntu1", "dpkg", "source"},
{"findutils", "4.6.0+git+20170828-2", "dpkg", "source"},
{"libdb5.3", "5.3.28-13.1ubuntu1", "dpkg", "binary"},
{"zlib", "1:1.2.11.dfsg-0ubuntu2", "dpkg", "source"},
{"findutils", "4.6.0+git+20170828-2", "dpkg", "binary"},
{"dpkg", "1.19.0.5ubuntu2", "dpkg", "binary"},
{"mawk", "1.3.3-17ubuntu3", "dpkg", "binary"},
},
},
{
"corrupted status file",
map[string]string{"var/lib/dpkg/status": "dpkg/testdata/corrupted"},
[]database.Feature{
{"libpam-runtime", "1.1.8-3.1ubuntu3", "pam", "1.1.8-3.1ubuntu3", dpkg.ParserName},
{"libpam-modules-bin", "1.1.8-3.1ubuntu3", "pam", "1.1.8-3.1ubuntu3", dpkg.ParserName},
{"makedev", "2.3.1-93ubuntu1", "makedev", "2.3.1-93ubuntu1", dpkg.ParserName},
{"libgcc1", "1:5.1.1-12ubuntu1", "gcc-5", "5.1.1-12ubuntu1", dpkg.ParserName},
{"libpam-modules-bin", "1.1.8-3.1ubuntu3", "dpkg", "binary"},
{"gcc-5", "5.1.1-12ubuntu1", "dpkg", "source"},
{"makedev", "2.3.1-93ubuntu1", "dpkg", "binary"},
{"libgcc1", "1:5.1.1-12ubuntu1", "dpkg", "binary"},
{"pam", "1.1.8-3.1ubuntu3", "dpkg", "source"},
{"makedev", "2.3.1-93ubuntu1", "dpkg", "source"},
{"libpam-runtime", "1.1.8-3.1ubuntu3", "dpkg", "binary"},
},
},
} {

@ -45,6 +45,10 @@ func init() {
featurefmt.RegisterLister("rpm", "1.0", &lister{})
}
func (l lister) RequiredFilenames() []string {
return []string{"var/lib/rpm/Packages"}
}
func isIgnored(packageName string) bool {
for _, pkg := range ignoredPackages {
if pkg == packageName {
@ -55,12 +59,6 @@ func isIgnored(packageName string) bool {
return false
}
func valid(pkg *database.Feature) bool {
return pkg.Name != "" && pkg.Version != "" &&
((pkg.SourceName == "" && pkg.SourceVersion != "") ||
(pkg.SourceName != "" && pkg.SourceVersion != ""))
}
func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error) {
f, hasFile := files["var/lib/rpm/Packages"]
if !hasFile {
@ -84,7 +82,7 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
// Extract binary package names because RHSA refers to binary package names.
out, err := exec.Command("rpm", "--dbpath", tmpDir, "-qa", "--qf", "%{NAME} %{EPOCH}:%{VERSION}-%{RELEASE} %{SOURCERPM}\n").CombinedOutput()
if err != nil {
log.WithError(err).WithField("output", string(out)).Error("could not query RPM")
log.WithError(err).WithField("output", string(out)).Error("failed to query RPM")
// Do not bubble up because we probably won't be able to fix it,
// the database must be corrupted
return []database.Feature{}, nil
@ -93,39 +91,51 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
packages := mapset.NewSet()
scanner := bufio.NewScanner(strings.NewReader(string(out)))
for scanner.Scan() {
line := strings.Split(scanner.Text(), " ")
if len(line) != 3 {
// We may see warnings on some RPM versions:
// "warning: Generating 12 missing index(es), please wait..."
continue
rpmPackage, srpmPackage := parseRPMOutput(scanner.Text())
if rpmPackage != nil {
packages.Add(*rpmPackage)
}
if isIgnored(line[0]) {
continue
if srpmPackage != nil {
packages.Add(*srpmPackage)
}
}
pkg := database.Feature{Name: line[0], VersionFormat: rpm.ParserName}
pkg.Version = strings.Replace(line[1], "(none):", "", -1)
if err := versionfmt.Valid(rpm.ParserName, pkg.Version); err != nil {
log.WithError(err).WithField("version", line[1]).Warning("skipped unparseable package")
continue
}
return database.ConvertFeatureSetToFeatures(packages), nil
}
if err := parseSourceRPM(line[2], &pkg); err != nil {
log.WithError(err).WithField("sourcerpm", line[2]).Warning("skipped unparseable package")
continue
}
func parseRPMOutput(raw string) (rpmPackage *database.Feature, srpmPackage *database.Feature) {
line := strings.Split(raw, " ")
if len(line) != 3 {
// We may see warnings on some RPM versions:
// "warning: Generating 12 missing index(es), please wait..."
return
}
if valid(&pkg) {
packages.Add(pkg)
}
if isIgnored(line[0]) {
return
}
return database.ConvertFeatureSetToFeatures(packages), nil
}
name, version, srpm := line[0], strings.Replace(line[1], "(none):", "", -1), line[2]
if err := versionfmt.Valid(rpm.ParserName, version); err != nil {
log.WithError(err).WithFields(log.Fields{"name": name, "version": version}).Warning("skipped unparseable package")
return
}
func (l lister) RequiredFilenames() []string {
return []string{"var/lib/rpm/Packages"}
rpmPackage = &database.Feature{name, version, rpm.ParserName, database.BinaryPackage}
srpmName, srpmVersion, srpmRelease, _, err := parseSourceRPM(srpm)
if err != nil {
log.WithError(err).WithFields(log.Fields{"name": name, "sourcerpm": srpm}).Warning("skipped unparseable package")
return
}
srpmVersion = srpmVersion + "-" + srpmRelease
if err = versionfmt.Valid(rpm.ParserName, srpmVersion); err != nil {
return
}
srpmPackage = &database.Feature{srpmName, srpmVersion, rpm.ParserName, database.SourcePackage}
return
}
type rpmParserState string
@ -140,11 +150,9 @@ const (
// parseSourceRPM parses the source rpm package representation string
// http://ftp.rpm.org/max-rpm/ch-rpm-file-format.html
func parseSourceRPM(sourceRPM string, pkg *database.Feature) error {
func parseSourceRPM(sourceRPM string) (name string, version string, release string, architecture string, err error) {
state := parseRPM
previousCheckPoint := len(sourceRPM)
release := ""
version := ""
for i := len(sourceRPM) - 1; i >= 0; i-- {
switch state {
case parseRPM:
@ -153,16 +161,18 @@ func parseSourceRPM(sourceRPM string, pkg *database.Feature) error {
packageType := strutil.Substring(sourceRPM, i+1, len(sourceRPM))
previousCheckPoint = i
if packageType != "rpm" {
return fmt.Errorf("unexpected package type, expect: 'rpm', got: '%s'", packageType)
err = fmt.Errorf("unexpected package type, expect: 'rpm', got: '%s'", packageType)
return
}
}
case parseArchitecture:
if string(sourceRPM[i]) == "." {
state = parseRelease
architecture := strutil.Substring(sourceRPM, i+1, previousCheckPoint)
architecture = strutil.Substring(sourceRPM, i+1, previousCheckPoint)
previousCheckPoint = i
if architecture != "src" && architecture != "nosrc" {
return fmt.Errorf("unexpected package architecture, expect: 'src' or 'nosrc', got: '%s'", architecture)
err = fmt.Errorf("unexpected package architecture, expect: 'src' or 'nosrc', got: '%s'", architecture)
return
}
}
case parseRelease:
@ -171,7 +181,8 @@ func parseSourceRPM(sourceRPM string, pkg *database.Feature) error {
release = strutil.Substring(sourceRPM, i+1, previousCheckPoint)
previousCheckPoint = i
if release == "" {
return fmt.Errorf("unexpected package release, expect: not empty")
err = fmt.Errorf("unexpected package release, expect: not empty")
return
}
}
case parseVersion:
@ -181,7 +192,8 @@ func parseSourceRPM(sourceRPM string, pkg *database.Feature) error {
version = strutil.Substring(sourceRPM, i+1, previousCheckPoint)
previousCheckPoint = i
if version == "" {
return fmt.Errorf("unexpected package version, expect: not empty")
err = fmt.Errorf("unexpected package version, expect: not empty")
return
}
break
}
@ -189,20 +201,15 @@ func parseSourceRPM(sourceRPM string, pkg *database.Feature) error {
}
if state != terminate {
return fmt.Errorf("unexpected termination while parsing '%s'", state)
}
concatVersion := version + "-" + release
if err := versionfmt.Valid(rpm.ParserName, concatVersion); err != nil {
return err
err = fmt.Errorf("unexpected termination while parsing '%s'", state)
return
}
name := strutil.Substring(sourceRPM, 0, previousCheckPoint)
name = strutil.Substring(sourceRPM, 0, previousCheckPoint)
if name == "" {
return fmt.Errorf("unexpected package name, expect: not empty")
err = fmt.Errorf("unexpected package name, expect: not empty")
return
}
pkg.SourceName = name
pkg.SourceVersion = concatVersion
return nil
return
}

@ -25,179 +25,307 @@ import (
)
var expectedBigCaseInfo = []database.Feature{
{"publicsuffix-list-dafsa", "20180514-1.fc28", "publicsuffix-list", "20180514-1.fc28", rpm.ParserName},
{"libreport-filesystem", "2.9.5-1.fc28", "libreport", "2.9.5-1.fc28", rpm.ParserName},
{"fedora-gpg-keys", "28-5", "fedora-repos", "28-5", rpm.ParserName},
{"fedora-release", "28-2", "fedora-release", "28-2", rpm.ParserName},
{"filesystem", "3.8-2.fc28", "filesystem", "3.8-2.fc28", rpm.ParserName},
{"tzdata", "2018e-1.fc28", "tzdata", "2018e-1.fc28", rpm.ParserName},
{"pcre2", "10.31-10.fc28", "pcre2", "10.31-10.fc28", rpm.ParserName},
{"glibc-minimal-langpack", "2.27-32.fc28", "glibc", "2.27-32.fc28", rpm.ParserName},
{"glibc-common", "2.27-32.fc28", "glibc", "2.27-32.fc28", rpm.ParserName},
{"bash", "4.4.23-1.fc28", "bash", "4.4.23-1.fc28", rpm.ParserName},
{"zlib", "1.2.11-8.fc28", "zlib", "1.2.11-8.fc28", rpm.ParserName},
{"bzip2-libs", "1.0.6-26.fc28", "bzip2", "1.0.6-26.fc28", rpm.ParserName},
{"libcap", "2.25-9.fc28", "libcap", "2.25-9.fc28", rpm.ParserName},
{"libgpg-error", "1.31-1.fc28", "libgpg-error", "1.31-1.fc28", rpm.ParserName},
{"libzstd", "1.3.5-1.fc28", "zstd", "1.3.5-1.fc28", rpm.ParserName},
{"expat", "2.2.5-3.fc28", "expat", "2.2.5-3.fc28", rpm.ParserName},
{"nss-util", "3.38.0-1.0.fc28", "nss-util", "3.38.0-1.0.fc28", rpm.ParserName},
{"libcom_err", "1.44.2-0.fc28", "e2fsprogs", "1.44.2-0.fc28", rpm.ParserName},
{"libffi", "3.1-16.fc28", "libffi", "3.1-16.fc28", rpm.ParserName},
{"libgcrypt", "1.8.3-1.fc28", "libgcrypt", "1.8.3-1.fc28", rpm.ParserName},
{"libxml2", "2.9.8-4.fc28", "libxml2", "2.9.8-4.fc28", rpm.ParserName},
{"libacl", "2.2.53-1.fc28", "acl", "2.2.53-1.fc28", rpm.ParserName},
{"sed", "4.5-1.fc28", "sed", "4.5-1.fc28", rpm.ParserName},
{"libmount", "2.32.1-1.fc28", "util-linux", "2.32.1-1.fc28", rpm.ParserName},
{"p11-kit", "0.23.12-1.fc28", "p11-kit", "0.23.12-1.fc28", rpm.ParserName},
{"libidn2", "2.0.5-1.fc28", "libidn2", "2.0.5-1.fc28", rpm.ParserName},
{"libcap-ng", "0.7.9-4.fc28", "libcap-ng", "0.7.9-4.fc28", rpm.ParserName},
{"lz4-libs", "1.8.1.2-4.fc28", "lz4", "1.8.1.2-4.fc28", rpm.ParserName},
{"libassuan", "2.5.1-3.fc28", "libassuan", "2.5.1-3.fc28", rpm.ParserName},
{"keyutils-libs", "1.5.10-6.fc28", "keyutils", "1.5.10-6.fc28", rpm.ParserName},
{"glib2", "2.56.1-4.fc28", "glib2", "2.56.1-4.fc28", rpm.ParserName},
{"systemd-libs", "238-9.git0e0aa59.fc28", "systemd", "238-9.git0e0aa59.fc28", rpm.ParserName},
{"dbus-libs", "1:1.12.10-1.fc28", "dbus", "1.12.10-1.fc28", rpm.ParserName},
{"libtasn1", "4.13-2.fc28", "libtasn1", "4.13-2.fc28", rpm.ParserName},
{"ca-certificates", "2018.2.24-1.0.fc28", "ca-certificates", "2018.2.24-1.0.fc28", rpm.ParserName},
{"libarchive", "3.3.1-4.fc28", "libarchive", "3.3.1-4.fc28", rpm.ParserName},
{"openssl", "1:1.1.0h-3.fc28", "openssl", "1.1.0h-3.fc28", rpm.ParserName},
{"libusbx", "1.0.22-1.fc28", "libusbx", "1.0.22-1.fc28", rpm.ParserName},
{"libsemanage", "2.8-2.fc28", "libsemanage", "2.8-2.fc28", rpm.ParserName},
{"libutempter", "1.1.6-14.fc28", "libutempter", "1.1.6-14.fc28", rpm.ParserName},
{"mpfr", "3.1.6-1.fc28", "mpfr", "3.1.6-1.fc28", rpm.ParserName},
{"gnutls", "3.6.3-4.fc28", "gnutls", "3.6.3-4.fc28", rpm.ParserName},
{"gzip", "1.9-3.fc28", "gzip", "1.9-3.fc28", rpm.ParserName},
{"acl", "2.2.53-1.fc28", "acl", "2.2.53-1.fc28", rpm.ParserName},
{"nss-softokn-freebl", "3.38.0-1.0.fc28", "nss-softokn", "3.38.0-1.0.fc28", rpm.ParserName},
{"nss", "3.38.0-1.0.fc28", "nss", "3.38.0-1.0.fc28", rpm.ParserName},
{"libmetalink", "0.1.3-6.fc28", "libmetalink", "0.1.3-6.fc28", rpm.ParserName},
{"libdb-utils", "5.3.28-30.fc28", "libdb", "5.3.28-30.fc28", rpm.ParserName},
{"file-libs", "5.33-7.fc28", "file", "5.33-7.fc28", rpm.ParserName},
{"libsss_idmap", "1.16.3-2.fc28", "sssd", "1.16.3-2.fc28", rpm.ParserName},
{"libsigsegv", "2.11-5.fc28", "libsigsegv", "2.11-5.fc28", rpm.ParserName},
{"krb5-libs", "1.16.1-13.fc28", "krb5", "1.16.1-13.fc28", rpm.ParserName},
{"libnsl2", "1.2.0-2.20180605git4a062cf.fc28", "libnsl2", "1.2.0-2.20180605git4a062cf.fc28", rpm.ParserName},
{"python3-pip", "9.0.3-2.fc28", "python-pip", "9.0.3-2.fc28", rpm.ParserName},
{"python3", "3.6.6-1.fc28", "python3", "3.6.6-1.fc28", rpm.ParserName},
{"pam", "1.3.1-1.fc28", "pam", "1.3.1-1.fc28", rpm.ParserName},
{"python3-gobject-base", "3.28.3-1.fc28", "pygobject3", "3.28.3-1.fc28", rpm.ParserName},
{"python3-smartcols", "0.3.0-2.fc28", "python-smartcols", "0.3.0-2.fc28", rpm.ParserName},
{"python3-iniparse", "0.4-30.fc28", "python-iniparse", "0.4-30.fc28", rpm.ParserName},
{"openldap", "2.4.46-3.fc28", "openldap", "2.4.46-3.fc28", rpm.ParserName},
{"libseccomp", "2.3.3-2.fc28", "libseccomp", "2.3.3-2.fc28", rpm.ParserName},
{"npth", "1.5-4.fc28", "npth", "1.5-4.fc28", rpm.ParserName},
{"gpgme", "1.10.0-4.fc28", "gpgme", "1.10.0-4.fc28", rpm.ParserName},
{"json-c", "0.13.1-2.fc28", "json-c", "0.13.1-2.fc28", rpm.ParserName},
{"libyaml", "0.1.7-5.fc28", "libyaml", "0.1.7-5.fc28", rpm.ParserName},
{"libpkgconf", "1.4.2-1.fc28", "pkgconf", "1.4.2-1.fc28", rpm.ParserName},
{"pkgconf-pkg-config", "1.4.2-1.fc28", "pkgconf", "1.4.2-1.fc28", rpm.ParserName},
{"iptables-libs", "1.6.2-3.fc28", "iptables", "1.6.2-3.fc28", rpm.ParserName},
{"device-mapper-libs", "1.02.146-5.fc28", "lvm2", "2.02.177-5.fc28", rpm.ParserName},
{"systemd-pam", "238-9.git0e0aa59.fc28", "systemd", "238-9.git0e0aa59.fc28", rpm.ParserName},
{"systemd", "238-9.git0e0aa59.fc28", "systemd", "238-9.git0e0aa59.fc28", rpm.ParserName},
{"elfutils-default-yama-scope", "0.173-1.fc28", "elfutils", "0.173-1.fc28", rpm.ParserName},
{"libcurl", "7.59.0-6.fc28", "curl", "7.59.0-6.fc28", rpm.ParserName},
{"python3-librepo", "1.8.1-7.fc28", "librepo", "1.8.1-7.fc28", rpm.ParserName},
{"rpm-plugin-selinux", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName},
{"rpm", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName},
{"libdnf", "0.11.1-3.fc28", "libdnf", "0.11.1-3.fc28", rpm.ParserName},
{"rpm-build-libs", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName},
{"python3-rpm", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName},
{"dnf", "2.7.5-12.fc28", "dnf", "2.7.5-12.fc28", rpm.ParserName},
{"deltarpm", "3.6-25.fc28", "deltarpm", "3.6-25.fc28", rpm.ParserName},
{"sssd-client", "1.16.3-2.fc28", "sssd", "1.16.3-2.fc28", rpm.ParserName},
{"cracklib-dicts", "2.9.6-13.fc28", "cracklib", "2.9.6-13.fc28", rpm.ParserName},
{"tar", "2:1.30-3.fc28", "tar", "1.30-3.fc28", rpm.ParserName},
{"diffutils", "3.6-4.fc28", "diffutils", "3.6-4.fc28", rpm.ParserName},
{"langpacks-en", "1.0-12.fc28", "langpacks", "1.0-12.fc28", rpm.ParserName},
{"libgcc", "8.1.1-5.fc28", "gcc", "8.1.1-5.fc28", rpm.ParserName},
{"pkgconf-m4", "1.4.2-1.fc28", "pkgconf", "1.4.2-1.fc28", rpm.ParserName},
{"dnf-conf", "2.7.5-12.fc28", "dnf", "2.7.5-12.fc28", rpm.ParserName},
{"fedora-repos", "28-5", "fedora-repos", "28-5", rpm.ParserName},
{"setup", "2.11.4-1.fc28", "setup", "2.11.4-1.fc28", rpm.ParserName},
{"basesystem", "11-5.fc28", "basesystem", "11-5.fc28", rpm.ParserName},
{"ncurses-base", "6.1-5.20180224.fc28", "ncurses", "6.1-5.20180224.fc28", rpm.ParserName},
{"libselinux", "2.8-1.fc28", "libselinux", "2.8-1.fc28", rpm.ParserName},
{"ncurses-libs", "6.1-5.20180224.fc28", "ncurses", "6.1-5.20180224.fc28", rpm.ParserName},
{"glibc", "2.27-32.fc28", "glibc", "2.27-32.fc28", rpm.ParserName},
{"libsepol", "2.8-1.fc28", "libsepol", "2.8-1.fc28", rpm.ParserName},
{"xz-libs", "5.2.4-2.fc28", "xz", "5.2.4-2.fc28", rpm.ParserName},
{"info", "6.5-4.fc28", "texinfo", "6.5-4.fc28", rpm.ParserName},
{"libdb", "5.3.28-30.fc28", "libdb", "5.3.28-30.fc28", rpm.ParserName},
{"elfutils-libelf", "0.173-1.fc28", "elfutils", "0.173-1.fc28", rpm.ParserName},
{"popt", "1.16-14.fc28", "popt", "1.16-14.fc28", rpm.ParserName},
{"nspr", "4.19.0-1.fc28", "nspr", "4.19.0-1.fc28", rpm.ParserName},
{"libxcrypt", "4.1.2-1.fc28", "libxcrypt", "4.1.2-1.fc28", rpm.ParserName},
{"lua-libs", "5.3.4-10.fc28", "lua", "5.3.4-10.fc28", rpm.ParserName},
{"libuuid", "2.32.1-1.fc28", "util-linux", "2.32.1-1.fc28", rpm.ParserName},
{"readline", "7.0-11.fc28", "readline", "7.0-11.fc28", rpm.ParserName},
{"libattr", "2.4.48-3.fc28", "attr", "2.4.48-3.fc28", rpm.ParserName},
{"coreutils-single", "8.29-7.fc28", "coreutils", "8.29-7.fc28", rpm.ParserName},
{"libblkid", "2.32.1-1.fc28", "util-linux", "2.32.1-1.fc28", rpm.ParserName},
{"gmp", "1:6.1.2-7.fc28", "gmp", "6.1.2-7.fc28", rpm.ParserName},
{"libunistring", "0.9.10-1.fc28", "libunistring", "0.9.10-1.fc28", rpm.ParserName},
{"sqlite-libs", "3.22.0-4.fc28", "sqlite", "3.22.0-4.fc28", rpm.ParserName},
{"audit-libs", "2.8.4-2.fc28", "audit", "2.8.4-2.fc28", rpm.ParserName},
{"chkconfig", "1.10-4.fc28", "chkconfig", "1.10-4.fc28", rpm.ParserName},
{"libsmartcols", "2.32.1-1.fc28", "util-linux", "2.32.1-1.fc28", rpm.ParserName},
{"pcre", "8.42-3.fc28", "pcre", "8.42-3.fc28", rpm.ParserName},
{"grep", "3.1-5.fc28", "grep", "3.1-5.fc28", rpm.ParserName},
{"crypto-policies", "20180425-5.git6ad4018.fc28", "crypto-policies", "20180425-5.git6ad4018.fc28", rpm.ParserName},
{"gdbm-libs", "1:1.14.1-4.fc28", "gdbm", "1.14.1-4.fc28", rpm.ParserName},
{"p11-kit-trust", "0.23.12-1.fc28", "p11-kit", "0.23.12-1.fc28", rpm.ParserName},
{"openssl-libs", "1:1.1.0h-3.fc28", "openssl", "1.1.0h-3.fc28", rpm.ParserName},
{"ima-evm-utils", "1.1-2.fc28", "ima-evm-utils", "1.1-2.fc28", rpm.ParserName},
{"gdbm", "1:1.14.1-4.fc28", "gdbm", "1.14.1-4.fc28", rpm.ParserName},
{"gobject-introspection", "1.56.1-1.fc28", "gobject-introspection", "1.56.1-1.fc28", rpm.ParserName},
{"shadow-utils", "2:4.6-1.fc28", "shadow-utils", "4.6-1.fc28", rpm.ParserName},
{"libpsl", "0.20.2-2.fc28", "libpsl", "0.20.2-2.fc28", rpm.ParserName},
{"nettle", "3.4-2.fc28", "nettle", "3.4-2.fc28", rpm.ParserName},
{"libfdisk", "2.32.1-1.fc28", "util-linux", "2.32.1-1.fc28", rpm.ParserName},
{"cracklib", "2.9.6-13.fc28", "cracklib", "2.9.6-13.fc28", rpm.ParserName},
{"libcomps", "0.1.8-11.fc28", "libcomps", "0.1.8-11.fc28", rpm.ParserName},
{"nss-softokn", "3.38.0-1.0.fc28", "nss-softokn", "3.38.0-1.0.fc28", rpm.ParserName},
{"nss-sysinit", "3.38.0-1.0.fc28", "nss", "3.38.0-1.0.fc28", rpm.ParserName},
{"libksba", "1.3.5-7.fc28", "libksba", "1.3.5-7.fc28", rpm.ParserName},
{"kmod-libs", "25-2.fc28", "kmod", "25-2.fc28", rpm.ParserName},
{"libsss_nss_idmap", "1.16.3-2.fc28", "sssd", "1.16.3-2.fc28", rpm.ParserName},
{"libverto", "0.3.0-5.fc28", "libverto", "0.3.0-5.fc28", rpm.ParserName},
{"gawk", "4.2.1-1.fc28", "gawk", "4.2.1-1.fc28", rpm.ParserName},
{"libtirpc", "1.0.3-3.rc2.fc28", "libtirpc", "1.0.3-3.rc2.fc28", rpm.ParserName},
{"python3-libs", "3.6.6-1.fc28", "python3", "3.6.6-1.fc28", rpm.ParserName},
{"python3-setuptools", "39.2.0-6.fc28", "python-setuptools", "39.2.0-6.fc28", rpm.ParserName},
{"libpwquality", "1.4.0-7.fc28", "libpwquality", "1.4.0-7.fc28", rpm.ParserName},
{"util-linux", "2.32.1-1.fc28", "util-linux", "2.32.1-1.fc28", rpm.ParserName},
{"python3-libcomps", "0.1.8-11.fc28", "libcomps", "0.1.8-11.fc28", rpm.ParserName},
{"python3-six", "1.11.0-3.fc28", "python-six", "1.11.0-3.fc28", rpm.ParserName},
{"cyrus-sasl-lib", "2.1.27-0.2rc7.fc28", "cyrus-sasl", "2.1.27-0.2rc7.fc28", rpm.ParserName},
{"libssh", "0.8.2-1.fc28", "libssh", "0.8.2-1.fc28", rpm.ParserName},
{"qrencode-libs", "3.4.4-5.fc28", "qrencode", "3.4.4-5.fc28", rpm.ParserName},
{"gnupg2", "2.2.8-1.fc28", "gnupg2", "2.2.8-1.fc28", rpm.ParserName},
{"python3-gpg", "1.10.0-4.fc28", "gpgme", "1.10.0-4.fc28", rpm.ParserName},
{"libargon2", "20161029-5.fc28", "argon2", "20161029-5.fc28", rpm.ParserName},
{"libmodulemd", "1.6.2-2.fc28", "libmodulemd", "1.6.2-2.fc28", rpm.ParserName},
{"pkgconf", "1.4.2-1.fc28", "pkgconf", "1.4.2-1.fc28", rpm.ParserName},
{"libpcap", "14:1.9.0-1.fc28", "libpcap", "1.9.0-1.fc28", rpm.ParserName},
{"device-mapper", "1.02.146-5.fc28", "lvm2", "2.02.177-5.fc28", rpm.ParserName},
{"cryptsetup-libs", "2.0.4-1.fc28", "cryptsetup", "2.0.4-1.fc28", rpm.ParserName},
{"elfutils-libs", "0.173-1.fc28", "elfutils", "0.173-1.fc28", rpm.ParserName},
{"dbus", "1:1.12.10-1.fc28", "dbus", "1.12.10-1.fc28", rpm.ParserName},
{"libnghttp2", "1.32.1-1.fc28", "nghttp2", "1.32.1-1.fc28", rpm.ParserName},
{"librepo", "1.8.1-7.fc28", "librepo", "1.8.1-7.fc28", rpm.ParserName},
{"curl", "7.59.0-6.fc28", "curl", "7.59.0-6.fc28", rpm.ParserName},
{"rpm-libs", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName},
{"libsolv", "0.6.35-1.fc28", "libsolv", "0.6.35-1.fc28", rpm.ParserName},
{"python3-hawkey", "0.11.1-3.fc28", "libdnf", "0.11.1-3.fc28", rpm.ParserName},
{"rpm-sign-libs", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName},
{"python3-dnf", "2.7.5-12.fc28", "dnf", "2.7.5-12.fc28", rpm.ParserName},
{"dnf-yum", "2.7.5-12.fc28", "dnf", "2.7.5-12.fc28", rpm.ParserName},
{"rpm-plugin-systemd-inhibit", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName},
{"nss-tools", "3.38.0-1.0.fc28", "nss", "3.38.0-1.0.fc28", rpm.ParserName},
{"openssl-pkcs11", "0.4.8-1.fc28", "openssl-pkcs11", "0.4.8-1.fc28", rpm.ParserName},
{"vim-minimal", "2:8.1.328-1.fc28", "vim", "8.1.328-1.fc28", rpm.ParserName},
{"glibc-langpack-en", "2.27-32.fc28", "glibc", "2.27-32.fc28", rpm.ParserName},
{"rootfiles", "8.1-22.fc28", "rootfiles", "8.1-22.fc28", rpm.ParserName},
{"libmount", "2.32.1-1.fc28", "rpm", "binary"},
{"libffi", "3.1-16.fc28", "rpm", "binary"},
{"libunistring", "0.9.10-1.fc28", "rpm", "binary"},
{"fedora-repos", "28-5", "rpm", "binary"},
{"libarchive", "3.3.1-4.fc28", "rpm", "source"},
{"langpacks", "1.0-12.fc28", "rpm", "source"},
{"readline", "7.0-11.fc28", "rpm", "source"},
{"gzip", "1.9-3.fc28", "rpm", "source"},
{"libverto", "0.3.0-5.fc28", "rpm", "source"},
{"ncurses-base", "6.1-5.20180224.fc28", "rpm", "binary"},
{"libfdisk", "2.32.1-1.fc28", "rpm", "binary"},
{"libselinux", "2.8-1.fc28", "rpm", "source"},
{"nss-util", "3.38.0-1.0.fc28", "rpm", "source"},
{"mpfr", "3.1.6-1.fc28", "rpm", "source"},
{"libunistring", "0.9.10-1.fc28", "rpm", "source"},
{"libpcap", "14:1.9.0-1.fc28", "rpm", "binary"},
{"libarchive", "3.3.1-4.fc28", "rpm", "binary"},
{"gmp", "1:6.1.2-7.fc28", "rpm", "binary"},
{"crypto-policies", "20180425-5.git6ad4018.fc28", "rpm", "source"},
{"gzip", "1.9-3.fc28", "rpm", "binary"},
{"fedora-release", "28-2", "rpm", "source"},
{"zlib", "1.2.11-8.fc28", "rpm", "binary"},
{"crypto-policies", "20180425-5.git6ad4018.fc28", "rpm", "binary"},
{"lz4", "1.8.1.2-4.fc28", "rpm", "source"},
{"keyutils", "1.5.10-6.fc28", "rpm", "source"},
{"gpgme", "1.10.0-4.fc28", "rpm", "binary"},
{"libgpg-error", "1.31-1.fc28", "rpm", "binary"},
{"gnutls", "3.6.3-4.fc28", "rpm", "source"},
{"coreutils", "8.29-7.fc28", "rpm", "source"},
{"libsepol", "2.8-1.fc28", "rpm", "source"},
{"libssh", "0.8.2-1.fc28", "rpm", "binary"},
{"libpwquality", "1.4.0-7.fc28", "rpm", "binary"},
{"dnf-conf", "2.7.5-12.fc28", "rpm", "binary"},
{"basesystem", "11-5.fc28", "rpm", "source"},
{"setup", "2.11.4-1.fc28", "rpm", "binary"},
{"libmetalink", "0.1.3-6.fc28", "rpm", "source"},
{"texinfo", "6.5-4.fc28", "rpm", "source"},
{"expat", "2.2.5-3.fc28", "rpm", "source"},
{"ncurses", "6.1-5.20180224.fc28", "rpm", "source"},
{"libpwquality", "1.4.0-7.fc28", "rpm", "source"},
{"pcre", "8.42-3.fc28", "rpm", "binary"},
{"sssd", "1.16.3-2.fc28", "rpm", "source"},
{"basesystem", "11-5.fc28", "rpm", "binary"},
{"systemd-pam", "238-9.git0e0aa59.fc28", "rpm", "binary"},
{"python3-six", "1.11.0-3.fc28", "rpm", "binary"},
{"libcurl", "7.59.0-6.fc28", "rpm", "binary"},
{"qrencode", "3.4.4-5.fc28", "rpm", "source"},
{"xz", "5.2.4-2.fc28", "rpm", "source"},
{"libpkgconf", "1.4.2-1.fc28", "rpm", "binary"},
{"libzstd", "1.3.5-1.fc28", "rpm", "binary"},
{"bash", "4.4.23-1.fc28", "rpm", "binary"},
{"cyrus-sasl", "2.1.27-0.2rc7.fc28", "rpm", "source"},
{"ncurses-libs", "6.1-5.20180224.fc28", "rpm", "binary"},
{"xz-libs", "5.2.4-2.fc28", "rpm", "binary"},
{"dbus", "1.12.10-1.fc28", "rpm", "source"},
{"grep", "3.1-5.fc28", "rpm", "binary"},
{"libusbx", "1.0.22-1.fc28", "rpm", "binary"},
{"audit", "2.8.4-2.fc28", "rpm", "source"},
{"sed", "4.5-1.fc28", "rpm", "binary"},
{"sqlite", "3.22.0-4.fc28", "rpm", "source"},
{"openldap", "2.4.46-3.fc28", "rpm", "binary"},
{"gawk", "4.2.1-1.fc28", "rpm", "binary"},
{"gpgme", "1.10.0-4.fc28", "rpm", "source"},
{"lvm2", "2.02.177-5.fc28", "rpm", "source"},
{"nspr", "4.19.0-1.fc28", "rpm", "source"},
{"libsolv", "0.6.35-1.fc28", "rpm", "source"},
{"info", "6.5-4.fc28", "rpm", "binary"},
{"openssl-libs", "1:1.1.0h-3.fc28", "rpm", "binary"},
{"libxcrypt", "4.1.2-1.fc28", "rpm", "binary"},
{"libselinux", "2.8-1.fc28", "rpm", "binary"},
{"libgcc", "8.1.1-5.fc28", "rpm", "binary"},
{"cracklib", "2.9.6-13.fc28", "rpm", "binary"},
{"python3-libs", "3.6.6-1.fc28", "rpm", "binary"},
{"glibc-langpack-en", "2.27-32.fc28", "rpm", "binary"},
{"json-c", "0.13.1-2.fc28", "rpm", "binary"},
{"gnupg2", "2.2.8-1.fc28", "rpm", "source"},
{"openssl", "1:1.1.0h-3.fc28", "rpm", "binary"},
{"glibc-common", "2.27-32.fc28", "rpm", "binary"},
{"p11-kit-trust", "0.23.12-1.fc28", "rpm", "binary"},
{"zstd", "1.3.5-1.fc28", "rpm", "source"},
{"libxml2", "2.9.8-4.fc28", "rpm", "source"},
{"dbus", "1:1.12.10-1.fc28", "rpm", "binary"},
{"ca-certificates", "2018.2.24-1.0.fc28", "rpm", "binary"},
{"libcomps", "0.1.8-11.fc28", "rpm", "binary"},
{"nss", "3.38.0-1.0.fc28", "rpm", "binary"},
{"libcom_err", "1.44.2-0.fc28", "rpm", "binary"},
{"keyutils-libs", "1.5.10-6.fc28", "rpm", "binary"},
{"libseccomp", "2.3.3-2.fc28", "rpm", "binary"},
{"elfutils-libs", "0.173-1.fc28", "rpm", "binary"},
{"libuuid", "2.32.1-1.fc28", "rpm", "binary"},
{"pkgconf", "1.4.2-1.fc28", "rpm", "source"},
{"grep", "3.1-5.fc28", "rpm", "source"},
{"libpcap", "1.9.0-1.fc28", "rpm", "source"},
{"deltarpm", "3.6-25.fc28", "rpm", "binary"},
{"krb5-libs", "1.16.1-13.fc28", "rpm", "binary"},
{"glibc", "2.27-32.fc28", "rpm", "binary"},
{"libseccomp", "2.3.3-2.fc28", "rpm", "source"},
{"libsemanage", "2.8-2.fc28", "rpm", "binary"},
{"openssl-pkcs11", "0.4.8-1.fc28", "rpm", "binary"},
{"libxml2", "2.9.8-4.fc28", "rpm", "binary"},
{"e2fsprogs", "1.44.2-0.fc28", "rpm", "source"},
{"file-libs", "5.33-7.fc28", "rpm", "binary"},
{"elfutils-default-yama-scope", "0.173-1.fc28", "rpm", "binary"},
{"glibc", "2.27-32.fc28", "rpm", "source"},
{"publicsuffix-list-dafsa", "20180514-1.fc28", "rpm", "binary"},
{"popt", "1.16-14.fc28", "rpm", "binary"},
{"libnsl2", "1.2.0-2.20180605git4a062cf.fc28", "rpm", "binary"},
{"lua-libs", "5.3.4-10.fc28", "rpm", "binary"},
{"libsemanage", "2.8-2.fc28", "rpm", "source"},
{"glibc-minimal-langpack", "2.27-32.fc28", "rpm", "binary"},
{"attr", "2.4.48-3.fc28", "rpm", "source"},
{"gdbm", "1.14.1-4.fc28", "rpm", "source"},
{"pkgconf", "1.4.2-1.fc28", "rpm", "binary"},
{"acl", "2.2.53-1.fc28", "rpm", "source"},
{"gnutls", "3.6.3-4.fc28", "rpm", "binary"},
{"fedora-repos", "28-5", "rpm", "source"},
{"python3-pip", "9.0.3-2.fc28", "rpm", "binary"},
{"libnsl2", "1.2.0-2.20180605git4a062cf.fc28", "rpm", "source"},
{"rpm", "4.14.1-9.fc28", "rpm", "binary"},
{"libutempter", "1.1.6-14.fc28", "rpm", "source"},
{"libdnf", "0.11.1-3.fc28", "rpm", "source"},
{"vim-minimal", "2:8.1.328-1.fc28", "rpm", "binary"},
{"tzdata", "2018e-1.fc28", "rpm", "binary"},
{"nettle", "3.4-2.fc28", "rpm", "binary"},
{"python-pip", "9.0.3-2.fc28", "rpm", "source"},
{"python-six", "1.11.0-3.fc28", "rpm", "source"},
{"diffutils", "3.6-4.fc28", "rpm", "binary"},
{"rpm-plugin-selinux", "4.14.1-9.fc28", "rpm", "binary"},
{"shadow-utils", "2:4.6-1.fc28", "rpm", "binary"},
{"pkgconf-pkg-config", "1.4.2-1.fc28", "rpm", "binary"},
{"cracklib-dicts", "2.9.6-13.fc28", "rpm", "binary"},
{"libblkid", "2.32.1-1.fc28", "rpm", "binary"},
{"python-setuptools", "39.2.0-6.fc28", "rpm", "source"},
{"libsss_idmap", "1.16.3-2.fc28", "rpm", "binary"},
{"libksba", "1.3.5-7.fc28", "rpm", "source"},
{"sssd-client", "1.16.3-2.fc28", "rpm", "binary"},
{"curl", "7.59.0-6.fc28", "rpm", "binary"},
{"pam", "1.3.1-1.fc28", "rpm", "binary"},
{"libsigsegv", "2.11-5.fc28", "rpm", "binary"},
{"langpacks-en", "1.0-12.fc28", "rpm", "binary"},
{"nss-softokn-freebl", "3.38.0-1.0.fc28", "rpm", "binary"},
{"glib2", "2.56.1-4.fc28", "rpm", "binary"},
{"python3-gobject-base", "3.28.3-1.fc28", "rpm", "binary"},
{"libffi", "3.1-16.fc28", "rpm", "source"},
{"libmodulemd", "1.6.2-2.fc28", "rpm", "source"},
{"openssl", "1.1.0h-3.fc28", "rpm", "source"},
{"libyaml", "0.1.7-5.fc28", "rpm", "source"},
{"pam", "1.3.1-1.fc28", "rpm", "source"},
{"iptables", "1.6.2-3.fc28", "rpm", "source"},
{"util-linux", "2.32.1-1.fc28", "rpm", "source"},
{"libsmartcols", "2.32.1-1.fc28", "rpm", "binary"},
{"dnf", "2.7.5-12.fc28", "rpm", "binary"},
{"glib2", "2.56.1-4.fc28", "rpm", "source"},
{"lua", "5.3.4-10.fc28", "rpm", "source"},
{"nss-softokn", "3.38.0-1.0.fc28", "rpm", "source"},
{"python3-dnf", "2.7.5-12.fc28", "rpm", "binary"},
{"filesystem", "3.8-2.fc28", "rpm", "binary"},
{"libsss_nss_idmap", "1.16.3-2.fc28", "rpm", "binary"},
{"pcre2", "10.31-10.fc28", "rpm", "source"},
{"libyaml", "0.1.7-5.fc28", "rpm", "binary"},
{"python3-rpm", "4.14.1-9.fc28", "rpm", "binary"},
{"zlib", "1.2.11-8.fc28", "rpm", "source"},
{"libutempter", "1.1.6-14.fc28", "rpm", "binary"},
{"pcre2", "10.31-10.fc28", "rpm", "binary"},
{"libtirpc", "1.0.3-3.rc2.fc28", "rpm", "source"},
{"pkgconf-m4", "1.4.2-1.fc28", "rpm", "binary"},
{"libreport", "2.9.5-1.fc28", "rpm", "source"},
{"vim", "8.1.328-1.fc28", "rpm", "source"},
{"file", "5.33-7.fc28", "rpm", "source"},
{"shadow-utils", "4.6-1.fc28", "rpm", "source"},
{"sqlite-libs", "3.22.0-4.fc28", "rpm", "binary"},
{"setup", "2.11.4-1.fc28", "rpm", "source"},
{"gcc", "8.1.1-5.fc28", "rpm", "source"},
{"mpfr", "3.1.6-1.fc28", "rpm", "binary"},
{"device-mapper", "1.02.146-5.fc28", "rpm", "binary"},
{"p11-kit", "0.23.12-1.fc28", "rpm", "source"},
{"fedora-release", "28-2", "rpm", "binary"},
{"libnghttp2", "1.32.1-1.fc28", "rpm", "binary"},
{"libcap-ng", "0.7.9-4.fc28", "rpm", "source"},
{"iptables-libs", "1.6.2-3.fc28", "rpm", "binary"},
{"audit-libs", "2.8.4-2.fc28", "rpm", "binary"},
{"libsigsegv", "2.11-5.fc28", "rpm", "source"},
{"rootfiles", "8.1-22.fc28", "rpm", "source"},
{"kmod-libs", "25-2.fc28", "rpm", "binary"},
{"lz4-libs", "1.8.1.2-4.fc28", "rpm", "binary"},
{"libassuan", "2.5.1-3.fc28", "rpm", "source"},
{"p11-kit", "0.23.12-1.fc28", "rpm", "binary"},
{"nss-sysinit", "3.38.0-1.0.fc28", "rpm", "binary"},
{"libcap-ng", "0.7.9-4.fc28", "rpm", "binary"},
{"bash", "4.4.23-1.fc28", "rpm", "source"},
{"pygobject3", "3.28.3-1.fc28", "rpm", "source"},
{"dnf-yum", "2.7.5-12.fc28", "rpm", "binary"},
{"nss-softokn", "3.38.0-1.0.fc28", "rpm", "binary"},
{"expat", "2.2.5-3.fc28", "rpm", "binary"},
{"libassuan", "2.5.1-3.fc28", "rpm", "binary"},
{"libdb", "5.3.28-30.fc28", "rpm", "binary"},
{"tar", "2:1.30-3.fc28", "rpm", "binary"},
{"sed", "4.5-1.fc28", "rpm", "source"},
{"libmetalink", "0.1.3-6.fc28", "rpm", "binary"},
{"python-smartcols", "0.3.0-2.fc28", "rpm", "source"},
{"systemd", "238-9.git0e0aa59.fc28", "rpm", "source"},
{"python-iniparse", "0.4-30.fc28", "rpm", "source"},
{"libsepol", "2.8-1.fc28", "rpm", "binary"},
{"libattr", "2.4.48-3.fc28", "rpm", "binary"},
{"python3-smartcols", "0.3.0-2.fc28", "rpm", "binary"},
{"libdb", "5.3.28-30.fc28", "rpm", "source"},
{"libmodulemd", "1.6.2-2.fc28", "rpm", "binary"},
{"python3-hawkey", "0.11.1-3.fc28", "rpm", "binary"},
{"dbus-libs", "1:1.12.10-1.fc28", "rpm", "binary"},
{"chkconfig", "1.10-4.fc28", "rpm", "source"},
{"libargon2", "20161029-5.fc28", "rpm", "binary"},
{"openssl-pkcs11", "0.4.8-1.fc28", "rpm", "source"},
{"libusbx", "1.0.22-1.fc28", "rpm", "source"},
{"python3-setuptools", "39.2.0-6.fc28", "rpm", "binary"},
{"chkconfig", "1.10-4.fc28", "rpm", "binary"},
{"openldap", "2.4.46-3.fc28", "rpm", "source"},
{"bzip2", "1.0.6-26.fc28", "rpm", "source"},
{"npth", "1.5-4.fc28", "rpm", "source"},
{"libtirpc", "1.0.3-3.rc2.fc28", "rpm", "binary"},
{"util-linux", "2.32.1-1.fc28", "rpm", "binary"},
{"nss", "3.38.0-1.0.fc28", "rpm", "source"},
{"elfutils", "0.173-1.fc28", "rpm", "source"},
{"libcomps", "0.1.8-11.fc28", "rpm", "source"},
{"libxcrypt", "4.1.2-1.fc28", "rpm", "source"},
{"gnupg2", "2.2.8-1.fc28", "rpm", "binary"},
{"libdnf", "0.11.1-3.fc28", "rpm", "binary"},
{"cracklib", "2.9.6-13.fc28", "rpm", "source"},
{"libidn2", "2.0.5-1.fc28", "rpm", "source"},
{"bzip2-libs", "1.0.6-26.fc28", "rpm", "binary"},
{"json-c", "0.13.1-2.fc28", "rpm", "source"},
{"gdbm", "1:1.14.1-4.fc28", "rpm", "binary"},
{"pcre", "8.42-3.fc28", "rpm", "source"},
{"systemd", "238-9.git0e0aa59.fc28", "rpm", "binary"},
{"cryptsetup-libs", "2.0.4-1.fc28", "rpm", "binary"},
{"dnf", "2.7.5-12.fc28", "rpm", "source"},
{"ca-certificates", "2018.2.24-1.0.fc28", "rpm", "source"},
{"libidn2", "2.0.5-1.fc28", "rpm", "binary"},
{"libpsl", "0.20.2-2.fc28", "rpm", "binary"},
{"gdbm-libs", "1:1.14.1-4.fc28", "rpm", "binary"},
{"kmod", "25-2.fc28", "rpm", "source"},
{"libreport-filesystem", "2.9.5-1.fc28", "rpm", "binary"},
{"ima-evm-utils", "1.1-2.fc28", "rpm", "source"},
{"nghttp2", "1.32.1-1.fc28", "rpm", "source"},
{"cyrus-sasl-lib", "2.1.27-0.2rc7.fc28", "rpm", "binary"},
{"libsolv", "0.6.35-1.fc28", "rpm", "binary"},
{"cryptsetup", "2.0.4-1.fc28", "rpm", "source"},
{"filesystem", "3.8-2.fc28", "rpm", "source"},
{"libcap", "2.25-9.fc28", "rpm", "source"},
{"libpsl", "0.20.2-2.fc28", "rpm", "source"},
{"deltarpm", "3.6-25.fc28", "rpm", "source"},
{"fedora-gpg-keys", "28-5", "rpm", "binary"},
{"ima-evm-utils", "1.1-2.fc28", "rpm", "binary"},
{"nss-tools", "3.38.0-1.0.fc28", "rpm", "binary"},
{"libtasn1", "4.13-2.fc28", "rpm", "source"},
{"elfutils-libelf", "0.173-1.fc28", "rpm", "binary"},
{"device-mapper-libs", "1.02.146-5.fc28", "rpm", "binary"},
{"gobject-introspection", "1.56.1-1.fc28", "rpm", "source"},
{"publicsuffix-list", "20180514-1.fc28", "rpm", "source"},
{"libcap", "2.25-9.fc28", "rpm", "binary"},
{"librepo", "1.8.1-7.fc28", "rpm", "binary"},
{"rpm-sign-libs", "4.14.1-9.fc28", "rpm", "binary"},
{"coreutils-single", "8.29-7.fc28", "rpm", "binary"},
{"libacl", "2.2.53-1.fc28", "rpm", "binary"},
{"popt", "1.16-14.fc28", "rpm", "source"},
{"libtasn1", "4.13-2.fc28", "rpm", "binary"},
{"gawk", "4.2.1-1.fc28", "rpm", "source"},
{"diffutils", "3.6-4.fc28", "rpm", "source"},
{"libgpg-error", "1.31-1.fc28", "rpm", "source"},
{"libdb-utils", "5.3.28-30.fc28", "rpm", "binary"},
{"python3-iniparse", "0.4-30.fc28", "rpm", "binary"},
{"acl", "2.2.53-1.fc28", "rpm", "binary"},
{"libssh", "0.8.2-1.fc28", "rpm", "source"},
{"python3-librepo", "1.8.1-7.fc28", "rpm", "binary"},
{"gobject-introspection", "1.56.1-1.fc28", "rpm", "binary"},
{"rpm", "4.14.1-9.fc28", "rpm", "source"},
{"libgcrypt", "1.8.3-1.fc28", "rpm", "source"},
{"curl", "7.59.0-6.fc28", "rpm", "source"},
{"tzdata", "2018e-1.fc28", "rpm", "source"},
{"krb5", "1.16.1-13.fc28", "rpm", "source"},
{"librepo", "1.8.1-7.fc28", "rpm", "source"},
{"python3-gpg", "1.10.0-4.fc28", "rpm", "binary"},
{"nettle", "3.4-2.fc28", "rpm", "source"},
{"libgcrypt", "1.8.3-1.fc28", "rpm", "binary"},
{"python3", "3.6.6-1.fc28", "rpm", "binary"},
{"python3-libcomps", "0.1.8-11.fc28", "rpm", "binary"},
{"rpm-libs", "4.14.1-9.fc28", "rpm", "binary"},
{"nspr", "4.19.0-1.fc28", "rpm", "binary"},
{"argon2", "20161029-5.fc28", "rpm", "source"},
{"tar", "1.30-3.fc28", "rpm", "source"},
{"qrencode-libs", "3.4.4-5.fc28", "rpm", "binary"},
{"gmp", "6.1.2-7.fc28", "rpm", "source"},
{"libverto", "0.3.0-5.fc28", "rpm", "binary"},
{"python3", "3.6.6-1.fc28", "rpm", "source"},
{"libksba", "1.3.5-7.fc28", "rpm", "binary"},
{"readline", "7.0-11.fc28", "rpm", "binary"},
{"rpm-build-libs", "4.14.1-9.fc28", "rpm", "binary"},
{"npth", "1.5-4.fc28", "rpm", "binary"},
{"rootfiles", "8.1-22.fc28", "rpm", "binary"},
{"rpm-plugin-systemd-inhibit", "4.14.1-9.fc28", "rpm", "binary"},
{"systemd-libs", "238-9.git0e0aa59.fc28", "rpm", "binary"},
{"nss-util", "3.38.0-1.0.fc28", "rpm", "binary"},
}
func TestRpmFeatureDetection(t *testing.T) {
@ -206,8 +334,10 @@ func TestRpmFeatureDetection(t *testing.T) {
"valid small case",
map[string]string{"var/lib/rpm/Packages": "rpm/testdata/valid"},
[]database.Feature{
{"centos-release", "7-1.1503.el7.centos.2.8", "centos-release", "7-1.1503.el7.centos.2.8", rpm.ParserName},
{"filesystem", "3.2-18.el7", "filesystem", "3.2-18.el7", rpm.ParserName},
{"centos-release", "7-1.1503.el7.centos.2.8", "rpm", "binary"},
{"filesystem", "3.2-18.el7", "rpm", "binary"},
{"centos-release", "7-1.1503.el7.centos.2.8", "rpm", "source"},
{"filesystem", "3.2-18.el7", "rpm", "source"},
},
},
{
@ -248,15 +378,14 @@ func TestParseSourceRPM(t *testing.T) {
// actual expected: name="lua", version="5.3.4", release="10.fc-28"
{"lua-5.3.4-10.fc-28.src.rpm", "lua-5.3.4", "10.fc-28", ""},
} {
pkg := database.Feature{}
err := parseSourceRPM(test.sourceRPM, &pkg)
name, version, release, _, err := parseSourceRPM(test.sourceRPM)
if test.expectedErr != "" {
require.EqualError(t, err, test.expectedErr)
continue
}
require.Nil(t, err)
require.Equal(t, test.expectedName, pkg.SourceName)
require.Equal(t, test.expectedVersion, pkg.SourceVersion)
require.Equal(t, test.expectedName, name)
require.Equal(t, test.expectedVersion, version+"-"+release)
}
}

@ -40,7 +40,7 @@ const (
nvdURLPrefix = "https://cve.mitre.org/cgi-bin/cvename.cgi?name="
// affected type indicates if the affected feature hint is for binary or
// source package.
affectedType = database.AffectBinaryPackage
affectedType = database.BinaryPackage
)
func init() {
@ -177,7 +177,7 @@ func (file *secDB) Vulnerabilities() (vulns []database.VulnerabilityWithAffected
vuln.Affected = []database.AffectedFeature{
{
AffectedType: affectedType,
FeatureType: affectedType,
FeatureName: pkg.Pkg.Name,
AffectedVersion: version,
FixedInVersion: fixedInVersion,

@ -38,7 +38,7 @@ const (
url = "https://security-tracker.debian.org/tracker/data/json"
cveURLPrefix = "https://security-tracker.debian.org/tracker"
updaterFlag = "debianUpdater"
affectedType = database.AffectSourcePackage
affectedType = database.SourcePackage
)
type jsonData map[string]map[string]jsonVuln
@ -215,7 +215,7 @@ func parseDebianJSON(data *jsonData) (vulnerabilities []database.VulnerabilityWi
// Create and add the feature version.
pkg := database.AffectedFeature{
AffectedType: affectedType,
FeatureType: affectedType,
FeatureName: pkgName,
AffectedVersion: version,
FixedInVersion: fixedInVersion,

@ -41,7 +41,7 @@ func TestDebianParser(t *testing.T) {
expectedFeatures := []database.AffectedFeature{
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "debian:8",
VersionFormat: dpkg.ParserName,
@ -50,7 +50,7 @@ func TestDebianParser(t *testing.T) {
AffectedVersion: versionfmt.MaxVersion,
},
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "debian:unstable",
VersionFormat: dpkg.ParserName,
@ -71,7 +71,7 @@ func TestDebianParser(t *testing.T) {
expectedFeatures := []database.AffectedFeature{
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "debian:8",
VersionFormat: dpkg.ParserName,
@ -81,7 +81,7 @@ func TestDebianParser(t *testing.T) {
AffectedVersion: "0.7.0",
},
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "debian:unstable",
VersionFormat: dpkg.ParserName,
@ -91,7 +91,7 @@ func TestDebianParser(t *testing.T) {
AffectedVersion: "0.7.0",
},
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "debian:8",
VersionFormat: dpkg.ParserName,

@ -41,7 +41,7 @@ const (
ovalURI = "https://linux.oracle.com/oval/"
elsaFilePrefix = "com.oracle.elsa-"
updaterFlag = "oracleUpdater"
affectedType = database.AffectBinaryPackage
affectedType = database.BinaryPackage
)
var (
@ -365,7 +365,7 @@ func toFeatures(criteria criteria) []database.AffectedFeature {
} else if strings.Contains(c.Comment, " is earlier than ") {
const prefixLen = len(" is earlier than ")
featureVersion.FeatureName = strings.TrimSpace(c.Comment[:strings.Index(c.Comment, " is earlier than ")])
featureVersion.AffectedType = affectedType
featureVersion.FeatureType = affectedType
version := c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:]
err := versionfmt.Valid(rpm.ParserName, version)
if err != nil {

@ -43,7 +43,7 @@ func TestOracleParserOneCve(t *testing.T) {
expectedFeatures := []database.AffectedFeature{
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "oracle:7",
VersionFormat: rpm.ParserName,
@ -53,7 +53,7 @@ func TestOracleParserOneCve(t *testing.T) {
AffectedVersion: "0:3.1.1-7.el7_1",
},
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "oracle:7",
VersionFormat: rpm.ParserName,
@ -63,7 +63,7 @@ func TestOracleParserOneCve(t *testing.T) {
AffectedVersion: "0:3.1.1-7.el7_1",
},
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "oracle:7",
VersionFormat: rpm.ParserName,

@ -43,7 +43,7 @@ const (
ovalURI = "https://www.redhat.com/security/data/oval/"
rhsaFilePrefix = "com.redhat.rhsa-"
updaterFlag = "rhelUpdater"
affectedType = database.AffectBinaryPackage
affectedType = database.BinaryPackage
)
var (
@ -333,7 +333,7 @@ func toFeatures(criteria criteria) []database.AffectedFeature {
} else if strings.Contains(c.Comment, " is earlier than ") {
const prefixLen = len(" is earlier than ")
featureVersion.FeatureName = strings.TrimSpace(c.Comment[:strings.Index(c.Comment, " is earlier than ")])
featureVersion.AffectedType = affectedType
featureVersion.FeatureType = affectedType
version := c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:]
err := versionfmt.Valid(rpm.ParserName, version)
if err != nil {

@ -46,7 +46,7 @@ func TestRHELParserMultipleCVE(t *testing.T) {
database.MediumSeverity, database.MediumSeverity}
expectedFeatures := []database.AffectedFeature{
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "centos:6",
VersionFormat: rpm.ParserName,
@ -56,7 +56,7 @@ func TestRHELParserMultipleCVE(t *testing.T) {
AffectedVersion: "0:38.1.0-1.el6_6",
},
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "centos:7",
VersionFormat: rpm.ParserName,
@ -96,7 +96,7 @@ func TestRHELParserOneCVE(t *testing.T) {
expectedFeatures := []database.AffectedFeature{
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "centos:7",
VersionFormat: rpm.ParserName,
@ -106,7 +106,7 @@ func TestRHELParserOneCVE(t *testing.T) {
FixedInVersion: "0:3.1.1-7.el7_1",
},
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "centos:7",
VersionFormat: rpm.ParserName,
@ -116,7 +116,7 @@ func TestRHELParserOneCVE(t *testing.T) {
FixedInVersion: "0:3.1.1-7.el7_1",
},
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "centos:7",
VersionFormat: rpm.ParserName,

@ -39,7 +39,7 @@ const (
trackerURI = "https://git.launchpad.net/ubuntu-cve-tracker"
updaterFlag = "ubuntuUpdater"
cveURL = "http://people.ubuntu.com/~ubuntu-security/cve/%s"
affectedType = database.AffectSourcePackage
affectedType = database.SourcePackage
)
var (
@ -335,7 +335,7 @@ func parseUbuntuCVE(fileContent io.Reader) (vulnerability database.Vulnerability
// Create and add the new package.
featureVersion := database.AffectedFeature{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: releaseName,
VersionFormat: dpkg.ParserName,

@ -46,7 +46,7 @@ func TestUbuntuParser(t *testing.T) {
expectedFeatures := []database.AffectedFeature{
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "ubuntu:14.04",
VersionFormat: dpkg.ParserName,
@ -55,7 +55,7 @@ func TestUbuntuParser(t *testing.T) {
AffectedVersion: versionfmt.MaxVersion,
},
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "ubuntu:15.04",
VersionFormat: dpkg.ParserName,
@ -65,7 +65,7 @@ func TestUbuntuParser(t *testing.T) {
AffectedVersion: "0.4-3",
},
{
AffectedType: affectedType,
FeatureType: affectedType,
Namespace: database.Namespace{
Name: "ubuntu:15.10",
VersionFormat: dpkg.ParserName,

@ -499,7 +499,7 @@ func doVulnerabilitiesNamespacing(vulnerabilities []database.VulnerabilityWithAf
for _, fv := range namespacedFeatures {
// validate vulnerabilities, throw out the invalid vulnerabilities
if fv.AffectedType == "" || fv.AffectedVersion == "" || fv.FeatureName == "" || fv.Namespace.Name == "" || fv.Namespace.VersionFormat == "" {
if fv.FeatureType == "" || fv.AffectedVersion == "" || fv.FeatureName == "" || fv.Namespace.Name == "" || fv.Namespace.VersionFormat == "" {
log.WithFields(log.Fields{
"Name": fv.FeatureName,
"Affected Version": fv.AffectedVersion,

@ -183,7 +183,7 @@ func newmockUpdaterDatastore() *mockUpdaterDatastore {
func TestDoVulnerabilitiesNamespacing(t *testing.T) {
fv1 := database.AffectedFeature{
AffectedType: database.AffectSourcePackage,
FeatureType: database.SourcePackage,
Namespace: database.Namespace{Name: "Namespace1"},
FeatureName: "Feature1",
FixedInVersion: "0.1",
@ -191,7 +191,7 @@ func TestDoVulnerabilitiesNamespacing(t *testing.T) {
}
fv2 := database.AffectedFeature{
AffectedType: database.AffectSourcePackage,
FeatureType: database.SourcePackage,
Namespace: database.Namespace{Name: "Namespace2"},
FeatureName: "Feature1",
FixedInVersion: "0.2",
@ -199,7 +199,7 @@ func TestDoVulnerabilitiesNamespacing(t *testing.T) {
}
fv3 := database.AffectedFeature{
AffectedType: database.AffectSourcePackage,
FeatureType: database.SourcePackage,
Namespace: database.Namespace{Name: "Namespace2"},
FeatureName: "Feature2",
FixedInVersion: "0.3",
@ -237,9 +237,9 @@ func TestCreatVulnerabilityNotification(t *testing.T) {
VersionFormat: vf1,
}
af1 := database.AffectedFeature{
AffectedType: database.AffectSourcePackage,
Namespace: ns1,
FeatureName: "feature 1",
FeatureType: database.SourcePackage,
Namespace: ns1,
FeatureName: "feature 1",
}
v1 := database.VulnerabilityWithAffected{

@ -284,11 +284,15 @@ func TestProcessAncestryWithDistUpgrade(t *testing.T) {
{Name: "db", Version: "5.1.29-5"},
{Name: "ustr", Version: "1.0.4-3"},
{Name: "xz-utils", Version: "5.1.1alpha+20120614-2"},
{Name: "libdb5.1", Version: "5.1.29-5"},
}
nonUpgradedMap := map[database.Feature]struct{}{}
for _, f := range nonUpgradedFeatures {
f.VersionFormat = "dpkg"
f.Type = database.SourcePackage
nonUpgradedMap[f] = struct{}{}
f.Type = database.BinaryPackage
nonUpgradedMap[f] = struct{}{}
}
@ -318,12 +322,12 @@ func TestProcessAncestryWithDistUpgrade(t *testing.T) {
features = append(features, l.Features...)
}
assert.Len(t, features, 74)
assert.Len(t, features, 161)
for _, f := range features {
if _, ok := nonUpgradedMap[f.Feature]; ok {
assert.Equal(t, "debian:7", f.Namespace.Name)
assert.Equal(t, "debian:7", f.Namespace.Name, "%#v", f)
} else {
assert.Equal(t, "debian:8", f.Namespace.Name)
assert.Equal(t, "debian:8", f.Namespace.Name, "#%v", f)
}
}
}
@ -352,8 +356,8 @@ func TestProcessLayers(t *testing.T) {
assert.Len(t, LayerWithContents[1].Namespaces, 1)
assert.Len(t, LayerWithContents[2].Namespaces, 1)
assert.Len(t, LayerWithContents[0].Features, 0)
assert.Len(t, LayerWithContents[1].Features, 52)
assert.Len(t, LayerWithContents[2].Features, 74)
assert.Len(t, LayerWithContents[1].Features, 132)
assert.Len(t, LayerWithContents[2].Features, 191)
// Ensure each layer has expected namespaces and features detected
if blank, ok := datastore.layers["blank"]; ok {
@ -371,7 +375,7 @@ func TestProcessLayers(t *testing.T) {
{database.Namespace{"debian:7", dpkg.ParserName}, database.NewNamespaceDetector("os-release", "1.0")},
}, wheezy.Namespaces)
assert.Len(t, wheezy.Features, 52)
assert.Len(t, wheezy.Features, 132)
} else {
assert.Fail(t, "wheezy is not stored")
return
@ -382,7 +386,7 @@ func TestProcessLayers(t *testing.T) {
assert.Equal(t, []database.LayerNamespace{
{database.Namespace{"debian:8", dpkg.ParserName}, database.NewNamespaceDetector("os-release", "1.0")},
}, jessie.Namespaces)
assert.Len(t, jessie.Features, 74)
assert.Len(t, jessie.Features, 191)
} else {
assert.Fail(t, "jessie is not stored")
return

Loading…
Cancel
Save