2017-06-05 14:37:29 +00:00
|
|
|
// Copyright 2017 clair authors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2017-08-16 21:26:53 +00:00
|
|
|
package v3
|
2017-06-05 14:37:29 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
"github.com/golang/protobuf/ptypes"
|
2017-06-05 14:37:29 +00:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
|
|
|
|
|
|
|
"github.com/coreos/clair"
|
2017-08-16 21:26:53 +00:00
|
|
|
pb "github.com/coreos/clair/api/v3/clairpb"
|
2017-06-05 14:37:29 +00:00
|
|
|
"github.com/coreos/clair/database"
|
|
|
|
"github.com/coreos/clair/pkg/commonerr"
|
|
|
|
)
|
|
|
|
|
|
|
|
// NotificationServer implements NotificationService interface for serving RPC.
|
|
|
|
type NotificationServer struct {
|
2017-07-12 21:04:05 +00:00
|
|
|
Store database.Datastore
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AncestryServer implements AncestryService interface for serving RPC.
|
|
|
|
type AncestryServer struct {
|
|
|
|
Store database.Datastore
|
|
|
|
}
|
|
|
|
|
|
|
|
// PostAncestry implements posting an ancestry via the Clair gRPC service.
|
|
|
|
func (s *AncestryServer) PostAncestry(ctx context.Context, req *pb.PostAncestryRequest) (*pb.PostAncestryResponse, error) {
|
|
|
|
ancestryName := req.GetAncestryName()
|
|
|
|
if ancestryName == "" {
|
2017-07-12 21:04:05 +00:00
|
|
|
return nil, status.Error(codes.InvalidArgument, "ancestry name should not be empty")
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
layers := req.GetLayers()
|
|
|
|
if len(layers) == 0 {
|
2017-07-12 21:04:05 +00:00
|
|
|
return nil, status.Error(codes.InvalidArgument, "ancestry should have at least one layer")
|
|
|
|
}
|
|
|
|
|
|
|
|
ancestryFormat := req.GetFormat()
|
|
|
|
if ancestryFormat == "" {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "ancestry format should not be empty")
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
ancestryLayers := []clair.LayerRequest{}
|
|
|
|
for _, layer := range layers {
|
2017-06-05 14:37:29 +00:00
|
|
|
if layer == nil {
|
2017-07-12 21:04:05 +00:00
|
|
|
err := status.Error(codes.InvalidArgument, "ancestry layer is invalid")
|
|
|
|
return nil, err
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
if layer.GetHash() == "" {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "ancestry layer hash should not be empty")
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
if layer.GetPath() == "" {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "ancestry layer path should not be empty")
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
ancestryLayers = append(ancestryLayers, clair.LayerRequest{
|
|
|
|
Hash: layer.Hash,
|
|
|
|
Headers: layer.Headers,
|
|
|
|
Path: layer.Path,
|
|
|
|
})
|
|
|
|
}
|
2017-06-05 14:37:29 +00:00
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
err := clair.ProcessAncestry(s.Store, ancestryFormat, ancestryName, ancestryLayers)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, "ancestry is failed to be processed: "+err.Error())
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
clairStatus, err := s.getClairStatus()
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
return &pb.PostAncestryResponse{Status: clairStatus}, nil
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
func (s *AncestryServer) getClairStatus() (*pb.ClairStatus, error) {
|
|
|
|
status := &pb.ClairStatus{
|
|
|
|
Listers: clair.Processors.Listers,
|
|
|
|
Detectors: clair.Processors.Detectors,
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
t, firstUpdate, err := clair.GetLastUpdateTime(s.Store)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if firstUpdate {
|
|
|
|
return status, nil
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
status.LastUpdateTime, err = ptypes.TimestampProto(t)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return status, nil
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
// GetAncestry implements retrieving an ancestry via the Clair gRPC service.
|
|
|
|
func (s *AncestryServer) GetAncestry(ctx context.Context, req *pb.GetAncestryRequest) (*pb.GetAncestryResponse, error) {
|
|
|
|
if req.GetAncestryName() == "" {
|
|
|
|
return nil, status.Errorf(codes.InvalidArgument, "ancestry name should not be empty")
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
tx, err := s.Store.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
2017-07-12 21:04:05 +00:00
|
|
|
defer tx.Rollback()
|
2017-06-05 14:37:29 +00:00
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
ancestry, _, ok, err := tx.FindAncestry(req.GetAncestryName())
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
|
|
|
} else if !ok {
|
|
|
|
return nil, status.Error(codes.NotFound, fmt.Sprintf("requested ancestry '%s' is not found", req.GetAncestryName()))
|
|
|
|
}
|
|
|
|
|
|
|
|
pbAncestry := pb.AncestryFromDatabaseModel(ancestry)
|
|
|
|
if req.GetWithFeatures() || req.GetWithVulnerabilities() {
|
|
|
|
ancestryWFeature, ok, err := tx.FindAncestryFeatures(ancestry.Name)
|
2017-06-05 14:37:29 +00:00
|
|
|
if err != nil {
|
2017-07-12 21:04:05 +00:00
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
2017-07-12 21:04:05 +00:00
|
|
|
|
|
|
|
if !ok {
|
|
|
|
return nil, status.Error(codes.NotFound, fmt.Sprintf("requested ancestry '%s' is not found", req.GetAncestryName()))
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
2017-07-12 21:04:05 +00:00
|
|
|
pbAncestry.ScannedDetectors = ancestryWFeature.ProcessedBy.Detectors
|
|
|
|
pbAncestry.ScannedListers = ancestryWFeature.ProcessedBy.Listers
|
2017-06-05 14:37:29 +00:00
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
if req.GetWithVulnerabilities() {
|
|
|
|
featureVulnerabilities, err := tx.FindAffectedNamespacedFeatures(ancestryWFeature.Features)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, fv := range featureVulnerabilities {
|
|
|
|
// Ensure that every feature can be found.
|
|
|
|
if !fv.Valid {
|
|
|
|
return nil, status.Error(codes.Internal, "ancestry feature is not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
pbFeature := pb.NamespacedFeatureFromDatabaseModel(fv.NamespacedFeature)
|
|
|
|
for _, v := range fv.AffectedBy {
|
|
|
|
pbVuln, err := pb.VulnerabilityWithFixedInFromDatabaseModel(v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
pbFeature.Vulnerabilities = append(pbFeature.Vulnerabilities, pbVuln)
|
|
|
|
}
|
|
|
|
|
|
|
|
pbAncestry.Features = append(pbAncestry.Features, pbFeature)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for _, f := range ancestryWFeature.Features {
|
|
|
|
pbAncestry.Features = append(pbAncestry.Features, pb.NamespacedFeatureFromDatabaseModel(f))
|
|
|
|
}
|
|
|
|
}
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
clairStatus, err := s.getClairStatus()
|
2017-06-05 14:37:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
return &pb.GetAncestryResponse{
|
|
|
|
Status: clairStatus,
|
|
|
|
Ancestry: pbAncestry,
|
|
|
|
}, nil
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
// GetNotification implements retrieving a notification via the Clair gRPC
|
2017-06-05 14:37:29 +00:00
|
|
|
// service.
|
2017-07-12 21:04:05 +00:00
|
|
|
func (s *NotificationServer) GetNotification(ctx context.Context, req *pb.GetNotificationRequest) (*pb.GetNotificationResponse, error) {
|
2017-06-05 14:37:29 +00:00
|
|
|
if req.GetName() == "" {
|
2017-07-12 21:04:05 +00:00
|
|
|
return nil, status.Error(codes.InvalidArgument, "notification name should not be empty")
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
if req.GetLimit() <= 0 {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "notification page limit should not be empty or less than 1")
|
|
|
|
}
|
|
|
|
|
|
|
|
tx, err := s.Store.Begin()
|
|
|
|
if err != nil {
|
2017-06-05 14:37:29 +00:00
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
|
|
|
}
|
2017-07-12 21:04:05 +00:00
|
|
|
defer tx.Rollback()
|
2017-06-05 14:37:29 +00:00
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
dbNotification, ok, err := tx.FindVulnerabilityNotification(
|
|
|
|
req.GetName(),
|
|
|
|
int(req.GetLimit()),
|
|
|
|
database.PageNumber(req.GetOldVulnerabilityPage()),
|
|
|
|
database.PageNumber(req.GetNewVulnerabilityPage()),
|
|
|
|
)
|
2017-06-05 14:37:29 +00:00
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
if !ok {
|
|
|
|
return nil, status.Error(codes.NotFound, fmt.Sprintf("requested notification '%s' is not found", req.GetName()))
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
notification, err := pb.NotificationFromDatabaseModel(dbNotification)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
return &pb.GetNotificationResponse{Notification: notification}, nil
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
// MarkNotificationAsRead implements deleting a notification via the Clair gRPC
|
|
|
|
// service.
|
2018-04-23 19:36:52 +00:00
|
|
|
func (s *NotificationServer) MarkNotificationAsRead(ctx context.Context, req *pb.MarkNotificationAsReadRequest) (*pb.MarkNotificationAsReadResponse, error) {
|
2017-07-12 21:04:05 +00:00
|
|
|
if req.GetName() == "" {
|
|
|
|
return nil, status.Error(codes.InvalidArgument, "notification name should not be empty")
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
tx, err := s.Store.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
defer tx.Rollback()
|
|
|
|
err = tx.DeleteNotification(req.GetName())
|
|
|
|
if err == commonerr.ErrNotFound {
|
|
|
|
return nil, status.Error(codes.NotFound, "requested notification \""+req.GetName()+"\" is not found")
|
|
|
|
} else if err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2017-07-12 21:04:05 +00:00
|
|
|
if err := tx.Commit(); err != nil {
|
|
|
|
return nil, status.Error(codes.Internal, err.Error())
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|
|
|
|
|
2018-04-23 19:36:52 +00:00
|
|
|
return &pb.MarkNotificationAsReadResponse{}, nil
|
2017-06-05 14:37:29 +00:00
|
|
|
}
|