generated from kubernetes/kubernetes-template-project
-
Notifications
You must be signed in to change notification settings - Fork 45
/
object.go
278 lines (237 loc) · 7.66 KB
/
object.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
/*
Copyright 2021 The Kubernetes 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.
*/
// SHA1 is the currently accepted hash algorithm for SPDX documents, used for
// file integrity checks, NOT security.
// Instances of G401 and G505 can be safely ignored in this file.
//
// ref: https://github.com/spdx/spdx-spec/issues/11
package spdx
import (
"errors"
"fmt"
"path/filepath"
"strings"
intoto "github.com/in-toto/in-toto-golang/in_toto"
purl "github.com/package-url/packageurl-go"
"github.com/sirupsen/logrus"
"sigs.k8s.io/release-utils/hash"
"sigs.k8s.io/release-utils/util"
)
// Object is an interface that dictates the common methods of spdx
// objects. Currently this includes files and packages.
type Object interface {
SPDXID() string
SetSPDXID(string)
ReadSourceFile(string) error
Render() (string, error)
BuildID(seeds ...string)
SetEntity(*Entity)
AddRelationship(*Relationship)
GetRelationships() *[]*Relationship
ToProvenanceSubject() *intoto.Subject
getProvenanceSubjects(opts *ProvenanceOptions, seen *map[string]struct{}) []intoto.Subject
GetElementByID(string) Object
}
type Entity struct {
ID string // Identifier string for the object in the doc
SourceFile string // Local file to read for information
Name string // Name of the package
DownloadLocation string // Download point for the entity
CopyrightText string // NOASSERTION
FileName string // Name of the file
LicenseConcluded string // LicenseID o NOASSERTION
LicenseComments string // record any relevant background information or analysis that went in to arriving at the Concluded License
Opts *ObjectOptions // Entity options
Relationships []*Relationship // List of objects that have a relationship woth this package
Checksum map[string]string // Colection of source file checksums
}
type ObjectOptions struct {
Prefix string
WorkDir string
}
func (e *Entity) Options() *ObjectOptions {
return e.Opts
}
// SPDXID returns the SPDX reference string for the object
func (e *Entity) SPDXID() string {
return e.ID
}
// SPDXID returns the SPDX reference string for the object
func (e *Entity) SetSPDXID(id string) {
e.ID = id
}
// BuildID sets the file ID, optionally from a series of strings
func (e *Entity) BuildID(seeds ...string) {
if len(seeds) <= 1 {
seeds = append(seeds, e.Name)
}
e.ID = buildIDString(seeds...)
}
// AddRelated this adds a related object to the file to be rendered
// on the document. The exact output depends on the related obj options
func (e *Entity) AddRelationship(rel *Relationship) {
e.Relationships = append(e.Relationships, rel)
}
// ReadChecksums receives a path to a file and calculates its checksums
func (e *Entity) ReadChecksums(filePath string) error {
if e.Checksum == nil {
e.Checksum = map[string]string{}
}
// Hash the file contents
for algo, fn := range map[string]func(string) (string, error){
"SHA1": hash.SHA1ForFile,
"SHA256": hash.SHA256ForFile,
"SHA512": hash.SHA512ForFile,
} {
csum, err := fn(filePath)
if err != nil {
return fmt.Errorf("hashing %s file %s: %w", algo, filePath, err)
}
e.Checksum[algo] = csum
}
return nil
}
// ReadSourceFile reads the source file for the package and populates
//
// the fields derived from it (Checksums and FileName)
func (e *Entity) ReadSourceFile(path string) error {
if !util.Exists(path) {
return errors.New("unable to find package source file")
}
if err := e.ReadChecksums(path); err != nil {
return fmt.Errorf("reading file checksums: %w", err)
}
e.SourceFile = path
// If the entity name is blank, we set it to the file path
e.FileName = strings.TrimPrefix(
path, e.Options().WorkDir+string(filepath.Separator),
)
if e.Name == "" {
e.Name = e.FileName
}
return nil
}
// Render is overridden by Package and File with their own variants
func (e *Entity) Render() (string, error) {
return "", nil
}
func (e *Entity) GetRelationships() *[]*Relationship {
return &e.Relationships
}
// ToProvenanceSubject converts the element to an intoto subject, suitable
// to use inprovenance attestaions
func (e *Entity) ToProvenanceSubject() *intoto.Subject {
location := ""
if e.DownloadLocation != "" {
location = e.DownloadLocation
} else if e.FileName != "" {
location = e.FileName
}
if location == "" {
logrus.Warnf("%+v", e)
logrus.Warnf(
"Unable to convert element %s to provenance subject, no location found",
e.SPDXID(),
)
return nil
}
if len(e.Checksum) == 0 {
logrus.Warnf(
"Unable to convert element %s to provenance subject, no checksums found",
e.SPDXID(),
)
return nil
}
sub := &intoto.Subject{
Name: location,
Digest: map[string]string{},
}
for algo, hashVal := range e.Checksum {
sub.Digest[strings.ToLower(algo)] = hashVal
}
return sub
}
// getProvenanceSubjects regturns all provenance subjects found in this
// entity by scanning all relationships recursively
//
//nolint:gocritic // seen needs to be a pointer as it is used recursively
func (e *Entity) getProvenanceSubjects(opts *ProvenanceOptions, seen *map[string]struct{}) []intoto.Subject {
ret := []intoto.Subject{}
if _, ok := (*seen)[e.SPDXID()]; !ok {
esub := e.ToProvenanceSubject()
if esub != nil {
ret = append(ret, *esub)
}
}
mloop:
for _, rel := range *e.GetRelationships() {
if rel.Peer == nil {
continue mloop
}
// If peer is external, skip
if rel.PeerExtReference != "" {
continue
}
// If the peer has already been added, skip
if _, ok := (*seen)[rel.Peer.SPDXID()]; ok {
continue
}
// If relationships filters are set
if opts.Relationships != nil {
// Version is useful for dependencies, so add it:
found := false
for exclusion, rels := range opts.Relationships {
for _, relt := range rels {
// If rel is excluded, we can ignore
if exclusion == "exclude" && relt == rel.Type {
logrus.Debugf("Relationships of type %s are excluded from provenance", rel.Type)
continue mloop
}
if exclusion == "include" && relt == rel.Type {
found = true
break
}
}
}
// Now if rel was not found, we don't use it but only if we have a
// list of relationships we DO want:
if _, ok := opts.Relationships["include"]; ok {
if !found && len(opts.Relationships["include"]) > 0 {
logrus.Infof("Relationships of type %s not included in provenance", rel.Type)
continue
}
}
}
// Convert entity to subject
var subject *intoto.Subject
if p, ok := rel.Peer.(*Package); ok {
subject = p.ToProvenanceSubject()
}
if f, ok := rel.Peer.(*File); ok {
subject = f.ToProvenanceSubject()
}
if subject != nil {
ret = append(ret, *subject)
(*seen)[rel.Peer.SPDXID()] = struct{}{}
}
}
return ret
}
// GetElementByID nil function to be overridden by package and file
func (e *Entity) GetElementByID(string) Object { return nil }
// GetPackagesByPurl queries the package and returns all the nodes it is
// connected to that match the specified purl bits
func (p *Package) GetPackagesByPurl(purlSpec *purl.PackageURL, opts ...PurlSearchOption) []*Package {
seen := map[string]struct{}{}
return recursivePurlSearch(purlSpec, p, &seen, opts...)
}