github.com/quay/claircore@v1.5.28/libvuln/updates/manager_test.go (about)

     1  package updates
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"strconv"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  	"github.com/google/go-cmp/cmp/cmpopts"
    14  	"github.com/jackc/pgx/v4/pgxpool"
    15  	"github.com/quay/zlog"
    16  
    17  	"github.com/quay/claircore"
    18  
    19  	"github.com/quay/claircore/datastore/postgres"
    20  	"github.com/quay/claircore/libvuln/driver"
    21  	"github.com/quay/claircore/pkg/ctxlock"
    22  	"github.com/quay/claircore/test/integration"
    23  	pgtest "github.com/quay/claircore/test/postgres"
    24  )
    25  
    26  func TestDeltaUpdates(t *testing.T) {
    27  	integration.NeedDB(t)
    28  	ctx := zlog.Test(context.Background(), t)
    29  	facs := map[string]driver.UpdaterSetFactory{
    30  		"delta": &Factory{
    31  			vulnGetter: &vulnGetter{
    32  				testVulns: testVulns,
    33  			},
    34  		},
    35  	}
    36  
    37  	pool := pgtest.TestMatcherDB(ctx, t)
    38  	store := postgres.NewMatcherStore(pool)
    39  
    40  	locks, err := ctxlock.New(ctx, pool)
    41  	if err != nil {
    42  		t.Fatalf("%v", err)
    43  	}
    44  	defer locks.Close(ctx)
    45  
    46  	// Using default client here as a non-nil client is an error,
    47  	// it's never used.
    48  	mgr, err := NewManager(ctx, store, locks, http.DefaultClient, WithFactories(facs))
    49  	if err != nil {
    50  		t.Fatalf("%v", err)
    51  	}
    52  
    53  	for _, tc := range testVulns {
    54  		t.Run(tc.testName, func(t *testing.T) {
    55  			// force update
    56  			if err := mgr.Run(ctx); err != nil {
    57  				t.Fatalf("%v", err)
    58  			}
    59  
    60  			vulns, err := getLatestVulnerabilities(ctx, pool, "test/delta/updater")
    61  			if err != nil {
    62  				t.Fatalf("%v", err)
    63  			}
    64  			if len(vulns) != tc.expectedNumber {
    65  				t.Fatalf("expecting %d vuln but got %d", tc.expectedNumber, len(vulns))
    66  			}
    67  		})
    68  	}
    69  
    70  	vulns, err := getLatestVulnerabilities(ctx, pool, "test/delta/updater")
    71  	if err != nil {
    72  		t.Fatalf("%v", err)
    73  	}
    74  
    75  	if !cmp.Equal(vulns, finalVulns,
    76  		cmpopts.IgnoreFields(claircore.Vulnerability{}, "ID"), // Depends on the DB
    77  		cmpopts.SortSlices(func(a, b interface{}) bool {
    78  			return a.(*claircore.Vulnerability).Name < b.(*claircore.Vulnerability).Name
    79  		})) {
    80  		t.Error(cmp.Diff(vulns, finalVulns))
    81  	}
    82  }
    83  
    84  var _ driver.DeltaUpdater = (*testUpdater)(nil)
    85  var _ driver.Updater = (*testUpdater)(nil)
    86  
    87  type testUpdater struct {
    88  	vulnGetter *vulnGetter
    89  }
    90  
    91  func (tu *testUpdater) Name() string {
    92  	return "test/delta/updater"
    93  }
    94  
    95  // DeltaFetch signals to the manager that we want to use DeltaFetch and store.DeltaUpdateVulnerabilities.
    96  func (tu *testUpdater) Fetch(context.Context, driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) {
    97  	// NOOP
    98  	return nil, "", nil
    99  }
   100  
   101  func (tu *testUpdater) Parse(ctx context.Context, vulnUpdates io.ReadCloser) ([]*claircore.Vulnerability, error) {
   102  	// NOOP
   103  	return nil, nil
   104  }
   105  
   106  func (tu *testUpdater) DeltaParse(ctx context.Context, vulnUpdates io.ReadCloser) ([]*claircore.Vulnerability, []string, error) {
   107  	newVulns := tu.vulnGetter.Get()
   108  	return newVulns.vulns, []string{}, nil
   109  }
   110  
   111  type Factory struct {
   112  	vulnGetter *vulnGetter
   113  }
   114  
   115  func (f *Factory) Configure(ctx context.Context, cf driver.ConfigUnmarshaler, c *http.Client) error {
   116  	return nil
   117  }
   118  
   119  func (f *Factory) UpdaterSet(ctx context.Context) (s driver.UpdaterSet, err error) {
   120  	s = driver.NewUpdaterSet()
   121  	s.Add(&testUpdater{
   122  		vulnGetter: f.vulnGetter,
   123  	})
   124  	return s, nil
   125  }
   126  
   127  type vulnGetter struct {
   128  	testVulns []*fetchedVulns
   129  	idx       int
   130  }
   131  
   132  func (vg *vulnGetter) Get() *fetchedVulns {
   133  	if vg.idx+1 > len(vg.testVulns) {
   134  		return nil
   135  	}
   136  	defer func() { vg.idx++ }()
   137  	return vg.testVulns[vg.idx]
   138  }
   139  
   140  type fetchedVulns struct {
   141  	vulns          []*claircore.Vulnerability
   142  	expectedNumber int
   143  	testName       string
   144  }
   145  
   146  var testVulns = []*fetchedVulns{
   147  	{
   148  		testName: "initial vuln",
   149  		vulns: []*claircore.Vulnerability{
   150  			{
   151  				Updater:            "test/delta/updater",
   152  				Name:               "CVE-2023:123",
   153  				Description:        "bad things",
   154  				Issued:             time.Time{},
   155  				Links:              "https://ohno.com/CVE-2023:123 https://moreprobs.io/CVE-2023:123",
   156  				Severity:           "Very Medium",
   157  				NormalizedSeverity: claircore.Medium,
   158  				Package: &claircore.Package{
   159  					Name: "blah",
   160  				},
   161  			},
   162  		},
   163  		expectedNumber: 1,
   164  	},
   165  	{
   166  		testName: "same vuln desc updated",
   167  		vulns: []*claircore.Vulnerability{
   168  			{
   169  				Updater:            "test/delta/updater",
   170  				Name:               "CVE-2023:123",
   171  				Description:        "worse things",
   172  				Issued:             time.Time{},
   173  				Links:              "https://ohno.com/CVE-2023:123 https://moreprobs.io/CVE-2023:123",
   174  				Severity:           "Very Medium",
   175  				NormalizedSeverity: claircore.Medium,
   176  				Package: &claircore.Package{
   177  					Name: "blah",
   178  				},
   179  			},
   180  		},
   181  		expectedNumber: 1,
   182  	},
   183  	{
   184  		testName: "two new vulns",
   185  		vulns: []*claircore.Vulnerability{
   186  			{
   187  				Updater:            "test/delta/updater",
   188  				Name:               "CVE-2023:456",
   189  				Description:        "problems",
   190  				Issued:             time.Time{},
   191  				Links:              "https://ohno.com/CVE-2023:456 https://moreprobs.io/CVE-2023:456",
   192  				Severity:           "Very Medium",
   193  				NormalizedSeverity: claircore.Medium,
   194  				Package: &claircore.Package{
   195  					Name: "blah",
   196  				},
   197  			},
   198  			{
   199  				Updater:            "test/delta/updater",
   200  				Name:               "CVE-2023:789",
   201  				Description:        "problems again",
   202  				Issued:             time.Time{},
   203  				Links:              "https://ohno.com/CVE-2023:789 https://moreprobs.io/CVE-2023:789",
   204  				Severity:           "Very Medium",
   205  				NormalizedSeverity: claircore.Medium,
   206  				Package: &claircore.Package{
   207  					Name: "blah",
   208  				},
   209  			},
   210  		},
   211  		expectedNumber: 3,
   212  	},
   213  	{
   214  		testName: "two updated one new",
   215  		vulns: []*claircore.Vulnerability{
   216  			{
   217  				Updater:            "test/delta/updater",
   218  				Name:               "CVE-2023:456",
   219  				Description:        "problems 2",
   220  				Issued:             time.Time{},
   221  				Links:              "https://ohno.com/CVE-2023:456 https://moreprobs.io/CVE-2023:456",
   222  				Severity:           "Very Medium",
   223  				NormalizedSeverity: claircore.Medium,
   224  				Package: &claircore.Package{
   225  					Name: "blah",
   226  				},
   227  			},
   228  			{
   229  				Updater:            "test/delta/updater",
   230  				Name:               "CVE-2023:789",
   231  				Description:        "problems again",
   232  				Issued:             time.Time{},
   233  				Links:              "https://ohno.com/CVE-2023:789 https://moreprobs.io/CVE-2023:789",
   234  				Severity:           "Very Medium",
   235  				NormalizedSeverity: claircore.Medium,
   236  				Package: &claircore.Package{
   237  					Name: "blah",
   238  				},
   239  			},
   240  			{
   241  				Updater:            "test/delta/updater",
   242  				Name:               "CVE-2023:101112",
   243  				Description:        "problems again",
   244  				Issued:             time.Time{},
   245  				Links:              "https://ohno.com/CVE-2023:101112 https://moreprobs.io/CVE-2023:101112",
   246  				Severity:           "Very Medium",
   247  				NormalizedSeverity: claircore.Medium,
   248  				Package: &claircore.Package{
   249  					Name: "blah",
   250  				},
   251  			},
   252  		},
   253  		expectedNumber: 4,
   254  	},
   255  }
   256  
   257  var finalVulns = []*claircore.Vulnerability{
   258  	{
   259  		Updater:            "test/delta/updater",
   260  		Name:               "CVE-2023:123",
   261  		Description:        "worse things",
   262  		Issued:             time.Time{},
   263  		Links:              "https://ohno.com/CVE-2023:123 https://moreprobs.io/CVE-2023:123",
   264  		Severity:           "Very Medium",
   265  		NormalizedSeverity: claircore.Medium,
   266  		Package: &claircore.Package{
   267  			Name: "blah",
   268  		},
   269  		Dist: &claircore.Distribution{},
   270  		Repo: &claircore.Repository{},
   271  	},
   272  	{
   273  		Updater:            "test/delta/updater",
   274  		Name:               "CVE-2023:456",
   275  		Description:        "problems 2",
   276  		Issued:             time.Time{},
   277  		Links:              "https://ohno.com/CVE-2023:456 https://moreprobs.io/CVE-2023:456",
   278  		Severity:           "Very Medium",
   279  		NormalizedSeverity: claircore.Medium,
   280  		Package: &claircore.Package{
   281  			Name: "blah",
   282  		},
   283  		Dist: &claircore.Distribution{},
   284  		Repo: &claircore.Repository{},
   285  	},
   286  	{
   287  		Updater:            "test/delta/updater",
   288  		Name:               "CVE-2023:789",
   289  		Description:        "problems again",
   290  		Issued:             time.Time{},
   291  		Links:              "https://ohno.com/CVE-2023:789 https://moreprobs.io/CVE-2023:789",
   292  		Severity:           "Very Medium",
   293  		NormalizedSeverity: claircore.Medium,
   294  		Package: &claircore.Package{
   295  			Name: "blah",
   296  		},
   297  		Dist: &claircore.Distribution{},
   298  		Repo: &claircore.Repository{},
   299  	},
   300  	{
   301  		Updater:            "test/delta/updater",
   302  		Name:               "CVE-2023:101112",
   303  		Description:        "problems again",
   304  		Issued:             time.Time{},
   305  		Links:              "https://ohno.com/CVE-2023:101112 https://moreprobs.io/CVE-2023:101112",
   306  		Severity:           "Very Medium",
   307  		NormalizedSeverity: claircore.Medium,
   308  		Package: &claircore.Package{
   309  			Name: "blah",
   310  		},
   311  		Dist: &claircore.Distribution{},
   312  		Repo: &claircore.Repository{},
   313  	},
   314  }
   315  
   316  func getLatestVulnerabilities(ctx context.Context, pool *pgxpool.Pool, updater string) ([]*claircore.Vulnerability, error) {
   317  	query := `
   318  		SELECT 
   319  			"vuln"."id",
   320  			"name",
   321  			"description",
   322  			"issued",
   323  			"links",
   324  			"severity",
   325  			"normalized_severity",
   326  			"package_name",
   327  			"package_version",
   328  			"package_module",
   329  			"package_arch",
   330  			"package_kind",
   331  			"dist_id",
   332  			"dist_name",
   333  			"dist_version",
   334  			"dist_version_code_name",
   335  			"dist_version_id",
   336  			"dist_arch",
   337  			"dist_cpe",
   338  			"dist_pretty_name",
   339  			"arch_operation",
   340  			"repo_name",
   341  			"repo_key",
   342  			"repo_uri",
   343  			"fixed_in_version",
   344  			"vuln"."updater"
   345  		FROM
   346  			"vuln"
   347  			INNER JOIN "uo_vuln" ON ("vuln"."id" = "uo_vuln"."vuln")
   348  			INNER JOIN "latest_update_operations" ON (
   349  			"latest_update_operations"."id" = "uo_vuln"."uo"
   350  			) 
   351  		WHERE 
   352  			(
   353  			"latest_update_operations"."kind" = 'vulnerability'
   354  			)
   355  		AND
   356  			(
   357  			"vuln"."updater" = $1
   358  			)
   359  	`
   360  	results := []*claircore.Vulnerability{}
   361  	rows, err := pool.Query(ctx, query, updater)
   362  	if err != nil {
   363  		return nil, err
   364  	}
   365  	defer rows.Close()
   366  
   367  	// unpack all returned rows into claircore.Vulnerability structs
   368  	for rows.Next() {
   369  		// fully allocate vuln struct
   370  		v := &claircore.Vulnerability{
   371  			Package: &claircore.Package{},
   372  			Dist:    &claircore.Distribution{},
   373  			Repo:    &claircore.Repository{},
   374  		}
   375  
   376  		var id int64
   377  		err := rows.Scan(
   378  			&id,
   379  			&v.Name,
   380  			&v.Description,
   381  			&v.Issued,
   382  			&v.Links,
   383  			&v.Severity,
   384  			&v.NormalizedSeverity,
   385  			&v.Package.Name,
   386  			&v.Package.Version,
   387  			&v.Package.Module,
   388  			&v.Package.Arch,
   389  			&v.Package.Kind,
   390  			&v.Dist.DID,
   391  			&v.Dist.Name,
   392  			&v.Dist.Version,
   393  			&v.Dist.VersionCodeName,
   394  			&v.Dist.VersionID,
   395  			&v.Dist.Arch,
   396  			&v.Dist.CPE,
   397  			&v.Dist.PrettyName,
   398  			&v.ArchOperation,
   399  			&v.Repo.Name,
   400  			&v.Repo.Key,
   401  			&v.Repo.URI,
   402  			&v.FixedInVersion,
   403  			&v.Updater,
   404  		)
   405  		v.ID = strconv.FormatInt(id, 10)
   406  		if err != nil {
   407  			return nil, fmt.Errorf("failed to scan vulnerability: %v", err)
   408  		}
   409  		results = append(results, v)
   410  	}
   411  
   412  	return results, nil
   413  }