From 395b041d1e85c9268956eb425de676edd1aa991b Mon Sep 17 00:00:00 2001 From: Hank Donnay Date: Wed, 27 Mar 2024 17:26:25 -0500 Subject: [PATCH] debian: remove sourcemapper This shouldn't be needed anymore, now that source packages are being extracted from dpkg correctly and the debian Updater is tagging the created Vulnerabilities as "source". Signed-off-by: Hank Donnay --- debian/parser.go | 11 -- debian/sourcemapper.go | 168 ---------------------------- debian/sourcemapper_test.go | 72 ------------ debian/testdata/Bullseye-Sources.gz | Bin 14418 -> 0 bytes debian/updater.go | 63 +++-------- 5 files changed, 14 insertions(+), 300 deletions(-) delete mode 100644 debian/sourcemapper.go delete mode 100644 debian/sourcemapper_test.go delete mode 100644 debian/testdata/Bullseye-Sources.gz diff --git a/debian/parser.go b/debian/parser.go index 66f32bfeb..e9f6f0952 100644 --- a/debian/parser.go +++ b/debian/parser.go @@ -70,17 +70,6 @@ func (u *updater) Parse(ctx context.Context, r io.ReadCloser) ([]*claircore.Vuln }, } vs = append(vs, &v) - - for _, bin := range u.sm.Get(d.VersionCodeName, src) { - // Shallow copy. - vuln := v - vuln.Package = &claircore.Package{ - Name: bin, - Kind: claircore.BINARY, - } - - vs = append(vs, &vuln) - } } } } diff --git a/debian/sourcemapper.go b/debian/sourcemapper.go deleted file mode 100644 index 539443192..000000000 --- a/debian/sourcemapper.go +++ /dev/null @@ -1,168 +0,0 @@ -package debian - -import ( - "bufio" - "compress/gzip" - "context" - "errors" - "fmt" - "io" - "net/http" - "net/textproto" - "net/url" - "path" - "strings" - "sync" - - "github.com/quay/zlog" - "golang.org/x/sync/errgroup" -) - -var sourceRepos = [3]string{"main", "contrib", "non-free"} - -// NewSourcesMap returns a SourcesMap but does not perform any -// inserts into the map. That needs to be done explicitly by calling -// the Update method. -func newSourcesMap(client *http.Client, srcs []sourceURL) *sourcesMap { - return &sourcesMap{ - urls: srcs, - sourceMap: make(map[string]map[string]map[string]struct{}), - etagMap: make(map[string]string), - client: client, - } -} - -type sourceURL struct { - distro string - url *url.URL -} - -// sourcesMap wraps a map that defines the relationship between a source -// package and its associated binaries. It offers an Update method -// to populate and update the map. It is Release-centric. -// -// It should have the same lifespan as the Updater to save allocations -// and take advantage of the entity tag that Debian sends back. -type sourcesMap struct { - urls []sourceURL - mu, etagMu sync.RWMutex - // sourceMap maps distribution -> source package -> binary packages - sourceMap map[string]map[string]map[string]struct{} - etagMap map[string]string - client *http.Client -} - -// Get returns all the binaries associated with a source package -// identified by a string. Empty slice is returned if the source -// doesn't exist in the map. -func (m *sourcesMap) Get(distro, source string) []string { - m.mu.RLock() - defer m.mu.RUnlock() - bins := []string{} - for bin := range m.sourceMap[distro][source] { - bins = append(bins, bin) - } - return bins -} - -// Update pulls the Sources.gz files for the different repos and saves -// the resulting source to binary relationships. -func (m *sourcesMap) Update(ctx context.Context) error { - g, ctx := errgroup.WithContext(ctx) - for _, source := range m.urls { - // Required as source is overwritten upon each iteration, - // which may cause a race condition when used below in g.Go. - src := source - for _, r := range sourceRepos { - u, err := source.url.Parse(path.Join(r, `source`, `Sources.gz`)) - g.Go(func() error { - if err != nil { - return fmt.Errorf("unable to construct URL: %w", err) - } - if err := m.fetchSources(ctx, src.distro, u.String()); err != nil { - return fmt.Errorf("unable to fetch sources: %w", err) - } - return nil - }) - } - } - return g.Wait() -} - -func (m *sourcesMap) fetchSources(ctx context.Context, distro, url string) error { - ctx = zlog.ContextWithValues(ctx, - "component", "debian/sourcemapper.fetchSources", - "url", url) - zlog.Debug(ctx).Msg("attempting fetch of Sources file") - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return err - } - m.etagMu.RLock() - etag := m.etagMap[url] - m.etagMu.RUnlock() - req.Header.Set("If-None-Match", etag) - - resp, err := m.client.Do(req) - if err != nil { - return err - } - - defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusOK: - if etag == "" || etag != resp.Header.Get("etag") { - break - } - fallthrough - case http.StatusNotModified: - zlog.Debug(ctx).Msg("already processed the latest version of the file") - return nil - default: - return fmt.Errorf("received status code %d querying mapping url %s", resp.StatusCode, url) - } - m.etagMu.Lock() - m.etagMap[url] = resp.Header.Get("etag") - m.etagMu.Unlock() - - var reader io.ReadCloser - switch resp.Header.Get("Content-Type") { - case "application/gzip", "application/x-gzip": - reader, err = gzip.NewReader(resp.Body) - if err != nil { - return err - } - defer reader.Close() - default: - return fmt.Errorf("received bad content-type %s querying mapping url %s", resp.Header.Get("Content-Type"), url) - } - - tp := textproto.NewReader(bufio.NewReader(reader)) - hdr, err := tp.ReadMIMEHeader() - for ; err == nil && len(hdr) > 0; hdr, err = tp.ReadMIMEHeader() { - source := hdr.Get("Package") - if source == "linux" { - continue - } - binaries := hdr.Get("Binary") - m.mu.Lock() - if m.sourceMap[distro] == nil { - m.sourceMap[distro] = make(map[string]map[string]struct{}) - } - if m.sourceMap[distro][source] == nil { - m.sourceMap[distro][source] = make(map[string]struct{}) - } - for _, bin := range strings.Split(binaries, ", ") { - m.sourceMap[distro][source][bin] = struct{}{} - } - m.mu.Unlock() - } - switch { - case errors.Is(err, io.EOF): - default: - return fmt.Errorf("could not read Sources file %s: %w", url, err) - } - - return nil -} diff --git a/debian/sourcemapper_test.go b/debian/sourcemapper_test.go deleted file mode 100644 index 20efea79f..000000000 --- a/debian/sourcemapper_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package debian - -import ( - "bytes" - "context" - "io" - "net/http" - "net/url" - "os" - "testing" - - "github.com/quay/zlog" -) - -type TestClientFunc func(req *http.Request) *http.Response - -func (f TestClientFunc) RoundTrip(req *http.Request) (*http.Response, error) { - return f(req), nil -} - -func newTestClient() (*http.Client, error) { - f, err := os.Open("testdata/Bullseye-Sources.gz") - if err != nil { - return nil, err - } - b, err := io.ReadAll(f) - if err != nil { - return nil, err - } - f.Close() - - return &http.Client{ - Transport: TestClientFunc( - func(req *http.Request) *http.Response { - w := &http.Response{ - StatusCode: http.StatusOK, - Header: make(http.Header), - Body: io.NopCloser(bytes.NewReader(b)), - } - w.Header.Set("Content-Type", "application/gzip") - return w - }, - ), - }, nil -} - -func TestCreateSourcesMap(t *testing.T) { - ctx := zlog.Test(context.Background(), t) - client, err := newTestClient() - if err != nil { - t.Fatalf("got the error %v", err) - } - u, err := url.Parse("http://[::1]/") - if err != nil { - t.Fatal(err) - } - mapper := newSourcesMap(client, []sourceURL{{distro: "bullseye", url: u}}) - - err = mapper.Update(ctx) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - opensshBinaries := mapper.Get("bullseye", "aalib") - if len(opensshBinaries) != 3 { - t.Fatalf("expected 3 binaries related to aalib found %d - %v", len(opensshBinaries), opensshBinaries) - } - - tarBinaries := mapper.Get("bullseye", "389-ds-base") - if len(tarBinaries) != 6 { - t.Fatalf("expected 6 binaries related to 389-ds-base found %d - %v", len(tarBinaries), tarBinaries) - } -} diff --git a/debian/testdata/Bullseye-Sources.gz b/debian/testdata/Bullseye-Sources.gz deleted file mode 100644 index c054c7ce255a44197bf1b642eebbb330531d5d9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14418 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