From 8f9aa1fbe9f38faa83e9fa0a4005493d84b5687c Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Fri, 15 Sep 2023 09:53:31 -0500 Subject: [PATCH] feat(sbom): return SBOMs if present This will extract the SBOMs from the build and return to the builder if present. Additionally, it will attempt to send the SBOMs to the API. Signed-off-by: Chris Goller --- control/control.go | 78 +++- depot/api/cloudv3connect/machine.connect.go | 27 ++ depot/api/machine.pb.go | 435 +++++++++++++++++--- depot/api/machine.proto | 22 + depot/auth.go | 13 + depot/sbom.go | 48 +++ depot/spiffe.go | 23 ++ exporter/containerimage/export.go | 12 + exporter/containerimage/writer.go | 42 +- frontend/attestations/sbom/sbom.go | 16 + solver/llbsolver/solver.go | 119 +++++- 11 files changed, 726 insertions(+), 109 deletions(-) create mode 100644 depot/auth.go create mode 100644 depot/sbom.go create mode 100644 depot/spiffe.go diff --git a/control/control.go b/control/control.go index 36e07ff1458c..bd39b91d4d6b 100644 --- a/control/control.go +++ b/control/control.go @@ -10,6 +10,7 @@ import ( "sync/atomic" "time" + "connectrpc.com/connect" contentapi "github.com/containerd/containerd/api/services/content/v1" leasesapi "github.com/containerd/containerd/api/services/leases/v1" "github.com/containerd/containerd/content" @@ -24,6 +25,7 @@ import ( "github.com/moby/buildkit/client" "github.com/moby/buildkit/cmd/buildkitd/config" controlgateway "github.com/moby/buildkit/control/gateway" + "github.com/moby/buildkit/depot" cloudv3 "github.com/moby/buildkit/depot/api" "github.com/moby/buildkit/depot/api/cloudv3connect" "github.com/moby/buildkit/exporter" @@ -50,9 +52,7 @@ import ( "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" - "google.golang.org/grpc/peer" "google.golang.org/grpc/status" ) @@ -357,6 +357,9 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (* atomic.AddInt64(&c.buildCount, 1) defer atomic.AddInt64(&c.buildCount, -1) + spiffeID := depot.SpiffeFromContext(ctx) + bearer := depot.BearerFromEnv() + // This method registers job ID in solver.Solve. Make sure there are no blocking calls before that might delay this. if err := translateLegacySolveRequest(req); err != nil { @@ -478,7 +481,7 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (* procs = append(procs, proc.ProvenanceProcessor(attrs)) } - resp, err := c.solver.Solve(ctx, req.Ref, req.Session, frontend.SolveRequest{ + resp, sboms, err := c.solver.Solve(ctx, req.Ref, req.Session, frontend.SolveRequest{ Frontend: req.Frontend, Definition: req.Definition, FrontendOpt: req.FrontendAttrs, @@ -493,23 +496,66 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (* if err != nil { return nil, err } + + // DEPOT: send SBOMs to the API in the background. + go func() { + if sboms == nil || spiffeID == "" || bearer == "" { + return + } + + apiSBOMs := []*cloudv3.SBOM{} + for _, sbom := range sboms { + apiSBOM := &cloudv3.SBOM{ + Platform: sbom.Platform, + SpdxJson: string(sbom.Statement), + Digest: sbom.Digest, + } + if sbom.Image != nil { + apiSBOM.Image = &cloudv3.Image{ + Name: sbom.Image.Name, + ManifestDigest: sbom.Image.ManifestDigest, + } + } + + apiSBOMs = append(apiSBOMs, apiSBOM) + } + + req := connect.NewRequest(&cloudv3.ReportSBOMRequest{ + SpiffeId: spiffeID, + Sboms: apiSBOMs, + }) + req.Header().Add("Authorization", bearer) + + attempts := 0 + for { + attempts++ + _, err := NewDepotClient().ReportSBOM(context.Background(), req) + if err == nil { + break + } + + if attempts > 10 { + bklog.G(ctx).WithError(err).Errorf("unable to send SBOM to API, giving up") + return + } + + bklog.G(ctx).WithError(err).Errorf("unable to send SBOM to API, retrying") + time.Sleep(100 * time.Millisecond) + } + }() + return &controlapi.SolveResponse{ ExporterResponse: resp.ExporterResponse, }, nil } func (c *Controller) Status(req *controlapi.StatusRequest, stream controlapi.Control_StatusServer) error { - var spiffeID string ctx := stream.Context() - peer, ok := peer.FromContext(ctx) - if ok { - tlsInfo, ok := peer.AuthInfo.(credentials.TLSInfo) - if ok && tlsInfo.SPIFFEID != nil { - spiffeID = tlsInfo.SPIFFEID.String() - } - } + + spiffeID := depot.SpiffeFromContext(ctx) + bearer := depot.BearerFromEnv() + statusCh := make(chan client.SolveStatus, 1024) - token := os.Getenv("DEPOT_BUILDKIT_TOKEN") if err := sendTimestampHeader(stream); err != nil { return err @@ -522,12 +568,12 @@ func (c *Controller) Status(req *controlapi.StatusRequest, stream controlapi.Con }) go func() { - if spiffeID == "" || token == "" { + if spiffeID == "" || bearer == "" { return } sender := NewDepotClient().ReportStatus(context.Background()) - sender.RequestHeader().Add("Authorization", "Bearer "+token) + sender.RequestHeader().Add("Authorization", bearer) defer func() { _, _ = sender.CloseAndReceive() }() @@ -561,7 +607,7 @@ func (c *Controller) Status(req *controlapi.StatusRequest, stream controlapi.Con time.Sleep(100 * time.Millisecond) _, _ = sender.CloseAndReceive() sender = NewDepotClient().ReportStatus(ctx) - sender.RequestHeader().Add("Authorization", "Bearer "+token) + sender.RequestHeader().Add("Authorization", bearer) } } } @@ -577,7 +623,7 @@ func (c *Controller) Status(req *controlapi.StatusRequest, stream controlapi.Con } // DEPOT: we need to make a copy because ss.Marshal() mutates the SolveStatus - if spiffeID != "" && token != "" && ss != nil { + if spiffeID != "" && bearer != "" && ss != nil { select { case statusCh <- *ss: default: diff --git a/depot/api/cloudv3connect/machine.connect.go b/depot/api/cloudv3connect/machine.connect.go index 748d36dcde77..8da526a4d9d4 100644 --- a/depot/api/cloudv3connect/machine.connect.go +++ b/depot/api/cloudv3connect/machine.connect.go @@ -44,6 +44,9 @@ const ( // MachineServiceReportStatusProcedure is the fully-qualified name of the MachineService's // ReportStatus RPC. MachineServiceReportStatusProcedure = "/depot.cloud.v3.MachineService/ReportStatus" + // MachineServiceReportSBOMProcedure is the fully-qualified name of the MachineService's ReportSBOM + // RPC. + MachineServiceReportSBOMProcedure = "/depot.cloud.v3.MachineService/ReportSBOM" ) // MachineServiceClient is a client for the depot.cloud.v3.MachineService service. @@ -52,6 +55,7 @@ type MachineServiceClient interface { PingMachineHealth(context.Context, *connect.Request[api.PingMachineHealthRequest]) (*connect.Response[api.PingMachineHealthResponse], error) Usage(context.Context, *connect.Request[api.UsageRequest]) (*connect.Response[api.UsageResponse], error) ReportStatus(context.Context) *connect.ClientStreamForClient[api.ReportStatusRequest, api.ReportStatusResponse] + ReportSBOM(context.Context, *connect.Request[api.ReportSBOMRequest]) (*connect.Response[api.ReportSBOMResponse], error) } // NewMachineServiceClient constructs a client for the depot.cloud.v3.MachineService service. By @@ -84,6 +88,11 @@ func NewMachineServiceClient(httpClient connect.HTTPClient, baseURL string, opts baseURL+MachineServiceReportStatusProcedure, opts..., ), + reportSBOM: connect.NewClient[api.ReportSBOMRequest, api.ReportSBOMResponse]( + httpClient, + baseURL+MachineServiceReportSBOMProcedure, + opts..., + ), } } @@ -93,6 +102,7 @@ type machineServiceClient struct { pingMachineHealth *connect.Client[api.PingMachineHealthRequest, api.PingMachineHealthResponse] usage *connect.Client[api.UsageRequest, api.UsageResponse] reportStatus *connect.Client[api.ReportStatusRequest, api.ReportStatusResponse] + reportSBOM *connect.Client[api.ReportSBOMRequest, api.ReportSBOMResponse] } // RegisterMachine calls depot.cloud.v3.MachineService.RegisterMachine. @@ -115,12 +125,18 @@ func (c *machineServiceClient) ReportStatus(ctx context.Context) *connect.Client return c.reportStatus.CallClientStream(ctx) } +// ReportSBOM calls depot.cloud.v3.MachineService.ReportSBOM. +func (c *machineServiceClient) ReportSBOM(ctx context.Context, req *connect.Request[api.ReportSBOMRequest]) (*connect.Response[api.ReportSBOMResponse], error) { + return c.reportSBOM.CallUnary(ctx, req) +} + // MachineServiceHandler is an implementation of the depot.cloud.v3.MachineService service. type MachineServiceHandler interface { RegisterMachine(context.Context, *connect.Request[api.RegisterMachineRequest], *connect.ServerStream[api.RegisterMachineResponse]) error PingMachineHealth(context.Context, *connect.Request[api.PingMachineHealthRequest]) (*connect.Response[api.PingMachineHealthResponse], error) Usage(context.Context, *connect.Request[api.UsageRequest]) (*connect.Response[api.UsageResponse], error) ReportStatus(context.Context, *connect.ClientStream[api.ReportStatusRequest]) (*connect.Response[api.ReportStatusResponse], error) + ReportSBOM(context.Context, *connect.Request[api.ReportSBOMRequest]) (*connect.Response[api.ReportSBOMResponse], error) } // NewMachineServiceHandler builds an HTTP handler from the service implementation. It returns the @@ -149,6 +165,11 @@ func NewMachineServiceHandler(svc MachineServiceHandler, opts ...connect.Handler svc.ReportStatus, opts..., ) + machineServiceReportSBOMHandler := connect.NewUnaryHandler( + MachineServiceReportSBOMProcedure, + svc.ReportSBOM, + opts..., + ) return "/depot.cloud.v3.MachineService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case MachineServiceRegisterMachineProcedure: @@ -159,6 +180,8 @@ func NewMachineServiceHandler(svc MachineServiceHandler, opts ...connect.Handler machineServiceUsageHandler.ServeHTTP(w, r) case MachineServiceReportStatusProcedure: machineServiceReportStatusHandler.ServeHTTP(w, r) + case MachineServiceReportSBOMProcedure: + machineServiceReportSBOMHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -183,3 +206,7 @@ func (UnimplementedMachineServiceHandler) Usage(context.Context, *connect.Reques func (UnimplementedMachineServiceHandler) ReportStatus(context.Context, *connect.ClientStream[api.ReportStatusRequest]) (*connect.Response[api.ReportStatusResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("depot.cloud.v3.MachineService.ReportStatus is not implemented")) } + +func (UnimplementedMachineServiceHandler) ReportSBOM(context.Context, *connect.Request[api.ReportSBOMRequest]) (*connect.Response[api.ReportSBOMResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("depot.cloud.v3.MachineService.ReportSBOM is not implemented")) +} diff --git a/depot/api/machine.pb.go b/depot/api/machine.pb.go index 48d0137bedcb..de4342a0e2b4 100644 --- a/depot/api/machine.pb.go +++ b/depot/api/machine.pb.go @@ -731,6 +731,227 @@ func (*ReportStatusResponse) Descriptor() ([]byte, []int) { return file_machine_proto_rawDescGZIP(), []int{10} } +type ReportSBOMRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SpiffeId string `protobuf:"bytes,1,opt,name=spiffe_id,json=spiffeId,proto3" json:"spiffe_id,omitempty"` + Sboms []*SBOM `protobuf:"bytes,2,rep,name=sboms,proto3" json:"sboms,omitempty"` +} + +func (x *ReportSBOMRequest) Reset() { + *x = ReportSBOMRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_machine_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReportSBOMRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReportSBOMRequest) ProtoMessage() {} + +func (x *ReportSBOMRequest) ProtoReflect() protoreflect.Message { + mi := &file_machine_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReportSBOMRequest.ProtoReflect.Descriptor instead. +func (*ReportSBOMRequest) Descriptor() ([]byte, []int) { + return file_machine_proto_rawDescGZIP(), []int{11} +} + +func (x *ReportSBOMRequest) GetSpiffeId() string { + if x != nil { + return x.SpiffeId + } + return "" +} + +func (x *ReportSBOMRequest) GetSboms() []*SBOM { + if x != nil { + return x.Sboms + } + return nil +} + +type SBOM struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Platform string `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"` + SpdxJson string `protobuf:"bytes,2,opt,name=spdx_json,json=spdxJson,proto3" json:"spdx_json,omitempty"` + Digest string `protobuf:"bytes,3,opt,name=digest,proto3" json:"digest,omitempty"` + // If the build was exported to an image the image name and digest are included. + Image *Image `protobuf:"bytes,4,opt,name=image,proto3,oneof" json:"image,omitempty"` +} + +func (x *SBOM) Reset() { + *x = SBOM{} + if protoimpl.UnsafeEnabled { + mi := &file_machine_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SBOM) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SBOM) ProtoMessage() {} + +func (x *SBOM) ProtoReflect() protoreflect.Message { + mi := &file_machine_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SBOM.ProtoReflect.Descriptor instead. +func (*SBOM) Descriptor() ([]byte, []int) { + return file_machine_proto_rawDescGZIP(), []int{12} +} + +func (x *SBOM) GetPlatform() string { + if x != nil { + return x.Platform + } + return "" +} + +func (x *SBOM) GetSpdxJson() string { + if x != nil { + return x.SpdxJson + } + return "" +} + +func (x *SBOM) GetDigest() string { + if x != nil { + return x.Digest + } + return "" +} + +func (x *SBOM) GetImage() *Image { + if x != nil { + return x.Image + } + return nil +} + +type Image struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name is the image name and tag. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + ManifestDigest string `protobuf:"bytes,2,opt,name=manifest_digest,json=manifestDigest,proto3" json:"manifest_digest,omitempty"` +} + +func (x *Image) Reset() { + *x = Image{} + if protoimpl.UnsafeEnabled { + mi := &file_machine_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Image) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Image) ProtoMessage() {} + +func (x *Image) ProtoReflect() protoreflect.Message { + mi := &file_machine_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Image.ProtoReflect.Descriptor instead. +func (*Image) Descriptor() ([]byte, []int) { + return file_machine_proto_rawDescGZIP(), []int{13} +} + +func (x *Image) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Image) GetManifestDigest() string { + if x != nil { + return x.ManifestDigest + } + return "" +} + +type ReportSBOMResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ReportSBOMResponse) Reset() { + *x = ReportSBOMResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_machine_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReportSBOMResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReportSBOMResponse) ProtoMessage() {} + +func (x *ReportSBOMResponse) ProtoReflect() protoreflect.Message { + mi := &file_machine_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReportSBOMResponse.ProtoReflect.Descriptor instead. +func (*ReportSBOMResponse) Descriptor() ([]byte, []int) { + return file_machine_proto_rawDescGZIP(), []int{14} +} + type RegisterMachineRequest_AWSRegistration struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -743,7 +964,7 @@ type RegisterMachineRequest_AWSRegistration struct { func (x *RegisterMachineRequest_AWSRegistration) Reset() { *x = RegisterMachineRequest_AWSRegistration{} if protoimpl.UnsafeEnabled { - mi := &file_machine_proto_msgTypes[11] + mi := &file_machine_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -756,7 +977,7 @@ func (x *RegisterMachineRequest_AWSRegistration) String() string { func (*RegisterMachineRequest_AWSRegistration) ProtoMessage() {} func (x *RegisterMachineRequest_AWSRegistration) ProtoReflect() protoreflect.Message { - mi := &file_machine_proto_msgTypes[11] + mi := &file_machine_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -801,7 +1022,7 @@ type RegisterMachineResponse_Mount struct { func (x *RegisterMachineResponse_Mount) Reset() { *x = RegisterMachineResponse_Mount{} if protoimpl.UnsafeEnabled { - mi := &file_machine_proto_msgTypes[12] + mi := &file_machine_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -814,7 +1035,7 @@ func (x *RegisterMachineResponse_Mount) String() string { func (*RegisterMachineResponse_Mount) ProtoMessage() {} func (x *RegisterMachineResponse_Mount) ProtoReflect() protoreflect.Message { - mi := &file_machine_proto_msgTypes[12] + mi := &file_machine_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -875,7 +1096,7 @@ type RegisterMachineResponse_PendingTask struct { func (x *RegisterMachineResponse_PendingTask) Reset() { *x = RegisterMachineResponse_PendingTask{} if protoimpl.UnsafeEnabled { - mi := &file_machine_proto_msgTypes[13] + mi := &file_machine_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -888,7 +1109,7 @@ func (x *RegisterMachineResponse_PendingTask) String() string { func (*RegisterMachineResponse_PendingTask) ProtoMessage() {} func (x *RegisterMachineResponse_PendingTask) ProtoReflect() protoreflect.Message { - mi := &file_machine_proto_msgTypes[13] + mi := &file_machine_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -930,7 +1151,7 @@ type RegisterMachineResponse_BuildKitTask struct { func (x *RegisterMachineResponse_BuildKitTask) Reset() { *x = RegisterMachineResponse_BuildKitTask{} if protoimpl.UnsafeEnabled { - mi := &file_machine_proto_msgTypes[14] + mi := &file_machine_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -943,7 +1164,7 @@ func (x *RegisterMachineResponse_BuildKitTask) String() string { func (*RegisterMachineResponse_BuildKitTask) ProtoMessage() {} func (x *RegisterMachineResponse_BuildKitTask) ProtoReflect() protoreflect.Message { - mi := &file_machine_proto_msgTypes[14] + mi := &file_machine_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1064,7 +1285,7 @@ type RegisterMachineResponse_Profiler struct { func (x *RegisterMachineResponse_Profiler) Reset() { *x = RegisterMachineResponse_Profiler{} if protoimpl.UnsafeEnabled { - mi := &file_machine_proto_msgTypes[15] + mi := &file_machine_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1077,7 +1298,7 @@ func (x *RegisterMachineResponse_Profiler) String() string { func (*RegisterMachineResponse_Profiler) ProtoMessage() {} func (x *RegisterMachineResponse_Profiler) ProtoReflect() protoreflect.Message { - mi := &file_machine_proto_msgTypes[15] + mi := &file_machine_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1133,7 +1354,7 @@ type RegisterMachineResponse_Mount_CephVolume struct { func (x *RegisterMachineResponse_Mount_CephVolume) Reset() { *x = RegisterMachineResponse_Mount_CephVolume{} if protoimpl.UnsafeEnabled { - mi := &file_machine_proto_msgTypes[16] + mi := &file_machine_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1146,7 +1367,7 @@ func (x *RegisterMachineResponse_Mount_CephVolume) String() string { func (*RegisterMachineResponse_Mount_CephVolume) ProtoMessage() {} func (x *RegisterMachineResponse_Mount_CephVolume) ProtoReflect() protoreflect.Message { - mi := &file_machine_proto_msgTypes[16] + mi := &file_machine_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1373,7 +1594,28 @@ var file_machine_proto_rawDesc = []byte{ 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x83, 0x03, 0x0a, 0x0e, 0x4d, 0x61, 0x63, 0x68, 0x69, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5c, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x53, 0x42, 0x4f, 0x4d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x73, + 0x70, 0x69, 0x66, 0x66, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x73, 0x70, 0x69, 0x66, 0x66, 0x65, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x05, 0x73, 0x62, 0x6f, 0x6d, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x33, 0x2e, 0x53, 0x42, 0x4f, 0x4d, 0x52, 0x05, 0x73, + 0x62, 0x6f, 0x6d, 0x73, 0x22, 0x93, 0x01, 0x0a, 0x04, 0x53, 0x42, 0x4f, 0x4d, 0x12, 0x1a, 0x0a, + 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x70, 0x64, + 0x78, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x70, + 0x64, 0x78, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x30, + 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x33, 0x2e, 0x49, + 0x6d, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, + 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x44, 0x0a, 0x05, 0x49, 0x6d, + 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x6d, 0x61, 0x6e, 0x69, 0x66, + 0x65, 0x73, 0x74, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74, + 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x42, 0x4f, 0x4d, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xd8, 0x03, 0x0a, 0x0e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x26, 0x2e, 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x33, 0x2e, 0x52, 0x65, @@ -1397,18 +1639,24 @@ var file_machine_proto_rawDesc = []byte{ 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x33, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x42, 0xa8, 0x01, 0x0a, - 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x76, 0x33, 0x42, 0x0c, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x50, 0x01, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x6d, 0x6f, 0x62, 0x79, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x6b, 0x69, 0x74, 0x2f, 0x64, 0x65, - 0x70, 0x6f, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x3b, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x76, 0x33, 0xa2, - 0x02, 0x03, 0x44, 0x43, 0x58, 0xaa, 0x02, 0x0e, 0x44, 0x65, 0x70, 0x6f, 0x74, 0x2e, 0x43, 0x6c, - 0x6f, 0x75, 0x64, 0x2e, 0x56, 0x33, 0xca, 0x02, 0x0e, 0x44, 0x65, 0x70, 0x6f, 0x74, 0x5c, 0x43, - 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, 0x33, 0xe2, 0x02, 0x1a, 0x44, 0x65, 0x70, 0x6f, 0x74, 0x5c, - 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, 0x33, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x10, 0x44, 0x65, 0x70, 0x6f, 0x74, 0x3a, 0x3a, 0x43, 0x6c, - 0x6f, 0x75, 0x64, 0x3a, 0x3a, 0x56, 0x33, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x12, 0x53, 0x0a, 0x0a, + 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x42, 0x4f, 0x4d, 0x12, 0x21, 0x2e, 0x64, 0x65, 0x70, + 0x6f, 0x74, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x33, 0x2e, 0x52, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x53, 0x42, 0x4f, 0x4d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, + 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x33, 0x2e, 0x52, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x42, 0x4f, 0x4d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x42, 0xa8, 0x01, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x76, 0x33, 0x42, 0x0c, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, + 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x6f, 0x62, 0x79, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x6b, + 0x69, 0x74, 0x2f, 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x3b, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x76, 0x33, 0xa2, 0x02, 0x03, 0x44, 0x43, 0x58, 0xaa, 0x02, 0x0e, 0x44, 0x65, 0x70, + 0x6f, 0x74, 0x2e, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x56, 0x33, 0xca, 0x02, 0x0e, 0x44, 0x65, + 0x70, 0x6f, 0x74, 0x5c, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, 0x33, 0xe2, 0x02, 0x1a, 0x44, + 0x65, 0x70, 0x6f, 0x74, 0x5c, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x5c, 0x56, 0x33, 0x5c, 0x47, 0x50, + 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x10, 0x44, 0x65, 0x70, 0x6f, + 0x74, 0x3a, 0x3a, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x3a, 0x3a, 0x56, 0x33, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1424,7 +1672,7 @@ func file_machine_proto_rawDescGZIP() []byte { } var file_machine_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_machine_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_machine_proto_msgTypes = make([]protoimpl.MessageInfo, 22) var file_machine_proto_goTypes = []interface{}{ (RegisterMachineResponse_Mount_FilesystemType)(0), // 0: depot.cloud.v3.RegisterMachineResponse.Mount.FilesystemType (*RegisterMachineRequest)(nil), // 1: depot.cloud.v3.RegisterMachineRequest @@ -1438,42 +1686,50 @@ var file_machine_proto_goTypes = []interface{}{ (*UsageResponse)(nil), // 9: depot.cloud.v3.UsageResponse (*ReportStatusRequest)(nil), // 10: depot.cloud.v3.ReportStatusRequest (*ReportStatusResponse)(nil), // 11: depot.cloud.v3.ReportStatusResponse - (*RegisterMachineRequest_AWSRegistration)(nil), // 12: depot.cloud.v3.RegisterMachineRequest.AWSRegistration - (*RegisterMachineResponse_Mount)(nil), // 13: depot.cloud.v3.RegisterMachineResponse.Mount - (*RegisterMachineResponse_PendingTask)(nil), // 14: depot.cloud.v3.RegisterMachineResponse.PendingTask - (*RegisterMachineResponse_BuildKitTask)(nil), // 15: depot.cloud.v3.RegisterMachineResponse.BuildKitTask - (*RegisterMachineResponse_Profiler)(nil), // 16: depot.cloud.v3.RegisterMachineResponse.Profiler - (*RegisterMachineResponse_Mount_CephVolume)(nil), // 17: depot.cloud.v3.RegisterMachineResponse.Mount.CephVolume - nil, // 18: depot.cloud.v3.ReportStatusRequest.StableDigestsEntry - (*control.StatusResponse)(nil), // 19: moby.buildkit.v1.StatusResponse + (*ReportSBOMRequest)(nil), // 12: depot.cloud.v3.ReportSBOMRequest + (*SBOM)(nil), // 13: depot.cloud.v3.SBOM + (*Image)(nil), // 14: depot.cloud.v3.Image + (*ReportSBOMResponse)(nil), // 15: depot.cloud.v3.ReportSBOMResponse + (*RegisterMachineRequest_AWSRegistration)(nil), // 16: depot.cloud.v3.RegisterMachineRequest.AWSRegistration + (*RegisterMachineResponse_Mount)(nil), // 17: depot.cloud.v3.RegisterMachineResponse.Mount + (*RegisterMachineResponse_PendingTask)(nil), // 18: depot.cloud.v3.RegisterMachineResponse.PendingTask + (*RegisterMachineResponse_BuildKitTask)(nil), // 19: depot.cloud.v3.RegisterMachineResponse.BuildKitTask + (*RegisterMachineResponse_Profiler)(nil), // 20: depot.cloud.v3.RegisterMachineResponse.Profiler + (*RegisterMachineResponse_Mount_CephVolume)(nil), // 21: depot.cloud.v3.RegisterMachineResponse.Mount.CephVolume + nil, // 22: depot.cloud.v3.ReportStatusRequest.StableDigestsEntry + (*control.StatusResponse)(nil), // 23: moby.buildkit.v1.StatusResponse } var file_machine_proto_depIdxs = []int32{ - 12, // 0: depot.cloud.v3.RegisterMachineRequest.aws:type_name -> depot.cloud.v3.RegisterMachineRequest.AWSRegistration - 14, // 1: depot.cloud.v3.RegisterMachineResponse.pending:type_name -> depot.cloud.v3.RegisterMachineResponse.PendingTask - 15, // 2: depot.cloud.v3.RegisterMachineResponse.buildkit:type_name -> depot.cloud.v3.RegisterMachineResponse.BuildKitTask + 16, // 0: depot.cloud.v3.RegisterMachineRequest.aws:type_name -> depot.cloud.v3.RegisterMachineRequest.AWSRegistration + 18, // 1: depot.cloud.v3.RegisterMachineResponse.pending:type_name -> depot.cloud.v3.RegisterMachineResponse.PendingTask + 19, // 2: depot.cloud.v3.RegisterMachineResponse.buildkit:type_name -> depot.cloud.v3.RegisterMachineResponse.BuildKitTask 4, // 3: depot.cloud.v3.PingMachineHealthRequest.disks:type_name -> depot.cloud.v3.DiskSpace 8, // 4: depot.cloud.v3.UsageRequest.cache:type_name -> depot.cloud.v3.Cache - 19, // 5: depot.cloud.v3.ReportStatusRequest.status:type_name -> moby.buildkit.v1.StatusResponse - 18, // 6: depot.cloud.v3.ReportStatusRequest.stable_digests:type_name -> depot.cloud.v3.ReportStatusRequest.StableDigestsEntry - 0, // 7: depot.cloud.v3.RegisterMachineResponse.Mount.fs_type:type_name -> depot.cloud.v3.RegisterMachineResponse.Mount.FilesystemType - 17, // 8: depot.cloud.v3.RegisterMachineResponse.Mount.ceph_volume:type_name -> depot.cloud.v3.RegisterMachineResponse.Mount.CephVolume - 6, // 9: depot.cloud.v3.RegisterMachineResponse.BuildKitTask.cert:type_name -> depot.cloud.v3.Cert - 6, // 10: depot.cloud.v3.RegisterMachineResponse.BuildKitTask.ca_cert:type_name -> depot.cloud.v3.Cert - 13, // 11: depot.cloud.v3.RegisterMachineResponse.BuildKitTask.mounts:type_name -> depot.cloud.v3.RegisterMachineResponse.Mount - 16, // 12: depot.cloud.v3.RegisterMachineResponse.BuildKitTask.profiler:type_name -> depot.cloud.v3.RegisterMachineResponse.Profiler - 1, // 13: depot.cloud.v3.MachineService.RegisterMachine:input_type -> depot.cloud.v3.RegisterMachineRequest - 3, // 14: depot.cloud.v3.MachineService.PingMachineHealth:input_type -> depot.cloud.v3.PingMachineHealthRequest - 7, // 15: depot.cloud.v3.MachineService.Usage:input_type -> depot.cloud.v3.UsageRequest - 10, // 16: depot.cloud.v3.MachineService.ReportStatus:input_type -> depot.cloud.v3.ReportStatusRequest - 2, // 17: depot.cloud.v3.MachineService.RegisterMachine:output_type -> depot.cloud.v3.RegisterMachineResponse - 5, // 18: depot.cloud.v3.MachineService.PingMachineHealth:output_type -> depot.cloud.v3.PingMachineHealthResponse - 9, // 19: depot.cloud.v3.MachineService.Usage:output_type -> depot.cloud.v3.UsageResponse - 11, // 20: depot.cloud.v3.MachineService.ReportStatus:output_type -> depot.cloud.v3.ReportStatusResponse - 17, // [17:21] is the sub-list for method output_type - 13, // [13:17] is the sub-list for method input_type - 13, // [13:13] is the sub-list for extension type_name - 13, // [13:13] is the sub-list for extension extendee - 0, // [0:13] is the sub-list for field type_name + 23, // 5: depot.cloud.v3.ReportStatusRequest.status:type_name -> moby.buildkit.v1.StatusResponse + 22, // 6: depot.cloud.v3.ReportStatusRequest.stable_digests:type_name -> depot.cloud.v3.ReportStatusRequest.StableDigestsEntry + 13, // 7: depot.cloud.v3.ReportSBOMRequest.sboms:type_name -> depot.cloud.v3.SBOM + 14, // 8: depot.cloud.v3.SBOM.image:type_name -> depot.cloud.v3.Image + 0, // 9: depot.cloud.v3.RegisterMachineResponse.Mount.fs_type:type_name -> depot.cloud.v3.RegisterMachineResponse.Mount.FilesystemType + 21, // 10: depot.cloud.v3.RegisterMachineResponse.Mount.ceph_volume:type_name -> depot.cloud.v3.RegisterMachineResponse.Mount.CephVolume + 6, // 11: depot.cloud.v3.RegisterMachineResponse.BuildKitTask.cert:type_name -> depot.cloud.v3.Cert + 6, // 12: depot.cloud.v3.RegisterMachineResponse.BuildKitTask.ca_cert:type_name -> depot.cloud.v3.Cert + 17, // 13: depot.cloud.v3.RegisterMachineResponse.BuildKitTask.mounts:type_name -> depot.cloud.v3.RegisterMachineResponse.Mount + 20, // 14: depot.cloud.v3.RegisterMachineResponse.BuildKitTask.profiler:type_name -> depot.cloud.v3.RegisterMachineResponse.Profiler + 1, // 15: depot.cloud.v3.MachineService.RegisterMachine:input_type -> depot.cloud.v3.RegisterMachineRequest + 3, // 16: depot.cloud.v3.MachineService.PingMachineHealth:input_type -> depot.cloud.v3.PingMachineHealthRequest + 7, // 17: depot.cloud.v3.MachineService.Usage:input_type -> depot.cloud.v3.UsageRequest + 10, // 18: depot.cloud.v3.MachineService.ReportStatus:input_type -> depot.cloud.v3.ReportStatusRequest + 12, // 19: depot.cloud.v3.MachineService.ReportSBOM:input_type -> depot.cloud.v3.ReportSBOMRequest + 2, // 20: depot.cloud.v3.MachineService.RegisterMachine:output_type -> depot.cloud.v3.RegisterMachineResponse + 5, // 21: depot.cloud.v3.MachineService.PingMachineHealth:output_type -> depot.cloud.v3.PingMachineHealthResponse + 9, // 22: depot.cloud.v3.MachineService.Usage:output_type -> depot.cloud.v3.UsageResponse + 11, // 23: depot.cloud.v3.MachineService.ReportStatus:output_type -> depot.cloud.v3.ReportStatusResponse + 15, // 24: depot.cloud.v3.MachineService.ReportSBOM:output_type -> depot.cloud.v3.ReportSBOMResponse + 20, // [20:25] is the sub-list for method output_type + 15, // [15:20] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name } func init() { file_machine_proto_init() } @@ -1615,7 +1871,7 @@ func file_machine_proto_init() { } } file_machine_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RegisterMachineRequest_AWSRegistration); i { + switch v := v.(*ReportSBOMRequest); i { case 0: return &v.state case 1: @@ -1627,7 +1883,7 @@ func file_machine_proto_init() { } } file_machine_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RegisterMachineResponse_Mount); i { + switch v := v.(*SBOM); i { case 0: return &v.state case 1: @@ -1639,7 +1895,7 @@ func file_machine_proto_init() { } } file_machine_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RegisterMachineResponse_PendingTask); i { + switch v := v.(*Image); i { case 0: return &v.state case 1: @@ -1651,7 +1907,7 @@ func file_machine_proto_init() { } } file_machine_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RegisterMachineResponse_BuildKitTask); i { + switch v := v.(*ReportSBOMResponse); i { case 0: return &v.state case 1: @@ -1663,7 +1919,7 @@ func file_machine_proto_init() { } } file_machine_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RegisterMachineResponse_Profiler); i { + switch v := v.(*RegisterMachineRequest_AWSRegistration); i { case 0: return &v.state case 1: @@ -1675,6 +1931,54 @@ func file_machine_proto_init() { } } file_machine_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterMachineResponse_Mount); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_machine_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterMachineResponse_PendingTask); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_machine_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterMachineResponse_BuildKitTask); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_machine_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterMachineResponse_Profiler); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_machine_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RegisterMachineResponse_Mount_CephVolume); i { case 0: return &v.state @@ -1695,14 +1999,15 @@ func file_machine_proto_init() { (*RegisterMachineResponse_Buildkit)(nil), } file_machine_proto_msgTypes[12].OneofWrappers = []interface{}{} - file_machine_proto_msgTypes[14].OneofWrappers = []interface{}{} + file_machine_proto_msgTypes[16].OneofWrappers = []interface{}{} + file_machine_proto_msgTypes[18].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_machine_proto_rawDesc, NumEnums: 1, - NumMessages: 18, + NumMessages: 22, NumExtensions: 0, NumServices: 1, }, diff --git a/depot/api/machine.proto b/depot/api/machine.proto index 7c0bc45161c4..ff1021d5b447 100644 --- a/depot/api/machine.proto +++ b/depot/api/machine.proto @@ -9,6 +9,7 @@ service MachineService { rpc PingMachineHealth(PingMachineHealthRequest) returns (PingMachineHealthResponse); rpc Usage(UsageRequest) returns (UsageResponse); rpc ReportStatus(stream ReportStatusRequest) returns (ReportStatusResponse); + rpc ReportSBOM(ReportSBOMRequest) returns (ReportSBOMResponse); } message RegisterMachineRequest { @@ -132,3 +133,24 @@ message ReportStatusRequest { } message ReportStatusResponse {} + +message ReportSBOMRequest { + string spiffe_id = 1; + repeated SBOM sboms = 2; +} + +message SBOM { + string platform = 1; + string spdx_json = 2; + string digest = 3; + // If the build was exported to an image the image name and digest are included. + optional Image image = 4; +} + +message Image { + // Name is the image name and tag. + string name = 1; + string manifest_digest = 2; +} + +message ReportSBOMResponse {} diff --git a/depot/auth.go b/depot/auth.go new file mode 100644 index 000000000000..e63b1a7a82d1 --- /dev/null +++ b/depot/auth.go @@ -0,0 +1,13 @@ +package depot + +import "os" + +// BearerFromEnv returns the bearer token from the environment. +// This is used to auth to the API. +func BearerFromEnv() string { + token := os.Getenv("DEPOT_BUILDKIT_TOKEN") + if token != "" { + return "Bearer " + token + } + return "" +} diff --git a/depot/sbom.go b/depot/sbom.go new file mode 100644 index 000000000000..04034a7c1977 --- /dev/null +++ b/depot/sbom.go @@ -0,0 +1,48 @@ +package depot + +import ( + "bytes" + "encoding/base64" + "encoding/json" +) + +// SBOMsLabel is the key for the SBOM attestation. +const SBOMsLabel = "depot/sboms" + +type SBOM struct { + // Statement is the spdx.json SBOM scanning output. + Statement []byte `json:"statement"` + // Platform is the specific platform that was scanned. + Platform string `json:"platform"` + // Digest is the content digest of the SBOM. + Digest string `json:"digest"` + // If an image was created this is the image name and digest of the scanned SBOM. + Image *ImageSBOM `json:"image"` +} + +// ImageSBOM describes an image that is described by an SBOM. +type ImageSBOM struct { + // Name is the image name and tag. + Name string `json:"name"` + // ManifestDigest is the digest of the manifest and can be used + // to pull the image such as: + // docker pull goller/xmarks@sha256:6839c1808eab334a9b0f400f119773a0a7d494631c083aef6d3447e3798b544f + ManifestDigest string `json:"manifest_digest"` +} + +func EncodeSBOMs(sboms []SBOM) (string, error) { + octets := new(bytes.Buffer) + b64 := base64.NewEncoder(base64.StdEncoding, octets) + if err := json.NewEncoder(b64).Encode(sboms); err != nil { + return "", err + } + _ = b64.Close() + return octets.String(), nil +} + +func DecodeSBOMs(encodedSBOMs string) ([]SBOM, error) { + b64 := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(encodedSBOMs)) + var sboms []SBOM + err := json.NewDecoder(b64).Decode(&sboms) + return sboms, err +} diff --git a/depot/spiffe.go b/depot/spiffe.go new file mode 100644 index 000000000000..b8732a7ca03e --- /dev/null +++ b/depot/spiffe.go @@ -0,0 +1,23 @@ +package depot + +import ( + "context" + + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/peer" +) + +// SpiffeFromContext returns the SPIFFE ID from context. +// This can be used for sending information to the API. +func SpiffeFromContext(ctx context.Context) string { + var spiffeID string + peer, ok := peer.FromContext(ctx) + if ok { + tlsInfo, ok := peer.AuthInfo.(credentials.TLSInfo) + if ok && tlsInfo.SPIFFEID != nil { + spiffeID = tlsInfo.SPIFFEID.String() + } + } + + return spiffeID +} diff --git a/exporter/containerimage/export.go b/exporter/containerimage/export.go index d4ae45047da3..fd4e4118567a 100644 --- a/exporter/containerimage/export.go +++ b/exporter/containerimage/export.go @@ -399,9 +399,11 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source // We use this because the garbage collector could remove them before download. if opts.ExportImageVersion == ExportImageVersionV2 { exportedImages := make([]depot.ExportedImage, len(committed)) + sboms := make([]depot.SBOM, 0, len(committed)) for i := range committed { exportedImages[i].Manifest = committed[i].ManifestBytes exportedImages[i].Config = committed[i].ConfigBytes + sboms = append(sboms, committed[i].SBOMs...) } octets, err := json.Marshal(exportedImages) @@ -410,6 +412,16 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source } resp[depot.ImagesExported] = base64.StdEncoding.EncodeToString(octets) + + if len(sboms) > 0 { + resultSBOMs, err := depot.EncodeSBOMs(sboms) + if err != nil { + return nil, nil, err + } + if resultSBOMs != "" { + resp[depot.SBOMsLabel] = resultSBOMs + } + } } return resp, nil, nil diff --git a/exporter/containerimage/writer.go b/exporter/containerimage/writer.go index 3a092d0cc6a9..d2dbb73e9aad 100644 --- a/exporter/containerimage/writer.go +++ b/exporter/containerimage/writer.go @@ -17,6 +17,7 @@ import ( intoto "github.com/in-toto/in-toto-golang/in_toto" "github.com/moby/buildkit/cache" cacheconfig "github.com/moby/buildkit/cache/config" + "github.com/moby/buildkit/depot" "github.com/moby/buildkit/exporter" "github.com/moby/buildkit/exporter/attestation" "github.com/moby/buildkit/exporter/containerimage/exptypes" @@ -282,10 +283,13 @@ func (ic *ImageWriter) Commit(ctx context.Context, inp *exporter.Source, session return nil, nil, err } - desc, err := ic.commitAttestationsManifest(ctx, opts, p, committed.Manifest.Digest.String(), stmts) + desc, sboms, err := ic.commitAttestationsManifest(ctx, opts, p, committed.Manifest.Digest.String(), stmts) if err != nil { return nil, nil, err } + if len(sboms) > 0 { + committed.SBOMs = append(committed.SBOMs, sboms...) + } desc.Platform = &intotoPlatform attestationManifests = append(attestationManifests, *desc) } @@ -375,6 +379,8 @@ type Committed struct { // We return bytes here rather than ocispecs.Image as buildkit adds extra information // to support docker schema. ConfigBytes []byte + // SBOMs is the spdx document for the specific image and platform. + SBOMs []depot.SBOM } func (ic *ImageWriter) commitDistributionManifest(ctx context.Context, opts *ImageCommitOpts, ref cache.ImmutableRef, config []byte, remote *solver.Remote, annotations *Annotations, inlineCache []byte, buildInfo []byte, epoch *time.Time, sg session.Group) (*Committed, error) { @@ -512,7 +518,8 @@ func (ic *ImageWriter) commitDistributionManifest(ctx context.Context, opts *Ima return committed, nil } -func (ic *ImageWriter) commitAttestationsManifest(ctx context.Context, opts *ImageCommitOpts, p exptypes.Platform, target string, statements []intoto.Statement) (*ocispecs.Descriptor, error) { +// DEPOT: Returns the manifest descriptor and all SBOMs. +func (ic *ImageWriter) commitAttestationsManifest(ctx context.Context, opts *ImageCommitOpts, p exptypes.Platform, target string, statements []intoto.Statement) (*ocispecs.Descriptor, []depot.SBOM, error) { var ( manifestType = ocispecs.MediaTypeImageManifest configType = ocispecs.MediaTypeImageConfig @@ -523,12 +530,13 @@ func (ic *ImageWriter) commitAttestationsManifest(ctx context.Context, opts *Ima } layers := make([]ocispecs.Descriptor, len(statements)) + var sboms []depot.SBOM for i, statement := range statements { i, statement := i, statement data, err := json.Marshal(statement) if err != nil { - return nil, errors.Wrap(err, "failed to marshal attestation") + return nil, nil, errors.Wrap(err, "failed to marshal attestation") } digest := digest.FromBytes(data) desc := ocispecs.Descriptor{ @@ -541,15 +549,28 @@ func (ic *ImageWriter) commitAttestationsManifest(ctx context.Context, opts *Ima }, } + if statement.PredicateType == intoto.PredicateSPDX { + sbom := depot.SBOM{ + Statement: data, + Platform: platforms.Format(p.Platform), + Digest: digest.String(), + Image: &depot.ImageSBOM{ + Name: opts.ImageName, + ManifestDigest: target, + }, + } + sboms = append(sboms, sbom) + } + if err := content.WriteBlob(ctx, ic.opt.ContentStore, digest.String(), bytes.NewReader(data), desc); err != nil { - return nil, errors.Wrapf(err, "error writing data blob %s", digest) + return nil, nil, errors.Wrapf(err, "error writing data blob %s", digest) } layers[i] = desc } config, err := attestationsConfig(layers) if err != nil { - return nil, err + return nil, nil, err } configDigest := digest.FromBytes(config) configDesc := ocispecs.Descriptor{ @@ -589,7 +610,7 @@ func (ic *ImageWriter) commitAttestationsManifest(ctx context.Context, opts *Ima mfstJSON, err := json.MarshalIndent(mfst, "", " ") if err != nil { - return nil, errors.Wrap(err, "failed to marshal manifest") + return nil, nil, errors.Wrap(err, "failed to marshal manifest") } mfstDigest := digest.FromBytes(mfstJSON) @@ -600,14 +621,14 @@ func (ic *ImageWriter) commitAttestationsManifest(ctx context.Context, opts *Ima done := progress.OneOff(ctx, "exporting attestation manifest "+mfstDigest.String()) if err := content.WriteBlob(ctx, ic.opt.ContentStore, mfstDigest.String(), bytes.NewReader(mfstJSON), mfstDesc, content.WithLabels((labels))); err != nil { - return nil, done(errors.Wrapf(err, "error writing manifest blob %s", mfstDigest)) + return nil, nil, done(errors.Wrapf(err, "error writing manifest blob %s", mfstDigest)) } if err := content.WriteBlob(ctx, ic.opt.ContentStore, configDigest.String(), bytes.NewReader(config), configDesc); err != nil { - return nil, done(errors.Wrap(err, "error writing config blob")) + return nil, nil, done(errors.Wrap(err, "error writing config blob")) } done(nil) - return &ocispecs.Descriptor{ + attestationsManifest := &ocispecs.Descriptor{ Digest: mfstDigest, Size: int64(len(mfstJSON)), MediaType: manifestType, @@ -615,7 +636,8 @@ func (ic *ImageWriter) commitAttestationsManifest(ctx context.Context, opts *Ima attestationTypes.DockerAnnotationReferenceType: attestationTypes.DockerAnnotationReferenceTypeDefault, attestationTypes.DockerAnnotationReferenceDigest: target, }, - }, nil + } + return attestationsManifest, sboms, nil } func (ic *ImageWriter) ContentStore() content.Store { diff --git a/frontend/attestations/sbom/sbom.go b/frontend/attestations/sbom/sbom.go index 113797b2139c..7d5db952e448 100644 --- a/frontend/attestations/sbom/sbom.go +++ b/frontend/attestations/sbom/sbom.go @@ -110,3 +110,19 @@ func HasSBOM[T any](res *result.Result[T]) bool { } return false } + +func SBOMOf[T any](res *result.Result[T]) map[string]*result.Attestation[T] { + var attestations map[string]*result.Attestation[T] + + for platform, as := range res.Attestations { + for i := range as { + if as[i].InToto.PredicateType == intoto.PredicateSPDX { + if attestations == nil { + attestations = make(map[string]*result.Attestation[T]) + } + attestations[platform] = &as[i] + } + } + } + return attestations +} diff --git a/solver/llbsolver/solver.go b/solver/llbsolver/solver.go index d65a9e6490c7..8c3c45b734f5 100644 --- a/solver/llbsolver/solver.go +++ b/solver/llbsolver/solver.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/containerd/continuity/fs" slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" controlapi "github.com/moby/buildkit/api/services/control" "github.com/moby/buildkit/cache" @@ -17,17 +18,21 @@ import ( "github.com/moby/buildkit/cache/remotecache" "github.com/moby/buildkit/client" controlgateway "github.com/moby/buildkit/control/gateway" + "github.com/moby/buildkit/depot" "github.com/moby/buildkit/exporter" "github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/frontend" + "github.com/moby/buildkit/frontend/attestations/sbom" "github.com/moby/buildkit/frontend/gateway" "github.com/moby/buildkit/identity" "github.com/moby/buildkit/session" + "github.com/moby/buildkit/snapshot" "github.com/moby/buildkit/solver" "github.com/moby/buildkit/solver/llbsolver/provenance" "github.com/moby/buildkit/solver/result" spb "github.com/moby/buildkit/sourcepolicy/pb" "github.com/moby/buildkit/util/attestation" + "github.com/moby/buildkit/util/bklog" "github.com/moby/buildkit/util/buildinfo" "github.com/moby/buildkit/util/compression" "github.com/moby/buildkit/util/entitlements" @@ -399,10 +404,11 @@ func (s *Solver) recordBuildHistory(ctx context.Context, id string, req frontend }, nil } -func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req frontend.SolveRequest, exp ExporterRequest, ent []entitlements.Entitlement, post []Processor, internal bool, srcPol *spb.Policy) (_ *client.SolveResponse, err error) { +// DEPOT: we return a second optional value, SBOM. +func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req frontend.SolveRequest, exp ExporterRequest, ent []entitlements.Entitlement, post []Processor, internal bool, srcPol *spb.Policy) (_ *client.SolveResponse, _ []depot.SBOM, err error) { j, err := s.solver.NewJob(id) if err != nil { - return nil, err + return nil, nil, err } defer j.Discard() @@ -427,7 +433,7 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro set, err := entitlements.WhiteList(ent, supportedEntitlements(s.entitlements)) if err != nil { - return nil, err + return nil, nil, err } j.SetValue(keyEntitlements, set) @@ -447,7 +453,7 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro // LeaseManager calls, and there is a fixed 3s timeout in // GatewayForwarder on build registration. if err := s.gatewayForwarder.RegisterBuild(ctx, id, fwd); err != nil { - return nil, err + return nil, nil, err } defer s.gatewayForwarder.UnregisterBuild(ctx, id) } @@ -456,7 +462,7 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro rec, err1 := s.recordBuildHistory(ctx, id, req, exp, j) if err1 != nil { defer j.CloseProgress() - return nil, err1 + return nil, nil, err1 } defer func() { err = rec(resProv, descref, err) @@ -472,12 +478,12 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro err = ctx.Err() } if err != nil { - return nil, err + return nil, nil, err } } else { res, err = br.Solve(ctx, req, sessionID) if err != nil { - return nil, err + return nil, nil, err } } @@ -501,18 +507,18 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro return nil }) if err := eg.Wait(); err != nil { - return nil, err + return nil, nil, err } resProv, err = addProvenanceToResult(res, br) if err != nil { - return nil, err + return nil, nil, err } for _, post := range post { res2, err := post(ctx, resProv, s, j) if err != nil { - return nil, err + return nil, nil, err } resProv = res2 } @@ -522,8 +528,9 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro return res.Result(ctx) }) if err != nil { - return nil, err + return nil, nil, err } + inp, err := result.ConvertResult(cached, func(res solver.CachedResult) (cache.ImmutableRef, error) { workerRef, ok := res.Sys().(*worker.WorkerRef) if !ok { @@ -532,7 +539,7 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro return workerRef.ImmutableRef, nil }) if err != nil { - return nil, err + return nil, nil, err } cacheExporters, inlineCacheExporter := splitCacheExporters(exp.CacheExporters) @@ -541,7 +548,7 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro if e := exp.Exporter; e != nil { meta, err := runInlineCacheExporter(ctx, e, inlineCacheExporter, j, cached) if err != nil { - return nil, err + return nil, nil, err } for k, v := range meta { inp.AddMeta(k, v) @@ -551,13 +558,13 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro exporterResponse, descref, err = e.Export(ctx, inp, j.SessionID) return err }); err != nil { - return nil, err + return nil, nil, err } } cacheExporterResponse, err := runCacheExporters(ctx, cacheExporters, j, cached, inp) if err != nil { - return nil, err + return nil, nil, err } if exporterResponse == nil { @@ -578,9 +585,28 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro } } - return &client.SolveResponse{ - ExporterResponse: exporterResponse, - }, nil + // DEPOT: If the exporter does not produce SBOMs, we try to create them from the cached results. + var resultSBOMs []depot.SBOM + exportedSBOMs, ok := exporterResponse[depot.SBOMsLabel] + if ok { + // The exporters can only return back map[string]string. As a result, we + // encode and then immediately decode here. + resultSBOMs, err = depot.DecodeSBOMs(exportedSBOMs) + if err != nil { + return nil, nil, err + } + } else if sbomAttestations := sbom.SBOMOf(cached); sbomAttestations != nil { + // If the SBOM was created without creating an image, we still grab what information we have. + // These SBOMs will not have any "subjects." + resultSBOMs, err = GetSBOM(ctx, j.SessionID, sbomAttestations) + if err == nil && len(resultSBOMs) > 0 { + exportedSBOMs, err := depot.EncodeSBOMs(resultSBOMs) + if err == nil && exportedSBOMs != "" { + exporterResponse[depot.SBOMsLabel] = exportedSBOMs + } + } + } + return &client.SolveResponse{ExporterResponse: exporterResponse}, resultSBOMs, nil } func runCacheExporters(ctx context.Context, exporters []RemoteCacheExporter, j *solver.Job, cached *result.Result[solver.CachedResult], inp *result.Result[cache.ImmutableRef]) (map[string]string, error) { @@ -1001,3 +1027,60 @@ func loadSourcePolicy(b solver.Builder) (*spb.Policy, error) { } return srcPol, nil } + +// DEPOT: GetSBOM returns the SBOMs of the scanned layers. +func GetSBOM(ctx context.Context, sessionID string, platformAttestations map[string]*result.Attestation[solver.CachedResult]) ([]depot.SBOM, error) { + var sboms []depot.SBOM + for platform, attestation := range platformAttestations { + sbomRef := attestation + if sbomRef == nil || sbomRef.Ref == nil { + continue + } + + sbomResult := sbomRef.Ref + workerRef, ok := sbomResult.Sys().(*worker.WorkerRef) + if !ok { + bklog.G(ctx).Errorf("invalid reference: %T", sbomResult.Sys()) + return nil, errors.Errorf("invalid reference: %T", sbomResult.Sys()) + } + + inp := workerRef.ImmutableRef + + sessionGroup := session.NewGroup(sessionID) + readOnly := true + mount, err := inp.Mount(ctx, readOnly, sessionGroup) + if err != nil { + bklog.G(ctx).Errorf("failed to mount: %v", err) + return nil, err + } + lm := snapshot.LocalMounter(mount) + root, err := lm.Mount() + if err != nil { + bklog.G(ctx).Errorf("failed to mount: %v", err) + return nil, err + } + defer func() { + if lm != nil { + _ = lm.Unmount() + } + }() + fp, err := fs.RootPath(root, "/sbom.spdx.json") + if err != nil { + bklog.G(ctx).Errorf("failed to get root path: %v", err) + return nil, err + } + statement, err := os.ReadFile(fp) + if err != nil { + bklog.G(ctx).Errorf("failed to read file: %v", err) + return nil, err + } + + d := depot.NewFastDigester() + _, _ = d.Hash().Write(statement) + digest := d.Digest().String() + + sboms = append(sboms, depot.SBOM{Platform: platform, Statement: statement, Digest: digest}) + } + + return sboms, nil +}