From 6831973bd05b3dac801cb69d87e7e36272cfa299 Mon Sep 17 00:00:00 2001 From: Fredrik Appelros Date: Thu, 16 Jun 2022 13:32:09 +0200 Subject: [PATCH] plugins/bundle: use unique temporary files In order to use the feature to persist activated bundles to disk in a cloud environment with shared storage, e.g. Kubernetes with the Amazon EFS storage driver, each instance of OPA needs to either synchronize their access to the temporary file using advisory file locks, or use unique temporary files. If not, then the following situation may occur: p1: open and trunc tmp file p1: write to tmp file p2: open and trunc tmp file p1: rename tmp file to dst p2: write to tmp file p2: rename tmp file to dst This may then lead to the persisted bundle being truncated or corrupted. Here the approach of using unique temporary files is chosen because it avoids the overhead of introducing file locks, and the additional dependency since Go lacks any such mechanisms in the standard library. This solution should avoid truncated or corrupt bundles as `rename()` is guaranteed to be atomic, even in file systems like NFS. Fixes: #4782 Signed-off-by: Fredrik Appelros --- plugins/bundle/plugin.go | 15 +++++++-------- plugins/bundle/plugin_test.go | 6 +++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/plugins/bundle/plugin.go b/plugins/bundle/plugin.go index 6e279cdeac..367e1c1e7a 100644 --- a/plugins/bundle/plugin.go +++ b/plugins/bundle/plugin.go @@ -652,10 +652,9 @@ func (p *Plugin) configDelta(newConfig *Config) (map[string]*Source, map[string] func (p *Plugin) saveBundleToDisk(name string, raw io.Reader) error { bundleDir := filepath.Join(p.bundlePersistPath, name) - tmpFile := filepath.Join(bundleDir, ".bundle.tar.gz.tmp") bundleFile := filepath.Join(bundleDir, "bundle.tar.gz") - saveErr := saveCurrentBundleToDisk(bundleDir, ".bundle.tar.gz.tmp", raw) + tmpFile, saveErr := saveCurrentBundleToDisk(bundleDir, raw) if saveErr != nil { p.log(name).Error("Failed to save new bundle to disk: %v", saveErr) @@ -673,26 +672,26 @@ func (p *Plugin) saveBundleToDisk(name string, raw io.Reader) error { return os.Rename(tmpFile, bundleFile) } -func saveCurrentBundleToDisk(path, filename string, raw io.Reader) error { +func saveCurrentBundleToDisk(path string, raw io.Reader) (string, error) { if _, err := os.Stat(path); os.IsNotExist(err) { err = os.MkdirAll(path, os.ModePerm) if err != nil { - return err + return "", err } } if raw == nil { - return fmt.Errorf("no raw bundle bytes to persist to disk") + return "", fmt.Errorf("no raw bundle bytes to persist to disk") } - dest, err := os.OpenFile(filepath.Join(path, filename), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + dest, err := os.CreateTemp(path, ".bundle.tar.gz.*.tmp") if err != nil { - return err + return "", err } defer dest.Close() _, err = io.Copy(dest, raw) - return err + return dest.Name(), err } func loadBundleFromDisk(path, name string, src *Source) (*bundle.Bundle, error) { diff --git a/plugins/bundle/plugin_test.go b/plugins/bundle/plugin_test.go index ae36ab5df6..845d42b85b 100644 --- a/plugins/bundle/plugin_test.go +++ b/plugins/bundle/plugin_test.go @@ -2390,16 +2390,16 @@ func TestSaveCurrentBundleToDisk(t *testing.T) { defer os.RemoveAll(srcDir) - err = saveCurrentBundleToDisk(srcDir, "bundle.tar.gz", getTestRawBundle(t)) + bundlePath, err := saveCurrentBundleToDisk(srcDir, getTestRawBundle(t)) if err != nil { t.Fatalf("unexpected error %v", err) } - if _, err := os.Stat(filepath.Join(srcDir, "bundle.tar.gz")); err != nil { + if _, err := os.Stat(bundlePath); err != nil { t.Fatalf("unexpected error %v", err) } - err = saveCurrentBundleToDisk(srcDir, "bundle.tar.gz", nil) + _, err = saveCurrentBundleToDisk(srcDir, nil) if err == nil { t.Fatal("expected error but got nil") }