From 2eb5744ed05522e69d5eba6b9c5dde3b5e47bd86 Mon Sep 17 00:00:00 2001 From: Fredrik Appelros Date: Tue, 12 Jul 2022 15:05:35 +0200 Subject: [PATCH] plugins/bundle: use unique temporary files (#4786) 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 60f33b3c06..55a572d653 100644 --- a/plugins/bundle/plugin.go +++ b/plugins/bundle/plugin.go @@ -653,10 +653,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) @@ -674,26 +673,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 c0e705b8d1..c97847163c 100644 --- a/plugins/bundle/plugin_test.go +++ b/plugins/bundle/plugin_test.go @@ -2530,16 +2530,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") }