mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-30 14:46:16 +00:00
219 lines
7.1 KiB
Go
219 lines
7.1 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
/*
|
|
Copyright 2023 The Kubernetes 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 filesystem
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
winio "github.com/Microsoft/go-winio"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
func TestIsUnixDomainSocketPipe(t *testing.T) {
|
|
generatePipeName := func(suffixLen int) string {
|
|
rand.Seed(time.Now().UnixNano())
|
|
letter := []rune("abcdef0123456789")
|
|
b := make([]rune, suffixLen)
|
|
for i := range b {
|
|
b[i] = letter[rand.Intn(len(letter))]
|
|
}
|
|
return "\\\\.\\pipe\\test-pipe" + string(b)
|
|
}
|
|
testFile := generatePipeName(4)
|
|
pipeln, err := winio.ListenPipe(testFile, &winio.PipeConfig{SecurityDescriptor: "D:P(A;;GA;;;BA)(A;;GA;;;SY)"})
|
|
defer pipeln.Close()
|
|
|
|
require.NoErrorf(t, err, "Failed to listen on named pipe for test purposes: %v", err)
|
|
result, err := IsUnixDomainSocket(testFile)
|
|
assert.NoError(t, err, "Unexpected error from IsUnixDomainSocket.")
|
|
assert.False(t, result, "Unexpected result: true from IsUnixDomainSocket.")
|
|
}
|
|
|
|
// This is required as on Windows it's possible for the socket file backing a Unix domain socket to
|
|
// exist but not be ready for socket communications yet as per
|
|
// https://github.com/kubernetes/kubernetes/issues/104584
|
|
func TestPendingUnixDomainSocket(t *testing.T) {
|
|
// Create a temporary file that will simulate the Unix domain socket file in a
|
|
// not-yet-ready state. We need this because the Kubelet keeps an eye on file
|
|
// changes and acts on them, leading to potential race issues as described in
|
|
// the referenced issue above
|
|
f, err := os.CreateTemp("", "test-domain-socket")
|
|
require.NoErrorf(t, err, "Failed to create file for test purposes: %v", err)
|
|
testFile := f.Name()
|
|
f.Close()
|
|
|
|
// Start the check at this point
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
go func() {
|
|
result, err := IsUnixDomainSocket(testFile)
|
|
assert.Nil(t, err, "Unexpected error from IsUnixDomainSocket: %v", err)
|
|
assert.True(t, result, "Unexpected result: false from IsUnixDomainSocket.")
|
|
wg.Done()
|
|
}()
|
|
|
|
// Wait a sufficient amount of time to make sure the retry logic kicks in
|
|
time.Sleep(socketDialRetryPeriod)
|
|
|
|
// Replace the temporary file with an actual Unix domain socket file
|
|
os.Remove(testFile)
|
|
ta, err := net.ResolveUnixAddr("unix", testFile)
|
|
require.NoError(t, err, "Failed to ResolveUnixAddr.")
|
|
unixln, err := net.ListenUnix("unix", ta)
|
|
require.NoError(t, err, "Failed to ListenUnix.")
|
|
|
|
// Wait for the goroutine to finish, then close the socket
|
|
wg.Wait()
|
|
unixln.Close()
|
|
}
|
|
|
|
func TestWindowsChmod(t *testing.T) {
|
|
// Note: OWNER will be replaced with the actual owner SID in the test cases
|
|
testCases := []struct {
|
|
fileMode os.FileMode
|
|
expectedDescriptor string
|
|
}{
|
|
{
|
|
fileMode: 0777,
|
|
expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;FA;;;OWNER)(A;OICI;FA;;;BA)(A;OICI;FA;;;BU)",
|
|
},
|
|
{
|
|
fileMode: 0750,
|
|
expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;FA;;;OWNER)(A;OICI;0x1200a9;;;BA)", // 0x1200a9 = GENERIC_READ | GENERIC_EXECUTE
|
|
},
|
|
{
|
|
fileMode: 0664,
|
|
expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;0x12019f;;;OWNER)(A;OICI;0x12019f;;;BA)(A;OICI;FR;;;BU)", // 0x12019f = GENERIC_READ | GENERIC_WRITE
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
tempDir, err := os.MkdirTemp("", "test-dir")
|
|
require.NoError(t, err, "Failed to create temporary directory.")
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
// Set the file GROUP to BUILTIN\Administrators (BA) for test determinism and
|
|
err = setGroupInfo(tempDir, "S-1-5-32-544")
|
|
require.NoError(t, err, "Failed to set group for directory.")
|
|
|
|
err = Chmod(tempDir, testCase.fileMode)
|
|
require.NoError(t, err, "Failed to set permissions for directory.")
|
|
|
|
owner, descriptor, err := getPermissionsInfo(tempDir)
|
|
require.NoError(t, err, "Failed to get permissions for directory.")
|
|
|
|
expectedDescriptor := strings.ReplaceAll(testCase.expectedDescriptor, "OWNER", owner)
|
|
|
|
assert.Equal(t, expectedDescriptor, descriptor, "Unexpected DACL for directory. when setting permissions to %o", testCase.fileMode)
|
|
}
|
|
}
|
|
|
|
// Gets the owner and entire security descriptor of a file or directory in the SDDL format
|
|
// https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-definition-language
|
|
func getPermissionsInfo(path string) (string, string, error) {
|
|
sd, err := windows.GetNamedSecurityInfo(
|
|
path,
|
|
windows.SE_FILE_OBJECT,
|
|
windows.DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("Error getting security descriptor for file %s: %v", path, err)
|
|
}
|
|
|
|
owner, _, err := sd.Owner()
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("Error getting owner SID for file %s: %v", path, err)
|
|
}
|
|
|
|
sdString := sd.String()
|
|
|
|
return owner.String(), sdString, nil
|
|
}
|
|
|
|
// Sets the GROUP of a file or a directory to the specified group
|
|
func setGroupInfo(path, group string) error {
|
|
groupSID, err := windows.StringToSid(group)
|
|
if err != nil {
|
|
return fmt.Errorf("Error converting group name %s to SID: %v", group, err)
|
|
|
|
}
|
|
|
|
err = windows.SetNamedSecurityInfo(
|
|
path,
|
|
windows.SE_FILE_OBJECT,
|
|
windows.GROUP_SECURITY_INFORMATION,
|
|
nil, // owner SID
|
|
groupSID,
|
|
nil, // DACL
|
|
nil, //SACL
|
|
)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("Error setting group SID for file %s: %v", path, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TestDeleteFilePermissions tests that when a folder's permissions are set to 0660, child items
|
|
// cannot be deleted in the folder but when a folder's permissions are set to 0770, child items can be deleted.
|
|
func TestDeleteFilePermissions(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "test-dir")
|
|
require.NoError(t, err, "Failed to create temporary directory.")
|
|
|
|
err = Chmod(tempDir, 0660)
|
|
require.NoError(t, err, "Failed to set permissions for directory to 0660.")
|
|
|
|
filePath := filepath.Join(tempDir, "test-file")
|
|
err = os.WriteFile(filePath, []byte("test"), 0440)
|
|
require.NoError(t, err, "Failed to create file in directory.")
|
|
|
|
err = os.Remove(filePath)
|
|
require.Error(t, err, "Expected expected error when trying to remove file in directory.")
|
|
|
|
err = Chmod(tempDir, 0770)
|
|
require.NoError(t, err, "Failed to set permissions for directory to 0770.")
|
|
|
|
err = os.Remove(filePath)
|
|
require.NoError(t, err, "Failed to remove file in directory.")
|
|
|
|
err = os.Remove(tempDir)
|
|
require.NoError(t, err, "Failed to remove directory.")
|
|
}
|
|
|
|
func TestAbsWithSlash(t *testing.T) {
|
|
// On Windows, filepath.IsAbs will not return True for paths prefixed with a slash
|
|
assert.True(t, IsAbs("/test"))
|
|
assert.True(t, IsAbs("\\test"))
|
|
|
|
assert.False(t, IsAbs("./local"))
|
|
assert.False(t, IsAbs("local"))
|
|
}
|