-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add full reader verification #1457
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,7 @@ import ( | |
"github.com/awslabs/soci-snapshotter/fs/layer" | ||
commonmetrics "github.com/awslabs/soci-snapshotter/fs/metrics/common" | ||
layermetrics "github.com/awslabs/soci-snapshotter/fs/metrics/layer" | ||
"github.com/awslabs/soci-snapshotter/fs/reader" | ||
"github.com/awslabs/soci-snapshotter/fs/remote" | ||
"github.com/awslabs/soci-snapshotter/fs/source" | ||
"github.com/awslabs/soci-snapshotter/idtools" | ||
|
@@ -229,7 +230,7 @@ func NewFilesystem(ctx context.Context, root string, cfg config.FSConfig, opts . | |
var bgFetcher *bf.BackgroundFetcher | ||
|
||
if !cfg.BackgroundFetchConfig.Disable { | ||
log.G(context.Background()).WithFields(logrus.Fields{ | ||
log.G(ctx).WithFields(logrus.Fields{ | ||
"fetchPeriod": bgFetchPeriod, | ||
"silencePeriod": bgSilencePeriod, | ||
"maxQueueSize": bgMaxQueueSize, | ||
|
@@ -246,10 +247,19 @@ func NewFilesystem(ctx context.Context, root string, cfg config.FSConfig, opts . | |
} | ||
go bgFetcher.Run(context.Background()) | ||
} else { | ||
log.G(context.Background()).Info("background fetch is disabled") | ||
log.G(ctx).Info("background fetch is disabled") | ||
} | ||
|
||
var readerVerifier *reader.Verifier | ||
if !cfg.DisableVerification { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So currently this is using the TOML config variable for disabling tar header verification. I think there's a world where one might want tar header integrity per file read (a somewhat hefty but not super resource-intensive process, particularly for lighter workloads) but not want to verify the entire image (a pretty resource-intensive process). TL;DR I think this should be its own config variable. |
||
log.G(ctx).Info("creating reader verifier") | ||
readerVerifier = reader.NewVerifier(cfg.BackgroundFetchConfig.MaxQueueSize) | ||
go readerVerifier.Run() | ||
} else { | ||
log.G(ctx).Info("reader verification is disabled") | ||
} | ||
|
||
r, err := layer.NewResolver(root, cfg, fsOpts.resolveHandlers, metadataStore, store, fsOpts.overlayOpaqueType, bgFetcher) | ||
r, err := layer.NewResolver(root, cfg, fsOpts.resolveHandlers, metadataStore, store, fsOpts.overlayOpaqueType, bgFetcher, readerVerifier) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to setup resolver: %w", err) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/* | ||
Copyright The Soci Snapshotter Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package reader | ||
|
||
import ( | ||
"errors" | ||
"sync" | ||
|
||
"github.com/containerd/log" | ||
) | ||
|
||
var ErrNotReader = errors.New("reader is not a *reader.reader") | ||
|
||
// Verifier is a rate-limited reader.reader verifier. | ||
// It will verify all reader.reader's added via `Add` | ||
// when their spanmanager indicates that the image is fully | ||
// downloaded. Verifier will verify exactly 1 reader.reader | ||
// at a time. | ||
// Similar to the BackgroundFetcher, this is a concurrency limiter | ||
// to make sure SOCI's background processes don't compete with | ||
// the containerized workload. | ||
type Verifier struct { | ||
queue chan *reader | ||
|
||
closedMu sync.Mutex | ||
closed bool | ||
closedC chan struct{} | ||
} | ||
|
||
// NewVerifier creates a verifier with the specified max queue size. | ||
func NewVerifier(maxQueueSize int) *Verifier { | ||
return &Verifier{ | ||
queue: make(chan *reader, maxQueueSize), | ||
closedMu: sync.Mutex{}, | ||
closed: false, | ||
closedC: make(chan struct{}), | ||
} | ||
} | ||
|
||
// Add adds a reader.reader to the verifier's queue | ||
// once the reader.reader's span manager finishes downloading | ||
// the image. | ||
// | ||
// `r` must be a `*reader.reader` obtained via `reader.NewReader` | ||
func (v *Verifier) Add(r Reader) error { | ||
switch r := r.(type) { | ||
case *reader: | ||
go func() { | ||
select { | ||
case <-r.spanManager.DownloadedC: | ||
v.queue <- r | ||
case <-v.closedC: | ||
case <-r.closedC: | ||
} | ||
}() | ||
return nil | ||
default: | ||
return ErrNotReader | ||
} | ||
} | ||
|
||
// Run runs the verifier to verify readers on the queue. | ||
// The process will run until the verifier is closed and | ||
// will only verify one reader at a time. | ||
func (v *Verifier) Run() { | ||
for { | ||
select { | ||
case r := <-v.queue: | ||
if !r.isClosed() { | ||
l := log.L.WithField("layer", r.layerSha) | ||
err := r.Verify() | ||
if err == nil { | ||
l.Debug("verified reader") | ||
} else { | ||
l.WithError(err).Error("failed to verify reader") | ||
} | ||
|
||
} | ||
case <-v.closedC: | ||
} | ||
} | ||
} | ||
|
||
// Close closes the verifier and prevents new readers from queuing. | ||
func (v *Verifier) Close() { | ||
v.closedMu.Lock() | ||
if !v.closed { | ||
v.closed = true | ||
close(v.closedC) | ||
} | ||
v.closedMu.Unlock() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -617,10 +617,6 @@ func (r *reader) OpenFile(id uint32) (File, error) { | |
return fmt.Errorf("failed to get file bucket %d: %w", id, err) | ||
} | ||
size, _ = binary.Varint(b.Get(bucketKeySize)) | ||
m, _ := binary.Uvarint(b.Get(bucketKeyMode)) | ||
if !os.FileMode(uint32(m)).IsRegular() { | ||
return fmt.Errorf("%q is not a regular file", id) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did this chunk of code get removed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This prevented calling In a practical sense, the big issue is that |
||
metadataEntries, err := getMetadataBucket(tx, r.fsID) | ||
if err != nil { | ||
return fmt.Errorf("metadata bucket of %q not found for opening %d: %w", r.fsID, id, err) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't looked too closely and unrelated, but any chance we might want to pass
ctx
into here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we probably should.