mirror of
https://github.com/containerd/containerd.git
synced 2026-07-06 22:59:01 +00:00
Merge pull request #11352 from ChengyuZhu6/fsverity
erofs-snapshotter: add fsverity support
This commit is contained in:
@@ -82,6 +82,13 @@ The following configuration can be used in your containerd `config.toml`. Don't
|
||||
forget to restart containerd after changing the configuration.
|
||||
|
||||
```
|
||||
[plugins."io.containerd.snapshotter.v1.erofs"]
|
||||
# Enable fsverity support for EROFS layers, default is false
|
||||
enable_fsverity = true
|
||||
|
||||
# Optional: Additional mount options for overlayfs
|
||||
ovl_mount_options = []
|
||||
|
||||
[plugins."io.containerd.service.v1.diff-service"]
|
||||
default = ["erofs","walking"]
|
||||
```
|
||||
@@ -135,6 +142,13 @@ In other words, the EROFS differ can only be used with the EROFS snapshotter;
|
||||
otherwise, it will skip to the next differ. The EROFS snapshotter can work
|
||||
with or without the EROFS differ.
|
||||
|
||||
## Data Integrity with fsverity
|
||||
|
||||
The EROFS snapshotter supports fsverity for data integrity verification of EROFS layers.
|
||||
When enabled via `enable_fsverity = true`, the snapshotter will:
|
||||
- Enable fsverity on EROFS layers during commit
|
||||
- Verify fsverity status before mounting layers
|
||||
- Skip fsverity if the filesystem or kernel does not support it
|
||||
|
||||
## TODO
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/containerd/containerd/v2/core/mount"
|
||||
"github.com/containerd/containerd/v2/core/snapshots"
|
||||
"github.com/containerd/containerd/v2/core/snapshots/storage"
|
||||
"github.com/containerd/containerd/v2/internal/fsverity"
|
||||
"github.com/containerd/containerd/v2/plugins/snapshots/erofs/erofsutils"
|
||||
"github.com/containerd/continuity/fs"
|
||||
"github.com/containerd/log"
|
||||
@@ -39,6 +40,8 @@ import (
|
||||
type SnapshotterConfig struct {
|
||||
// ovlOptions are the base options added to the overlayfs mount (defaults to [""])
|
||||
ovlOptions []string
|
||||
// enableFsverity enables fsverity for EROFS layers
|
||||
enableFsverity bool
|
||||
}
|
||||
|
||||
// Opt is an option to configure the erofs snapshotter
|
||||
@@ -51,6 +54,13 @@ func WithOvlOptions(options []string) Opt {
|
||||
}
|
||||
}
|
||||
|
||||
// WithFsverity enables fsverity for EROFS layers
|
||||
func WithFsverity() Opt {
|
||||
return func(config *SnapshotterConfig) {
|
||||
config.enableFsverity = true
|
||||
}
|
||||
}
|
||||
|
||||
type MetaStore interface {
|
||||
TransactionContext(ctx context.Context, writable bool) (context.Context, storage.Transactor, error)
|
||||
WithTransaction(ctx context.Context, writable bool, fn storage.TransactionCallback) error
|
||||
@@ -58,9 +68,10 @@ type MetaStore interface {
|
||||
}
|
||||
|
||||
type snapshotter struct {
|
||||
root string
|
||||
ms *storage.MetaStore
|
||||
ovlOptions []string
|
||||
root string
|
||||
ms *storage.MetaStore
|
||||
ovlOptions []string
|
||||
enableFsverity bool
|
||||
}
|
||||
|
||||
// check if EROFS kernel filesystem is registered or not
|
||||
@@ -108,6 +119,17 @@ func NewSnapshotter(root string, opts ...Opt) (snapshots.Snapshotter, error) {
|
||||
return nil, fmt.Errorf("EROFS unsupported, please `modprobe erofs`: %w", plugin.ErrSkipPlugin)
|
||||
}
|
||||
|
||||
// Check fsverity support if enabled
|
||||
if config.enableFsverity {
|
||||
supported, err := fsverity.IsSupported(root)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check fsverity support on %q: %w", root, err)
|
||||
}
|
||||
if !supported {
|
||||
return nil, fmt.Errorf("fsverity is not supported on the filesystem of %q", root)
|
||||
}
|
||||
}
|
||||
|
||||
ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -118,9 +140,10 @@ func NewSnapshotter(root string, opts ...Opt) (snapshots.Snapshotter, error) {
|
||||
}
|
||||
|
||||
return &snapshotter{
|
||||
root: root,
|
||||
ms: ms,
|
||||
ovlOptions: config.ovlOptions,
|
||||
root: root,
|
||||
ms: ms,
|
||||
ovlOptions: config.ovlOptions,
|
||||
enableFsverity: config.enableFsverity,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -182,12 +205,16 @@ func (s *snapshotter) mounts(snap storage.Snapshot, info snapshots.Info) ([]moun
|
||||
var options []string
|
||||
|
||||
if len(snap.ParentIDs) == 0 {
|
||||
// If the EROFS layer blob is valid, only snapshots.KindView is allowed.
|
||||
m, _, err := s.lowerPath(snap.ID)
|
||||
if err == nil {
|
||||
if snap.Kind != snapshots.KindView {
|
||||
return nil, fmt.Errorf("only works for snapshots.KindView on a committed snapshot: %w", err)
|
||||
}
|
||||
if s.enableFsverity {
|
||||
if err := s.verifyFsverity(m.Source); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// We have to force a loop device here since mount[] is static.
|
||||
m.Options = append(m.Options, "loop")
|
||||
return []mount.Mount{m}, nil
|
||||
@@ -369,6 +396,14 @@ func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable fsverity on the EROFS layer if configured
|
||||
if s.enableFsverity {
|
||||
if err := fsverity.Enable(layerBlob); err != nil {
|
||||
return fmt.Errorf("failed to enable fsverity: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -536,3 +571,18 @@ func (s *snapshotter) Usage(ctx context.Context, key string) (_ snapshots.Usage,
|
||||
}
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
// Add a method to verify fsverity
|
||||
func (s *snapshotter) verifyFsverity(path string) error {
|
||||
if !s.enableFsverity {
|
||||
return nil
|
||||
}
|
||||
enabled, err := fsverity.IsEnabled(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check fsverity status: %w", err)
|
||||
}
|
||||
if !enabled {
|
||||
return fmt.Errorf("fsverity is not enabled on %s", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,11 +18,16 @@ package erofs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/containerd/v2/core/mount"
|
||||
"github.com/containerd/containerd/v2/core/snapshots"
|
||||
"github.com/containerd/containerd/v2/core/snapshots/storage"
|
||||
"github.com/containerd/containerd/v2/core/snapshots/testsuite"
|
||||
"github.com/containerd/containerd/v2/internal/fsverity"
|
||||
"github.com/containerd/containerd/v2/pkg/testutil"
|
||||
)
|
||||
|
||||
@@ -51,3 +56,78 @@ func TestErofs(t *testing.T) {
|
||||
testutil.RequiresRoot(t)
|
||||
testsuite.SnapshotterSuite(t, "erofs", newSnapshotter(t))
|
||||
}
|
||||
|
||||
func TestErofsFsverity(t *testing.T) {
|
||||
testutil.RequiresRoot(t)
|
||||
ctx := context.Background()
|
||||
|
||||
root := t.TempDir()
|
||||
|
||||
// Skip if fsverity is not supported
|
||||
supported, err := fsverity.IsSupported(root)
|
||||
if !supported || err != nil {
|
||||
t.Skip("fsverity not supported, skipping test")
|
||||
}
|
||||
|
||||
// Create snapshotter with fsverity enabled
|
||||
s, err := NewSnapshotter(root, WithFsverity())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
// Create a test snapshot
|
||||
key := "test-snapshot"
|
||||
mounts, err := s.Prepare(ctx, key, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
target := filepath.Join(root, key)
|
||||
if err := os.MkdirAll(target, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := mount.All(mounts, target); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer testutil.Unmount(t, target)
|
||||
|
||||
// Write test data
|
||||
if err := os.WriteFile(filepath.Join(target, "foo"), []byte("test data"), 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Commit the snapshot
|
||||
commitKey := "test-commit"
|
||||
if err := s.Commit(ctx, commitKey, key); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
snap := s.(*snapshotter)
|
||||
|
||||
// Get the internal ID from the snapshotter
|
||||
var id string
|
||||
if err := snap.ms.WithTransaction(ctx, false, func(ctx context.Context) error {
|
||||
id, _, _, err = storage.GetInfo(ctx, commitKey)
|
||||
return err
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify fsverity is enabled on the EROFS layer
|
||||
|
||||
layerPath := snap.layerBlobPath(id)
|
||||
|
||||
enabled, err := fsverity.IsEnabled(layerPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to check fsverity status: %v", err)
|
||||
}
|
||||
if !enabled {
|
||||
t.Fatal("Expected fsverity to be enabled on committed layer")
|
||||
}
|
||||
|
||||
// Try to modify the layer file directly (should fail)
|
||||
if err := os.WriteFile(layerPath, []byte("tampered data"), 0666); err == nil {
|
||||
t.Fatal("Expected direct write to fsverity-enabled layer to fail")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ type Config struct {
|
||||
|
||||
// MountOptions are options used for the EROFS overlayfs mount
|
||||
OvlOptions []string `toml:"ovl_mount_options"`
|
||||
|
||||
// EnableFsverity enables fsverity for EROFS layers
|
||||
EnableFsverity bool `toml:"enable_fsverity"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -58,6 +61,10 @@ func init() {
|
||||
opts = append(opts, erofs.WithOvlOptions(config.OvlOptions))
|
||||
}
|
||||
|
||||
if config.EnableFsverity {
|
||||
opts = append(opts, erofs.WithFsverity())
|
||||
}
|
||||
|
||||
ic.Meta.Exports[plugins.SnapshotterRootDir] = root
|
||||
return erofs.NewSnapshotter(root, opts...)
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user