github.com/quay/claircore@v1.5.28/aws/updater.go (about)

     1  package aws
     2  
     3  import (
     4  	"context"
     5  	"encoding/xml"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/quay/zlog"
    13  
    14  	"github.com/quay/claircore"
    15  	"github.com/quay/claircore/aws/internal/alas"
    16  	"github.com/quay/claircore/internal/xmlutil"
    17  	"github.com/quay/claircore/libvuln/driver"
    18  )
    19  
    20  var _ driver.Updater = (*Updater)(nil)
    21  
    22  // Updater implements the claircore.Updater.Fetcher and claircore.Updater.Parser
    23  // interfaces making it eligible to be used as a claircore.Updater
    24  type Updater struct {
    25  	release Release
    26  	c       *http.Client
    27  }
    28  
    29  func NewUpdater(release Release) (*Updater, error) {
    30  	return &Updater{
    31  		release: release,
    32  	}, nil
    33  }
    34  
    35  func (u *Updater) Name() string {
    36  	return fmt.Sprintf("aws-%v-updater", u.release)
    37  }
    38  
    39  func (u *Updater) Configure(ctx context.Context, _ driver.ConfigUnmarshaler, c *http.Client) error {
    40  	// TODO This should be able to configure things, actually.
    41  	u.c = c
    42  	return nil
    43  }
    44  
    45  func (u *Updater) Fetch(ctx context.Context, fingerprint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) {
    46  	ctx = zlog.ContextWithValues(ctx, "component", "aws/Updater.Fetch")
    47  	client, err := NewClient(ctx, u.c, u.release)
    48  	if err != nil {
    49  		return nil, "", fmt.Errorf("failed to create client: %v", err)
    50  	}
    51  
    52  	tctx, cancel := context.WithTimeout(ctx, defaultOpTimeout)
    53  	defer cancel()
    54  	repoMD, err := client.RepoMD(tctx)
    55  	if err != nil {
    56  		return nil, "", fmt.Errorf("failed to retrieve repo metadata: %v", err)
    57  	}
    58  
    59  	updatesRepoMD, err := repoMD.Repo(alas.UpdateInfo, "")
    60  	if err != nil {
    61  		return nil, "", fmt.Errorf("updates repo metadata could not be retrieved: %v", err)
    62  	}
    63  	if updatesRepoMD.Checksum.Sum == string(fingerprint) {
    64  		return nil, fingerprint, driver.Unchanged
    65  	}
    66  
    67  	tctx, cancel = context.WithTimeout(ctx, defaultOpTimeout)
    68  	defer cancel()
    69  	rc, err := client.Updates(tctx)
    70  	if err != nil {
    71  		return nil, "", fmt.Errorf("failed to retrieve update info: %v", err)
    72  	}
    73  
    74  	return rc, driver.Fingerprint(updatesRepoMD.Checksum.Sum), nil
    75  }
    76  
    77  func (u *Updater) Parse(ctx context.Context, contents io.ReadCloser) ([]*claircore.Vulnerability, error) {
    78  	var updates alas.Updates
    79  	dec := xml.NewDecoder(contents)
    80  	dec.CharsetReader = xmlutil.CharsetReader
    81  	if err := dec.Decode(&updates); err != nil {
    82  		return nil, fmt.Errorf("failed to unmarshal updates xml: %v", err)
    83  	}
    84  	dist := releaseToDist(u.release)
    85  
    86  	vulns := []*claircore.Vulnerability{}
    87  	for _, update := range updates.Updates {
    88  		partial := &claircore.Vulnerability{
    89  			Updater:            u.Name(),
    90  			Name:               update.ID,
    91  			Description:        update.Description,
    92  			Issued:             time.Time(update.Issued.Date),
    93  			Links:              refsToLinks(update),
    94  			Severity:           update.Severity,
    95  			NormalizedSeverity: NormalizeSeverity(update.Severity),
    96  			Dist:               dist,
    97  			ArchOperation:      claircore.OpEquals,
    98  		}
    99  		vulns = append(vulns, u.unpack(partial, update.Packages)...)
   100  	}
   101  
   102  	return vulns, nil
   103  }
   104  
   105  // unpack takes the partially populated vulnerability and creates a fully populated vulnerability for each
   106  // provided alas.Package
   107  func (u *Updater) unpack(partial *claircore.Vulnerability, packages []alas.Package) []*claircore.Vulnerability {
   108  	out := []*claircore.Vulnerability{}
   109  
   110  	var b strings.Builder
   111  	for _, alasPKG := range packages {
   112  		// make copy
   113  		v := *partial
   114  
   115  		v.Package = &claircore.Package{
   116  			Name: alasPKG.Name,
   117  			Kind: claircore.BINARY,
   118  			Arch: alasPKG.Arch,
   119  		}
   120  		v.FixedInVersion = versionString(&b, alasPKG)
   121  
   122  		out = append(out, &v)
   123  	}
   124  
   125  	b.Reset()
   126  	return out
   127  }
   128  
   129  func versionString(b *strings.Builder, p alas.Package) string {
   130  	b.Reset()
   131  
   132  	if p.Epoch != "" && p.Epoch != "0" {
   133  		b.WriteString(p.Epoch)
   134  		b.WriteByte(':')
   135  	}
   136  	b.WriteString(p.Version)
   137  	b.WriteByte('-')
   138  	b.WriteString(p.Release)
   139  
   140  	return b.String()
   141  }
   142  
   143  // refsToLinks takes an alas.Update and creates a string with all the href links
   144  func refsToLinks(u alas.Update) string {
   145  	out := []string{}
   146  	for _, ref := range u.References {
   147  		out = append(out, ref.Href)
   148  	}
   149  
   150  	return strings.Join(out, " ")
   151  }