mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Set permissions on volume before publishing update
This change fixes a race condition that was caused by setting the file owner, group and mode non-atomically, after the updated files had been published. Users who were running non-root containers, without GID 0 permissions, and had removed read permissions from other users by setting defaultMode: 0440 or similar, were getting intermittent permission denied errors when accessing files on secret or configmap volumes or service account tokens on projected volumes during update.
This commit is contained in:
parent
eaa7e1c775
commit
96306f144a
@ -368,7 +368,7 @@ func (cephfsVolume *cephfs) execFuseMount(mountpoint string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = writer.Write(payload)
|
err = writer.Write(payload, nil /*setPerms*/)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("failed to write payload to dir: %v", err)
|
klog.Errorf("failed to write payload to dir: %v", err)
|
||||||
return err
|
return err
|
||||||
|
@ -249,17 +249,17 @@ func (b *configMapVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterA
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = writer.Write(payload)
|
setPerms := func(_ string) error {
|
||||||
|
// This may be the first time writing and new files get created outside the timestamp subdirectory:
|
||||||
|
// change the permissions on the whole volume and not only in the timestamp directory.
|
||||||
|
return volume.SetVolumeOwnership(b, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil))
|
||||||
|
}
|
||||||
|
err = writer.Write(payload, setPerms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Error writing payload to dir: %v", err)
|
klog.Errorf("Error writing payload to dir: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = volume.SetVolumeOwnership(b, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil))
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error applying volume ownership settings for group: %v", mounterArgs.FsGroup)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setupSuccess = true
|
setupSuccess = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -220,18 +220,17 @@ func (b *downwardAPIVolumeMounter) SetUpAt(dir string, mounterArgs volume.Mounte
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = writer.Write(data)
|
setPerms := func(_ string) error {
|
||||||
|
// This may be the first time writing and new files get created outside the timestamp subdirectory:
|
||||||
|
// change the permissions on the whole volume and not only in the timestamp directory.
|
||||||
|
return volume.SetVolumeOwnership(b, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil))
|
||||||
|
}
|
||||||
|
err = writer.Write(data, setPerms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Error writing payload to dir: %v", err)
|
klog.Errorf("Error writing payload to dir: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = volume.SetVolumeOwnership(b, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil))
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error applying volume ownership settings for group: %v", mounterArgs.FsGroup)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
setupSuccess = true
|
setupSuccess = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -230,17 +230,17 @@ func (s *projectedVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterA
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = writer.Write(data)
|
setPerms := func(_ string) error {
|
||||||
|
// This may be the first time writing and new files get created outside the timestamp subdirectory:
|
||||||
|
// change the permissions on the whole volume and not only in the timestamp directory.
|
||||||
|
return volume.SetVolumeOwnership(s, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(s.plugin, nil))
|
||||||
|
}
|
||||||
|
err = writer.Write(data, setPerms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Error writing payload to dir: %v", err)
|
klog.Errorf("Error writing payload to dir: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = volume.SetVolumeOwnership(s, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(s.plugin, nil))
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error applying volume ownership settings for group: %v", mounterArgs.FsGroup)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setupSuccess = true
|
setupSuccess = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -244,17 +244,17 @@ func (b *secretVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = writer.Write(payload)
|
setPerms := func(_ string) error {
|
||||||
|
// This may be the first time writing and new files get created outside the timestamp subdirectory:
|
||||||
|
// change the permissions on the whole volume and not only in the timestamp directory.
|
||||||
|
return volume.SetVolumeOwnership(b, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil))
|
||||||
|
}
|
||||||
|
err = writer.Write(payload, setPerms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Error writing payload to dir: %v", err)
|
klog.Errorf("Error writing payload to dir: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = volume.SetVolumeOwnership(b, mounterArgs.FsGroup, nil /*fsGroupChangePolicy*/, volumeutil.FSGroupCompleteHook(b.plugin, nil))
|
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("Error applying volume ownership settings for group: %v", mounterArgs.FsGroup)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
setupSuccess = true
|
setupSuccess = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -86,11 +86,16 @@ const (
|
|||||||
|
|
||||||
// Write does an atomic projection of the given payload into the writer's target
|
// Write does an atomic projection of the given payload into the writer's target
|
||||||
// directory. Input paths must not begin with '..'.
|
// directory. Input paths must not begin with '..'.
|
||||||
|
// setPerms is an optional pointer to a function that caller can provide to set the
|
||||||
|
// permissions of the newly created files before they are published. The function is
|
||||||
|
// passed subPath which is the name of the timestamped directory that was created
|
||||||
|
// under target directory.
|
||||||
//
|
//
|
||||||
// The Write algorithm is:
|
// The Write algorithm is:
|
||||||
//
|
//
|
||||||
// 1. The payload is validated; if the payload is invalid, the function returns
|
// 1. The payload is validated; if the payload is invalid, the function returns
|
||||||
// 2. The current timestamped directory is detected by reading the data directory
|
//
|
||||||
|
// 2. The current timestamped directory is detected by reading the data directory
|
||||||
// symlink
|
// symlink
|
||||||
//
|
//
|
||||||
// 3. The old version of the volume is walked to determine whether any
|
// 3. The old version of the volume is walked to determine whether any
|
||||||
@ -98,13 +103,19 @@ const (
|
|||||||
//
|
//
|
||||||
// 4. The data in the current timestamped directory is compared to the projected
|
// 4. The data in the current timestamped directory is compared to the projected
|
||||||
// data to determine if an update is required.
|
// data to determine if an update is required.
|
||||||
// 5. A new timestamped dir is created
|
|
||||||
//
|
//
|
||||||
// 6. The payload is written to the new timestamped directory
|
// 5. A new timestamped dir is created.
|
||||||
// 7. A symlink to the new timestamped directory ..data_tmp is created that will
|
//
|
||||||
// become the new data directory
|
// 6. The payload is written to the new timestamped directory.
|
||||||
// 8. The new data directory symlink is renamed to the data directory; rename is atomic
|
//
|
||||||
// 9. Symlinks and directory for new user-visible files are created (if needed).
|
// 7. Permissions are set (if setPerms is not nil) on the new timestamped directory and files.
|
||||||
|
//
|
||||||
|
// 8. A symlink to the new timestamped directory ..data_tmp is created that will
|
||||||
|
// become the new data directory.
|
||||||
|
//
|
||||||
|
// 9. The new data directory symlink is renamed to the data directory; rename is atomic.
|
||||||
|
//
|
||||||
|
// 10. Symlinks and directory for new user-visible files are created (if needed).
|
||||||
//
|
//
|
||||||
// For example, consider the files:
|
// For example, consider the files:
|
||||||
// <target-dir>/podName
|
// <target-dir>/podName
|
||||||
@ -123,9 +134,10 @@ const (
|
|||||||
// linking everything else. On Windows, if a target does not exist, the created symlink
|
// linking everything else. On Windows, if a target does not exist, the created symlink
|
||||||
// will not work properly if the target ends up being a directory.
|
// will not work properly if the target ends up being a directory.
|
||||||
//
|
//
|
||||||
// 10. Old paths are removed from the user-visible portion of the target directory
|
// 11. Old paths are removed from the user-visible portion of the target directory.
|
||||||
// 11. The previous timestamped directory is removed, if it exists
|
//
|
||||||
func (w *AtomicWriter) Write(payload map[string]FileProjection) error {
|
// 12. The previous timestamped directory is removed, if it exists.
|
||||||
|
func (w *AtomicWriter) Write(payload map[string]FileProjection, setPerms func(subPath string) error) error {
|
||||||
// (1)
|
// (1)
|
||||||
cleanPayload, err := validatePayload(payload)
|
cleanPayload, err := validatePayload(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -185,6 +197,14 @@ func (w *AtomicWriter) Write(payload map[string]FileProjection) error {
|
|||||||
klog.V(4).Infof("%s: performed write of new data to ts data directory: %s", w.logContext, tsDir)
|
klog.V(4).Infof("%s: performed write of new data to ts data directory: %s", w.logContext, tsDir)
|
||||||
|
|
||||||
// (7)
|
// (7)
|
||||||
|
if setPerms != nil {
|
||||||
|
if err := setPerms(tsDirName); err != nil {
|
||||||
|
klog.Errorf("%s: error applying ownership settings: %v", w.logContext, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (8)
|
||||||
newDataDirPath := filepath.Join(w.targetDir, newDataDirName)
|
newDataDirPath := filepath.Join(w.targetDir, newDataDirName)
|
||||||
if err = os.Symlink(tsDirName, newDataDirPath); err != nil {
|
if err = os.Symlink(tsDirName, newDataDirPath); err != nil {
|
||||||
os.RemoveAll(tsDir)
|
os.RemoveAll(tsDir)
|
||||||
@ -192,7 +212,7 @@ func (w *AtomicWriter) Write(payload map[string]FileProjection) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// (8)
|
// (9)
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
os.Remove(dataDirPath)
|
os.Remove(dataDirPath)
|
||||||
err = os.Symlink(tsDirName, dataDirPath)
|
err = os.Symlink(tsDirName, dataDirPath)
|
||||||
@ -207,19 +227,19 @@ func (w *AtomicWriter) Write(payload map[string]FileProjection) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// (9)
|
// (10)
|
||||||
if err = w.createUserVisibleFiles(cleanPayload); err != nil {
|
if err = w.createUserVisibleFiles(cleanPayload); err != nil {
|
||||||
klog.Errorf("%s: error creating visible symlinks in %s: %v", w.logContext, w.targetDir, err)
|
klog.Errorf("%s: error creating visible symlinks in %s: %v", w.logContext, w.targetDir, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// (10)
|
// (11)
|
||||||
if err = w.removeUserVisiblePaths(pathsToRemove); err != nil {
|
if err = w.removeUserVisiblePaths(pathsToRemove); err != nil {
|
||||||
klog.Errorf("%s: error removing old visible symlinks: %v", w.logContext, err)
|
klog.Errorf("%s: error removing old visible symlinks: %v", w.logContext, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// (11)
|
// (12)
|
||||||
if len(oldTsDir) > 0 {
|
if len(oldTsDir) > 0 {
|
||||||
if err = os.RemoveAll(oldTsPath); err != nil {
|
if err = os.RemoveAll(oldTsPath); err != nil {
|
||||||
klog.Errorf("%s: error removing old data directory %s: %v", w.logContext, oldTsDir, err)
|
klog.Errorf("%s: error removing old data directory %s: %v", w.logContext, oldTsDir, err)
|
||||||
|
@ -21,6 +21,7 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -229,7 +230,7 @@ func TestPathsToRemove(t *testing.T) {
|
|||||||
defer os.RemoveAll(targetDir)
|
defer os.RemoveAll(targetDir)
|
||||||
|
|
||||||
writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
|
writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
|
||||||
err = writer.Write(tc.payload1)
|
err = writer.Write(tc.payload1, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%v: unexpected error writing: %v", tc.name, err)
|
t.Errorf("%v: unexpected error writing: %v", tc.name, err)
|
||||||
continue
|
continue
|
||||||
@ -397,7 +398,7 @@ IAAAAAAAsDyZDwU=`
|
|||||||
defer os.RemoveAll(targetDir)
|
defer os.RemoveAll(targetDir)
|
||||||
|
|
||||||
writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
|
writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
|
||||||
err = writer.Write(tc.payload)
|
err = writer.Write(tc.payload, nil)
|
||||||
if err != nil && tc.success {
|
if err != nil && tc.success {
|
||||||
t.Errorf("%v: unexpected error writing payload: %v", tc.name, err)
|
t.Errorf("%v: unexpected error writing payload: %v", tc.name, err)
|
||||||
continue
|
continue
|
||||||
@ -574,7 +575,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
|
|
||||||
writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
|
writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
|
||||||
|
|
||||||
err = writer.Write(tc.first)
|
err = writer.Write(tc.first, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%v: unexpected error writing: %v", tc.name, err)
|
t.Errorf("%v: unexpected error writing: %v", tc.name, err)
|
||||||
continue
|
continue
|
||||||
@ -585,7 +586,7 @@ func TestUpdate(t *testing.T) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = writer.Write(tc.next)
|
err = writer.Write(tc.next, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if tc.shouldWrite {
|
if tc.shouldWrite {
|
||||||
t.Errorf("%v: unexpected error writing: %v", tc.name, err)
|
t.Errorf("%v: unexpected error writing: %v", tc.name, err)
|
||||||
@ -743,7 +744,7 @@ func TestMultipleUpdates(t *testing.T) {
|
|||||||
writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
|
writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
|
||||||
|
|
||||||
for _, payload := range tc.payloads {
|
for _, payload := range tc.payloads {
|
||||||
writer.Write(payload)
|
writer.Write(payload, nil)
|
||||||
|
|
||||||
checkVolumeContents(targetDir, tc.name, payload, t)
|
checkVolumeContents(targetDir, tc.name, payload, t)
|
||||||
}
|
}
|
||||||
@ -984,3 +985,54 @@ func TestCreateUserVisibleFiles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetPerms(t *testing.T) {
|
||||||
|
targetDir, err := utiltesting.MkTmpdir("atomic-write")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error creating tmp dir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(targetDir)
|
||||||
|
|
||||||
|
// Test that setPerms() is called once and with valid timestamp directory.
|
||||||
|
payload1 := map[string]FileProjection{
|
||||||
|
"foo/bar.txt": {Mode: 0644, Data: []byte("foo")},
|
||||||
|
"bar/zab.txt": {Mode: 0644, Data: []byte("bar")},
|
||||||
|
}
|
||||||
|
|
||||||
|
var setPermsCalled int
|
||||||
|
writer := &AtomicWriter{targetDir: targetDir, logContext: "-test-"}
|
||||||
|
err = writer.Write(payload1, func(subPath string) error {
|
||||||
|
fileInfo, err := os.Stat(filepath.Join(targetDir, subPath))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting file info: %v", err)
|
||||||
|
}
|
||||||
|
// Ensure that given timestamp directory really exists.
|
||||||
|
if !fileInfo.IsDir() {
|
||||||
|
t.Fatalf("subPath is not a directory: %v", subPath)
|
||||||
|
}
|
||||||
|
setPermsCalled++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error writing: %v", err)
|
||||||
|
}
|
||||||
|
if setPermsCalled != 1 {
|
||||||
|
t.Fatalf("unexpected number of calls to setPerms: %v", setPermsCalled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that errors from setPerms() are propagated.
|
||||||
|
payload2 := map[string]FileProjection{
|
||||||
|
"foo/bar.txt": {Mode: 0644, Data: []byte("foo2")},
|
||||||
|
"bar/zab.txt": {Mode: 0644, Data: []byte("bar2")},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writer.Write(payload2, func(_ string) error {
|
||||||
|
return fmt.Errorf("error in setPerms")
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error while writing but got nil")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "error in setPerms") {
|
||||||
|
t.Fatalf("unexpected error while writing: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user