Download missing sigs

This commit is contained in:
Daniel 2022-09-28 14:39:49 +02:00
parent 44dc8df5d6
commit cded2438f6
2 changed files with 128 additions and 15 deletions

View file

@ -42,17 +42,17 @@ func (reg *ResourceRegistry) fetchFile(ctx context.Context, client *http.Client,
var (
verifiedHash *lhash.LabeledHash
sigFileData []byte
verifOpts = reg.GetVerificationOptions(rv.resource.Identifier)
)
if verifOpts != nil {
if rv.resource.VerificationOptions != nil {
verifiedHash, sigFileData, err = reg.fetchAndVerifySigFile(
ctx, client,
verifOpts, rv.versionedPath()+filesig.Extension, rv.SigningMetadata(),
rv.resource.VerificationOptions,
rv.versionedSigPath(), rv.SigningMetadata(),
tries,
)
if err != nil {
switch verifOpts.DownloadPolicy {
switch rv.resource.VerificationOptions.DownloadPolicy {
case SignaturePolicyRequire:
return fmt.Errorf("signature verification failed: %w", err)
case SignaturePolicyWarn:
@ -82,7 +82,7 @@ func (reg *ResourceRegistry) fetchFile(ctx context.Context, client *http.Client,
// Write to the hasher at the same time, if needed.
var hasher hash.Hash
var writeDst io.Writer = atomicFile
if verifiedHash != nil && verifOpts.DownloadPolicy != SignaturePolicyDisable {
if verifiedHash != nil {
hasher = verifiedHash.Algorithm().RawHasher()
writeDst = io.MultiWriter(hasher, atomicFile)
}
@ -102,7 +102,7 @@ func (reg *ResourceRegistry) fetchFile(ctx context.Context, client *http.Client,
if verifiedHash.EqualRaw(downloadDigest) {
log.Infof("%s: verified signature of %s", reg.Name, downloadURL)
} else {
switch verifOpts.DownloadPolicy {
switch rv.resource.VerificationOptions.DownloadPolicy {
case SignaturePolicyRequire:
return errors.New("file does not match signed checksum")
case SignaturePolicyWarn:
@ -110,15 +110,18 @@ func (reg *ResourceRegistry) fetchFile(ctx context.Context, client *http.Client,
case SignaturePolicyDisable:
log.Debugf("%s: checksum does not match file from %s", reg.Name, downloadURL)
}
// Reset hasher to signal that the sig should not be written.
hasher = nil
}
}
// Write signature file, if we have one.
if len(sigFileData) > 0 {
// Write signature file, if we have one and if verification succeeded.
if len(sigFileData) > 0 && hasher != nil {
sigFilePath := rv.storagePath() + filesig.Extension
err := ioutil.WriteFile(sigFilePath, sigFileData, 0o0644) //nolint:gosec
if err != nil {
switch verifOpts.DownloadPolicy {
switch rv.resource.VerificationOptions.DownloadPolicy {
case SignaturePolicyRequire:
return fmt.Errorf("failed to write signature file %s: %w", sigFilePath, err)
case SignaturePolicyWarn:
@ -147,6 +150,84 @@ func (reg *ResourceRegistry) fetchFile(ctx context.Context, client *http.Client,
return nil
}
func (reg *ResourceRegistry) fetchMissingSig(ctx context.Context, client *http.Client, rv *ResourceVersion, tries int) error {
// backoff when retrying
if tries > 0 {
select {
case <-ctx.Done():
return nil // module is shutting down
case <-time.After(time.Duration(tries*tries) * time.Second):
}
}
// Check destination dir.
dirPath := filepath.Dir(rv.storagePath())
err := reg.storageDir.EnsureAbsPath(dirPath)
if err != nil {
return fmt.Errorf("could not create updates folder: %s", dirPath)
}
// Download and verify the missing signature.
verifiedHash, sigFileData, err := reg.fetchAndVerifySigFile(
ctx, client,
rv.resource.VerificationOptions,
rv.versionedSigPath(), rv.SigningMetadata(),
tries,
)
if err != nil {
switch rv.resource.VerificationOptions.DownloadPolicy {
case SignaturePolicyRequire:
return fmt.Errorf("signature verification failed: %w", err)
case SignaturePolicyWarn:
log.Warningf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
case SignaturePolicyDisable:
log.Debugf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
}
return nil
}
// Check if the signature matches the resource file.
ok, err := verifiedHash.MatchesFile(rv.storagePath())
if err != nil {
switch rv.resource.VerificationOptions.DownloadPolicy {
case SignaturePolicyRequire:
return fmt.Errorf("error while verifying resource file: %w", err)
case SignaturePolicyWarn:
log.Warningf("%s: error while verifying resource file %s", reg.Name, rv.storagePath())
case SignaturePolicyDisable:
log.Debugf("%s: error while verifying resource file %s", reg.Name, rv.storagePath())
}
return nil
}
if !ok {
switch rv.resource.VerificationOptions.DownloadPolicy {
case SignaturePolicyRequire:
return errors.New("resource file does not match signed checksum")
case SignaturePolicyWarn:
log.Warningf("%s: checksum does not match resource file from %s", reg.Name, rv.storagePath())
case SignaturePolicyDisable:
log.Debugf("%s: checksum does not match resource file from %s", reg.Name, rv.storagePath())
}
return nil
}
// Write signature file.
err = ioutil.WriteFile(rv.storageSigPath(), sigFileData, 0o0644) //nolint:gosec
if err != nil {
switch rv.resource.VerificationOptions.DownloadPolicy {
case SignaturePolicyRequire:
return fmt.Errorf("failed to write signature file %s: %w", rv.storageSigPath(), err)
case SignaturePolicyWarn:
log.Warningf("%s: failed to write signature file %s: %s", reg.Name, rv.storageSigPath(), err)
case SignaturePolicyDisable:
log.Debugf("%s: failed to write signature file %s: %s", reg.Name, rv.storageSigPath(), err)
}
}
log.Infof("%s: fetched %s (stored to %s)", reg.Name, rv.versionedSigPath(), rv.storageSigPath())
return nil
}
func (reg *ResourceRegistry) fetchAndVerifySigFile(ctx context.Context, client *http.Client, verifOpts *VerificationOptions, sigFilePath string, requiredMetadata map[string]string, tries int) (*lhash.LabeledHash, []byte, error) {
// Download signature file.
resp, _, err := reg.makeRequest(ctx, client, sigFilePath, tries)
@ -242,7 +323,7 @@ func (reg *ResourceRegistry) makeRequest(ctx context.Context, client *http.Clien
downloadURL = u.String()
// create request
req, err := http.NewRequestWithContext(ctx, "GET", downloadURL, http.NoBody)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, http.NoBody)
if err != nil {
return nil, "", fmt.Errorf("failed to create request for %q: %w", downloadURL, err)
}

View file

@ -85,7 +85,7 @@ func (reg *ResourceRegistry) downloadIndex(ctx context.Context, client *http.Cli
}
// Check if the index matches the verified hash.
if verifiedHash.MatchesData(indexData) {
if verifiedHash.Matches(indexData) {
log.Infof("%s: verified signature of %s", reg.Name, downloadURL)
} else {
sigErr = ErrIndexChecksumMismatch
@ -170,6 +170,7 @@ func (reg *ResourceRegistry) downloadIndex(ctx context.Context, client *http.Cli
func (reg *ResourceRegistry) DownloadUpdates(ctx context.Context) error {
// create list of downloads
var toUpdate []*ResourceVersion
var missingSigs []*ResourceVersion
reg.RLock()
for _, res := range reg.resources {
res.Lock()
@ -181,8 +182,15 @@ func (reg *ResourceRegistry) DownloadUpdates(ctx context.Context) error {
// add all non-available and eligible versions to update queue
for _, rv := range res.Versions {
if !rv.Available && rv.CurrentRelease {
switch {
case !rv.CurrentRelease:
// We are not interested in older releases.
case !rv.Available:
// File is not available.
toUpdate = append(toUpdate, rv)
case !rv.SigAvailable && res.VerificationOptions != nil:
// File signature is not available and verification is enabled.
missingSigs = append(missingSigs, rv)
}
}
}
@ -192,7 +200,7 @@ func (reg *ResourceRegistry) DownloadUpdates(ctx context.Context) error {
reg.RUnlock()
// nothing to update
if len(toUpdate) == 0 {
if len(toUpdate) == 0 && len(missingSigs) == 0 {
log.Infof("%s: everything up to date", reg.Name)
return nil
}
@ -203,10 +211,10 @@ func (reg *ResourceRegistry) DownloadUpdates(ctx context.Context) error {
}
// download updates
log.Infof("%s: starting to download %d updates parallel", reg.Name, len(toUpdate))
log.Infof("%s: starting to download %d updates in parallel", reg.Name, len(toUpdate)+len(missingSigs))
var wg sync.WaitGroup
wg.Add(len(toUpdate))
wg.Add(len(toUpdate) + len(missingSigs))
client := &http.Client{}
for idx := range toUpdate {
@ -233,6 +241,30 @@ func (reg *ResourceRegistry) DownloadUpdates(ctx context.Context) error {
}(toUpdate[idx])
}
for idx := range missingSigs {
go func(rv *ResourceVersion) {
var err error
defer wg.Done()
defer func() {
if x := recover(); x != nil {
log.Errorf("%s: %s: captured panic: %s", reg.Name, rv.resource.Identifier, x)
}
}()
for tries := 0; tries < 3; tries++ {
err = reg.fetchMissingSig(ctx, client, rv, tries)
if err == nil {
rv.SigAvailable = true
return
}
}
if err != nil {
log.Warningf("%s: failed to download missing sig of %s version %s: %s", reg.Name, rv.resource.Identifier, rv.VersionNumber, err)
}
}(missingSigs[idx])
}
wg.Wait()
log.Infof("%s: finished downloading updates", reg.Name)