-
Notifications
You must be signed in to change notification settings - Fork 80
/
distroless_scanner.go
144 lines (130 loc) · 4.05 KB
/
distroless_scanner.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
package dpkg
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"io/fs"
"net/textproto"
"path/filepath"
"runtime/trace"
"github.com/quay/zlog"
"github.com/quay/claircore"
"github.com/quay/claircore/indexer"
)
const (
distrolessName = "dpkg-distroless"
distrolessKind = "package"
distrolessVersion = "1"
)
var (
_ indexer.VersionedScanner = (*Scanner)(nil)
_ indexer.PackageScanner = (*Scanner)(nil)
)
// DistrolessScanner implements the scanner.PackageScanner interface.
//
// This looks for directories that look like dpkg databases and examines the
// files it finds there.
//
// The zero value is ready to use.
type DistrolessScanner struct{}
// Name implements scanner.VersionedScanner.
func (ps *DistrolessScanner) Name() string { return distrolessName }
// Version implements scanner.VersionedScanner.
func (ps *DistrolessScanner) Version() string { return distrolessVersion }
// Kind implements scanner.VersionedScanner.
func (ps *DistrolessScanner) Kind() string { return distrolessKind }
// Scan attempts to find a dpkg database files in the layer and read all
// of the installed packages it can find. These files are found in the
// dpkg/status.d directory.
//
// It's expected to return (nil, nil) if there's no dpkg databases in the layer.
//
// It does not respect any dpkg configuration files.
func (ps *DistrolessScanner) Scan(ctx context.Context, layer *claircore.Layer) ([]*claircore.Package, error) {
defer trace.StartRegion(ctx, "Scanner.Scan").End()
trace.Log(ctx, "layer", layer.Hash.String())
ctx = zlog.ContextWithValues(ctx,
"component", "dpkg/DistrolessScanner.Scan",
"version", ps.Version(),
"layer", layer.Hash.String())
zlog.Debug(ctx).Msg("start")
defer zlog.Debug(ctx).Msg("done")
sys, err := layer.FS()
if err != nil {
return nil, fmt.Errorf("dpkg-distroless: opening layer failed: %w", err)
}
var pkgs []*claircore.Package
walk := func(p string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.Name() == "status.d" && d.IsDir() {
zlog.Debug(ctx).Str("path", p).Msg("found potential distroless dpkg db directory")
dbFiles, err := fs.ReadDir(sys, p)
if err != nil {
return fmt.Errorf("error reading DB directory: %w", err)
}
for _, f := range dbFiles {
pkgCt := 0
fn := filepath.Join(p, f.Name())
ctx = zlog.ContextWithValues(ctx, "database-file", fn)
zlog.Debug(ctx).Msg("examining package database")
db, err := sys.Open(fn)
if err != nil {
return fmt.Errorf("reading database files from layer failed: %w", err)
}
// The database is actually an RFC822-like message with "\n\n"
// separators, so don't be alarmed by the usage of the "net/textproto"
// package here.
tp := textproto.NewReader(bufio.NewReader(db))
Restart:
hdr, err := tp.ReadMIMEHeader()
for ; (err == nil || errors.Is(err, io.EOF)) && len(hdr) > 0; hdr, err = tp.ReadMIMEHeader() {
// NB The "Status" header is not considered here. It seems
// to not be populated in the "distroless" scheme.
name := hdr.Get("Package")
v := hdr.Get("Version")
p := &claircore.Package{
Name: name,
Version: v,
Kind: claircore.BINARY,
Arch: hdr.Get("Architecture"),
PackageDB: fn,
}
if src := hdr.Get("Source"); src != "" {
p.Source = &claircore.Package{
Name: src,
Kind: claircore.SOURCE,
// Right now, this is an assumption that discovered source
// packages relate to their binary versions. We see this in
// Debian.
Version: v,
PackageDB: fn,
}
}
pkgCt++
pkgs = append(pkgs, p)
}
switch {
case errors.Is(err, io.EOF):
default:
if _, ok := err.(textproto.ProtocolError); ok {
zlog.Warn(ctx).Err(err).Msg("unable to read DB entry")
goto Restart
}
zlog.Warn(ctx).Err(err).Msg("error reading DB file")
}
zlog.Debug(ctx).
Int("count", pkgCt).
Msg("found packages")
}
}
return nil
}
if err := fs.WalkDir(sys, ".", walk); err != nil {
return nil, err
}
return pkgs, nil
}