Commit c4806186 authored by Jean Rouge's avatar Jean Rouge

Review comments

* value names are now purely random * cleaning up leaked registry keys at Kubelet init * fixing a small bug masking create errors Signed-off-by: 's avatarJean Rouge <rougej+github@gmail.com>
parent 3f567588
...@@ -167,19 +167,23 @@ func (ds *dockerService) CreateContainer(_ context.Context, r *runtimeapi.Create ...@@ -167,19 +167,23 @@ func (ds *dockerService) CreateContainer(_ context.Context, r *runtimeapi.Create
return nil, err return nil, err
} }
createResp, err := ds.client.CreateContainer(createConfig) createResp, createErr := ds.client.CreateContainer(createConfig)
if err != nil { if createErr != nil {
createResp, err = recoverFromCreationConflictIfNeeded(ds.client, createConfig, err) createResp, createErr = recoverFromCreationConflictIfNeeded(ds.client, createConfig, createErr)
} }
if err = ds.performPlatformSpecificContainerCreationCleanup(cleanupInfo); err != nil { if err = ds.performPlatformSpecificContainerCreationCleanup(cleanupInfo); err != nil {
if createErr != nil {
// that one is more important to surface
return nil, createErr
}
return nil, err return nil, err
} }
if createResp != nil { if createResp != nil {
return &runtimeapi.CreateContainerResponse{ContainerId: createResp.ID}, nil return &runtimeapi.CreateContainerResponse{ContainerId: createResp.ID}, nil
} }
return nil, err return nil, createErr
} }
// getContainerLogPath returns the container log path specified by kubelet and the real // getContainerLogPath returns the container log path specified by kubelet and the real
......
...@@ -37,3 +37,10 @@ func (ds *dockerService) applyPlatformSpecificDockerConfig(*runtimeapi.CreateCon ...@@ -37,3 +37,10 @@ func (ds *dockerService) applyPlatformSpecificDockerConfig(*runtimeapi.CreateCon
func (ds *dockerService) performPlatformSpecificContainerCreationCleanup(*containerCreationCleanupInfo) error { func (ds *dockerService) performPlatformSpecificContainerCreationCleanup(*containerCreationCleanupInfo) error {
return nil return nil
} }
// platformSpecificContainerCreationKubeletInitCleanup is called when the kubelet
// is starting, and is meant to clean up any cruft left by previous runs;
// errors are simply logged, but don't prevent the kubelet from starting.
func (ds *dockerService) platformSpecificContainerCreationInitCleanup() (errors []error) {
return
}
...@@ -20,10 +20,9 @@ package dockershim ...@@ -20,10 +20,9 @@ package dockershim
import ( import (
"crypto/rand" "crypto/rand"
"crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "regexp"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
...@@ -48,7 +47,7 @@ func (ds *dockerService) applyPlatformSpecificDockerConfig(request *runtimeapi.C ...@@ -48,7 +47,7 @@ func (ds *dockerService) applyPlatformSpecificDockerConfig(request *runtimeapi.C
cleanupInfo := &containerCreationCleanupInfo{} cleanupInfo := &containerCreationCleanupInfo{}
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WindowsGMSA) { if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WindowsGMSA) {
if err := applyGMSAConfig(request, createConfig, cleanupInfo); err != nil { if err := applyGMSAConfig(request.GetConfig(), createConfig, cleanupInfo); err != nil {
return nil, err return nil, err
} }
} }
...@@ -63,14 +62,13 @@ func (ds *dockerService) applyPlatformSpecificDockerConfig(request *runtimeapi.C ...@@ -63,14 +62,13 @@ func (ds *dockerService) applyPlatformSpecificDockerConfig(request *runtimeapi.C
// C: drive) // C: drive)
// When docker supports passing a credential spec's contents directly, we should switch to using that // When docker supports passing a credential spec's contents directly, we should switch to using that
// as it will avoid cluttering the registry. // as it will avoid cluttering the registry.
func applyGMSAConfig(request *runtimeapi.CreateContainerRequest, createConfig *dockertypes.ContainerCreateConfig, cleanupInfo *containerCreationCleanupInfo) error { func applyGMSAConfig(config *runtimeapi.ContainerConfig, createConfig *dockertypes.ContainerCreateConfig, cleanupInfo *containerCreationCleanupInfo) error {
config := request.GetConfig()
credSpec := config.Annotations[kuberuntime.GMSASpecContainerAnnotationKey] credSpec := config.Annotations[kuberuntime.GMSASpecContainerAnnotationKey]
if credSpec == "" { if credSpec == "" {
return nil return nil
} }
valueName, err := copyGMSACredSpecToRegistryValue(credSpec, makeContainerName(request.GetSandboxConfig(), config)) valueName, err := copyGMSACredSpecToRegistryValue(credSpec)
if err != nil { if err != nil {
return err return err
} }
...@@ -86,15 +84,19 @@ func applyGMSAConfig(request *runtimeapi.CreateContainerRequest, createConfig *d ...@@ -86,15 +84,19 @@ func applyGMSAConfig(request *runtimeapi.CreateContainerRequest, createConfig *d
} }
const ( const (
registryNamePrefix = "k8s-cred-spec-"
// same as https://github.com/moby/moby/blob/93d994e29c9cc8d81f1b0477e28d705fa7e2cd72/daemon/oci_windows.go#L23 // same as https://github.com/moby/moby/blob/93d994e29c9cc8d81f1b0477e28d705fa7e2cd72/daemon/oci_windows.go#L23
credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs` credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`
// the prefix for the registry values we write GMSA cred specs to
gMSARegistryValueNamePrefix = "k8s-cred-spec-"
// the number of random bytes to generate suffixes for registry value names
gMSARegistryValueNameSuffixRandomBytes = 40
) )
// useful to allow mocking the registry in tests // useful to allow mocking the registry in tests
type registryKey interface { type registryKey interface {
SetStringValue(name, value string) error SetStringValue(name, value string) error
DeleteValue(name string) error DeleteValue(name string) error
ReadValueNames(n int) ([]string, error)
Close() error Close() error
} }
...@@ -106,10 +108,8 @@ var registryCreateKeyFunc = func(baseKey registry.Key, path string, access uint3 ...@@ -106,10 +108,8 @@ var registryCreateKeyFunc = func(baseKey registry.Key, path string, access uint3
var randomReader = rand.Reader var randomReader = rand.Reader
// copyGMSACredSpecToRegistryKey copies the credential specs to a unique registry value, and returns its name. // copyGMSACredSpecToRegistryKey copies the credential specs to a unique registry value, and returns its name.
// To avoid leaking registry keys over the life of the node, we generate a unique name for that value, and clean func copyGMSACredSpecToRegistryValue(credSpec string) (string, error) {
// it up after creating the container. valueName, err := gMSARegistryValueName()
func copyGMSACredSpecToRegistryValue(credSpec string, dockerContainerName string) (string, error) {
valueName, err := gMSARegistryValueName(credSpec, dockerContainerName)
if err != nil { if err != nil {
return "", err return "", err
} }
...@@ -128,21 +128,18 @@ func copyGMSACredSpecToRegistryValue(credSpec string, dockerContainerName string ...@@ -128,21 +128,18 @@ func copyGMSACredSpecToRegistryValue(credSpec string, dockerContainerName string
} }
// gMSARegistryValueName computes the name of the registry value where to store the GMSA cred spec contents. // gMSARegistryValueName computes the name of the registry value where to store the GMSA cred spec contents.
// The value's name is computed by concatenating the docker container's name (guaranteed to be unique over the // The value's name is purely random.
// container's lifetime), the value itself, and an additional 64 random bytes. func gMSARegistryValueName() (string, error) {
func gMSARegistryValueName(inputs ...string) (string, error) { randBytes := make([]byte, gMSARegistryValueNameSuffixRandomBytes)
hasher := sha256.New()
for _, s := range inputs { if n, err := randomReader.Read(randBytes); err != nil || n != gMSARegistryValueNameSuffixRandomBytes {
// according to the doc, that can never return an error if err == nil {
io.WriteString(hasher, s) err = fmt.Errorf("only got %v random bytes, expected %v", n, len(randBytes))
} }
randBytes := make([]byte, 64) return "", fmt.Errorf("unable to generate random registry value name: %v", err)
if _, err := randomReader.Read(randBytes); err != nil {
return "", fmt.Errorf("unable to generate random string: %v", err)
} }
hasher.Write(randBytes)
return registryNamePrefix + hex.EncodeToString(hasher.Sum(nil)), nil return gMSARegistryValueNamePrefix + hex.EncodeToString(randBytes), nil
} }
// performPlatformSpecificContainerCreationCleanup is responsible for doing any platform-specific cleanup // performPlatformSpecificContainerCreationCleanup is responsible for doing any platform-specific cleanup
...@@ -161,9 +158,8 @@ func (ds *dockerService) performPlatformSpecificContainerCreationCleanup(cleanup ...@@ -161,9 +158,8 @@ func (ds *dockerService) performPlatformSpecificContainerCreationCleanup(cleanup
return nil return nil
} }
// removeGMSARegistryValue removes the registry value containing the GMSA cred spec for this container, if any.
func removeGMSARegistryValue(cleanupInfo *containerCreationCleanupInfo) error { func removeGMSARegistryValue(cleanupInfo *containerCreationCleanupInfo) error {
if cleanupInfo.gMSARegistryValueName == "" { if cleanupInfo == nil || cleanupInfo.gMSARegistryValueName == "" {
return nil return nil
} }
...@@ -178,3 +174,39 @@ func removeGMSARegistryValue(cleanupInfo *containerCreationCleanupInfo) error { ...@@ -178,3 +174,39 @@ func removeGMSARegistryValue(cleanupInfo *containerCreationCleanupInfo) error {
return nil return nil
} }
// platformSpecificContainerCreationKubeletInitCleanup is called when the kubelet
// is starting, and is meant to clean up any cruft left by previous runs;
// errors are simply logged, but don't prevent the kubelet from starting.
func (ds *dockerService) platformSpecificContainerCreationInitCleanup() (errors []error) {
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WindowsGMSA) {
errors = removeAllGMSARegistryValues()
}
return
}
// This is the regex used to detect gMSA cred spec registry values.
var gMSARegistryValueNamesRegex = regexp.MustCompile(fmt.Sprintf("^%s[0-9a-f]{%d}$", gMSARegistryValueNamePrefix, 2*gMSARegistryValueNameSuffixRandomBytes))
func removeAllGMSARegistryValues() (errors []error) {
key, _, err := registryCreateKeyFunc(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.SET_VALUE)
if err != nil {
return []error{fmt.Errorf("unable to open registry key %s: %v", credentialSpecRegistryLocation, err)}
}
defer key.Close()
valueNames, err := key.ReadValueNames(0)
if err != nil {
return []error{fmt.Errorf("unable to list values under registry key %s: %v", credentialSpecRegistryLocation, err)}
}
for _, valueName := range valueNames {
if gMSARegistryValueNamesRegex.MatchString(valueName) {
if err = key.DeleteValue(valueName); err != nil {
errors = append(errors, fmt.Errorf("unable to remove registry value %s/%s: %v", credentialSpecRegistryLocation, valueName, err))
}
}
}
return
}
...@@ -394,6 +394,8 @@ func (ds *dockerService) GetPodPortMappings(podSandboxID string) ([]*hostport.Po ...@@ -394,6 +394,8 @@ func (ds *dockerService) GetPodPortMappings(podSandboxID string) ([]*hostport.Po
// Start initializes and starts components in dockerService. // Start initializes and starts components in dockerService.
func (ds *dockerService) Start() error { func (ds *dockerService) Start() error {
ds.initCleanup()
// Initialize the legacy cleanup flag. // Initialize the legacy cleanup flag.
if ds.startLocalStreamingServer { if ds.startLocalStreamingServer {
go func() { go func() {
...@@ -405,6 +407,16 @@ func (ds *dockerService) Start() error { ...@@ -405,6 +407,16 @@ func (ds *dockerService) Start() error {
return ds.containerManager.Start() return ds.containerManager.Start()
} }
// initCleanup is responsible for cleaning up any crufts left by previous
// runs. If there are any errros, it simply logs them.
func (ds *dockerService) initCleanup() {
errors := ds.platformSpecificContainerCreationInitCleanup()
for _, err := range errors {
klog.Warningf("initialization error: %v", err)
}
}
// Status returns the status of the runtime. // Status returns the status of the runtime.
func (ds *dockerService) Status(_ context.Context, r *runtimeapi.StatusRequest) (*runtimeapi.StatusResponse, error) { func (ds *dockerService) Status(_ context.Context, r *runtimeapi.StatusRequest) (*runtimeapi.StatusResponse, error) {
runtimeReady := &runtimeapi.RuntimeCondition{ runtimeReady := &runtimeapi.RuntimeCondition{
......
...@@ -66,7 +66,6 @@ func makeSandboxName(s *runtimeapi.PodSandboxConfig) string { ...@@ -66,7 +66,6 @@ func makeSandboxName(s *runtimeapi.PodSandboxConfig) string {
}, nameDelimiter) }, nameDelimiter)
} }
// makeContainerName generates a container name that's guaranteed to be unique on its host.
func makeContainerName(s *runtimeapi.PodSandboxConfig, c *runtimeapi.ContainerConfig) string { func makeContainerName(s *runtimeapi.PodSandboxConfig, c *runtimeapi.ContainerConfig) string {
return strings.Join([]string{ return strings.Join([]string{
kubePrefix, // 0 kubePrefix, // 0
......
...@@ -38,7 +38,7 @@ func TestDetermineEffectiveSecurityContext(t *testing.T) { ...@@ -38,7 +38,7 @@ func TestDetermineEffectiveSecurityContext(t *testing.T) {
} }
} }
t.Run("when there's a specific GMSA for that pod, and no pod-wide GMSA", func(t *testing.T) { t.Run("when there's a specific GMSA for that container, and no pod-wide GMSA", func(t *testing.T) {
containerConfig := &runtimeapi.ContainerConfig{} containerConfig := &runtimeapi.ContainerConfig{}
pod := buildPod(map[string]string{ pod := buildPod(map[string]string{
...@@ -49,7 +49,7 @@ func TestDetermineEffectiveSecurityContext(t *testing.T) { ...@@ -49,7 +49,7 @@ func TestDetermineEffectiveSecurityContext(t *testing.T) {
assert.Equal(t, dummyCredSpec, containerConfig.Annotations["container.alpha.windows.kubernetes.io/gmsa-credential-spec"]) assert.Equal(t, dummyCredSpec, containerConfig.Annotations["container.alpha.windows.kubernetes.io/gmsa-credential-spec"])
}) })
t.Run("when there's a specific GMSA for that pod, and a pod-wide GMSA", func(t *testing.T) { t.Run("when there's a specific GMSA for that container, and a pod-wide GMSA", func(t *testing.T) {
containerConfig := &runtimeapi.ContainerConfig{} containerConfig := &runtimeapi.ContainerConfig{}
pod := buildPod(map[string]string{ pod := buildPod(map[string]string{
...@@ -61,7 +61,7 @@ func TestDetermineEffectiveSecurityContext(t *testing.T) { ...@@ -61,7 +61,7 @@ func TestDetermineEffectiveSecurityContext(t *testing.T) {
assert.Equal(t, dummyCredSpec, containerConfig.Annotations["container.alpha.windows.kubernetes.io/gmsa-credential-spec"]) assert.Equal(t, dummyCredSpec, containerConfig.Annotations["container.alpha.windows.kubernetes.io/gmsa-credential-spec"])
}) })
t.Run("when there's no specific GMSA for that pod, and a pod-wide GMSA", func(t *testing.T) { t.Run("when there's no specific GMSA for that container, and a pod-wide GMSA", func(t *testing.T) {
containerConfig := &runtimeapi.ContainerConfig{} containerConfig := &runtimeapi.ContainerConfig{}
pod := buildPod(map[string]string{ pod := buildPod(map[string]string{
...@@ -72,7 +72,7 @@ func TestDetermineEffectiveSecurityContext(t *testing.T) { ...@@ -72,7 +72,7 @@ func TestDetermineEffectiveSecurityContext(t *testing.T) {
assert.Equal(t, dummyCredSpec, containerConfig.Annotations["container.alpha.windows.kubernetes.io/gmsa-credential-spec"]) assert.Equal(t, dummyCredSpec, containerConfig.Annotations["container.alpha.windows.kubernetes.io/gmsa-credential-spec"])
}) })
t.Run("when there's no specific GMSA for that pod, and no pod-wide GMSA", func(t *testing.T) { t.Run("when there's no specific GMSA for that container, and no pod-wide GMSA", func(t *testing.T) {
containerConfig := &runtimeapi.ContainerConfig{} containerConfig := &runtimeapi.ContainerConfig{}
determineEffectiveSecurityContext(containerConfig, container, &corev1.Pod{}) determineEffectiveSecurityContext(containerConfig, container, &corev1.Pod{})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment