mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Kubelet changes for Windows GMSA support
This patch comprises the kubelet changes outlined in the GMSA KEP (https://github.com/kubernetes/enhancements/blob/master/keps/sig-windows/20181221-windows-group-managed-service-accounts-for-container-identity.md) to add GMSA support to Windows workloads. More precisely, it includes the logic proposed in the KEP to resolve which GMSA spec should be applied to which containers, and changes `dockershim` to copy the relevant GMSA credential specs to Windows registry values prior to creating the container, passing them down to docker itself, and finally removing the values from the registry afterwards; both these changes need to be activated with the `WindowsGMSA` feature gate. Includes unit tests. Signed-off-by: Jean Rouge <rougej+github@gmail.com>
This commit is contained in:
		@@ -404,6 +404,12 @@ const (
 | 
				
			|||||||
	//
 | 
						//
 | 
				
			||||||
	// Enables the AWS EBS in-tree driver to AWS EBS CSI Driver migration feature.
 | 
						// Enables the AWS EBS in-tree driver to AWS EBS CSI Driver migration feature.
 | 
				
			||||||
	CSIMigrationAWS utilfeature.Feature = "CSIMigrationAWS"
 | 
						CSIMigrationAWS utilfeature.Feature = "CSIMigrationAWS"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// owner: @wk8
 | 
				
			||||||
 | 
						// alpha: v1.14
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// Enables GMSA support for Windows workloads.
 | 
				
			||||||
 | 
						WindowsGMSA utilfeature.Feature = "WindowsGMSA"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,8 @@ go_library(
 | 
				
			|||||||
        "doc.go",
 | 
					        "doc.go",
 | 
				
			||||||
        "docker_checkpoint.go",
 | 
					        "docker_checkpoint.go",
 | 
				
			||||||
        "docker_container.go",
 | 
					        "docker_container.go",
 | 
				
			||||||
 | 
					        "docker_container_unsupported.go",
 | 
				
			||||||
 | 
					        "docker_container_windows.go",
 | 
				
			||||||
        "docker_image.go",
 | 
					        "docker_image.go",
 | 
				
			||||||
        "docker_image_linux.go",
 | 
					        "docker_image_linux.go",
 | 
				
			||||||
        "docker_image_unsupported.go",
 | 
					        "docker_image_unsupported.go",
 | 
				
			||||||
@@ -71,8 +73,11 @@ go_library(
 | 
				
			|||||||
        "//vendor/k8s.io/utils/exec:go_default_library",
 | 
					        "//vendor/k8s.io/utils/exec:go_default_library",
 | 
				
			||||||
    ] + select({
 | 
					    ] + select({
 | 
				
			||||||
        "@io_bazel_rules_go//go/platform:windows": [
 | 
					        "@io_bazel_rules_go//go/platform:windows": [
 | 
				
			||||||
 | 
					            "//pkg/features:go_default_library",
 | 
				
			||||||
            "//pkg/kubelet/apis:go_default_library",
 | 
					            "//pkg/kubelet/apis:go_default_library",
 | 
				
			||||||
            "//pkg/kubelet/winstats:go_default_library",
 | 
					            "//pkg/kubelet/winstats:go_default_library",
 | 
				
			||||||
 | 
					            "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
 | 
				
			||||||
 | 
					            "//vendor/golang.org/x/sys/windows/registry:go_default_library",
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "//conditions:default": [],
 | 
					        "//conditions:default": [],
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
@@ -84,6 +89,7 @@ go_test(
 | 
				
			|||||||
        "convert_test.go",
 | 
					        "convert_test.go",
 | 
				
			||||||
        "docker_checkpoint_test.go",
 | 
					        "docker_checkpoint_test.go",
 | 
				
			||||||
        "docker_container_test.go",
 | 
					        "docker_container_test.go",
 | 
				
			||||||
 | 
					        "docker_container_windows_test.go",
 | 
				
			||||||
        "docker_image_test.go",
 | 
					        "docker_image_test.go",
 | 
				
			||||||
        "docker_sandbox_test.go",
 | 
					        "docker_sandbox_test.go",
 | 
				
			||||||
        "docker_service_test.go",
 | 
					        "docker_service_test.go",
 | 
				
			||||||
@@ -118,6 +124,9 @@ go_test(
 | 
				
			|||||||
        "@io_bazel_rules_go//go/platform:linux": [
 | 
					        "@io_bazel_rules_go//go/platform:linux": [
 | 
				
			||||||
            "//staging/src/k8s.io/api/core/v1:go_default_library",
 | 
					            "//staging/src/k8s.io/api/core/v1:go_default_library",
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
 | 
					        "@io_bazel_rules_go//go/platform:windows": [
 | 
				
			||||||
 | 
					            "//vendor/golang.org/x/sys/windows/registry:go_default_library",
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
        "//conditions:default": [],
 | 
					        "//conditions:default": [],
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -162,11 +162,20 @@ func (ds *dockerService) CreateContainer(_ context.Context, r *runtimeapi.Create
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	hc.SecurityOpt = append(hc.SecurityOpt, securityOpts...)
 | 
						hc.SecurityOpt = append(hc.SecurityOpt, securityOpts...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cleanupInfo, err := ds.applyPlatformSpecificDockerConfig(r, &createConfig)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	createResp, err := ds.client.CreateContainer(createConfig)
 | 
						createResp, err := ds.client.CreateContainer(createConfig)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		createResp, err = recoverFromCreationConflictIfNeeded(ds.client, createConfig, err)
 | 
							createResp, err = recoverFromCreationConflictIfNeeded(ds.client, createConfig, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = ds.performPlatformSpecificContainerCreationCleanup(cleanupInfo); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if createResp != nil {
 | 
						if createResp != nil {
 | 
				
			||||||
		return &runtimeapi.CreateContainerResponse{ContainerId: createResp.ID}, nil
 | 
							return &runtimeapi.CreateContainerResponse{ContainerId: createResp.ID}, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										39
									
								
								pkg/kubelet/dockershim/docker_container_unsupported.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								pkg/kubelet/dockershim/docker_container_unsupported.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					// +build !windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2019 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 dockershim
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						dockertypes "github.com/docker/docker/api/types"
 | 
				
			||||||
 | 
						runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type containerCreationCleanupInfo struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// applyPlatformSpecificDockerConfig applies platform-specific configurations to a dockertypes.ContainerCreateConfig struct.
 | 
				
			||||||
 | 
					// The containerCreationCleanupInfo struct it returns will be passed as is to performPlatformSpecificContainerCreationCleanup
 | 
				
			||||||
 | 
					// after the container has been created.
 | 
				
			||||||
 | 
					func (ds *dockerService) applyPlatformSpecificDockerConfig(*runtimeapi.CreateContainerRequest, *dockertypes.ContainerCreateConfig) (*containerCreationCleanupInfo, error) {
 | 
				
			||||||
 | 
						return nil, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// performPlatformSpecificContainerCreationCleanup is responsible for doing any platform-specific cleanup
 | 
				
			||||||
 | 
					// after a container creation.
 | 
				
			||||||
 | 
					func (ds *dockerService) performPlatformSpecificContainerCreationCleanup(*containerCreationCleanupInfo) error {
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										180
									
								
								pkg/kubelet/dockershim/docker_container_windows.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								pkg/kubelet/dockershim/docker_container_windows.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,180 @@
 | 
				
			|||||||
 | 
					// +build windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2019 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 dockershim
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/rand"
 | 
				
			||||||
 | 
						"crypto/sha256"
 | 
				
			||||||
 | 
						"encoding/hex"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"golang.org/x/sys/windows/registry"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dockertypes "github.com/docker/docker/api/types"
 | 
				
			||||||
 | 
						dockercontainer "github.com/docker/docker/api/types/container"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
 | 
						"k8s.io/klog"
 | 
				
			||||||
 | 
						kubefeatures "k8s.io/kubernetes/pkg/features"
 | 
				
			||||||
 | 
						runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/kubelet/kuberuntime"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type containerCreationCleanupInfo struct {
 | 
				
			||||||
 | 
						gMSARegistryValueName string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// applyPlatformSpecificDockerConfig applies platform-specific configurations to a dockertypes.ContainerCreateConfig struct.
 | 
				
			||||||
 | 
					// The containerCreationCleanupInfo struct it returns will be passed as is to performPlatformSpecificContainerCreationCleanup
 | 
				
			||||||
 | 
					// after the container has been created.
 | 
				
			||||||
 | 
					func (ds *dockerService) applyPlatformSpecificDockerConfig(request *runtimeapi.CreateContainerRequest, createConfig *dockertypes.ContainerCreateConfig) (*containerCreationCleanupInfo, error) {
 | 
				
			||||||
 | 
						cleanupInfo := &containerCreationCleanupInfo{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WindowsGMSA) {
 | 
				
			||||||
 | 
							if err := applyGMSAConfig(request, createConfig, cleanupInfo); err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return cleanupInfo, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// applyGMSAConfig looks at the kuberuntime.GMSASpecContainerAnnotationKey container annotation; if present,
 | 
				
			||||||
 | 
					// it copies its contents to a unique registry value, and sets a SecurityOpt on the config pointing to that registry value.
 | 
				
			||||||
 | 
					// We use registry values instead of files since their location cannot change - as opposed to credential spec files,
 | 
				
			||||||
 | 
					// whose location could potentially change down the line, or even be unknown (eg if docker is not installed on the
 | 
				
			||||||
 | 
					// C: drive)
 | 
				
			||||||
 | 
					// When docker supports passing a credential spec's contents directly, we should switch to using that
 | 
				
			||||||
 | 
					// as it will avoid cluttering the registry.
 | 
				
			||||||
 | 
					func applyGMSAConfig(request *runtimeapi.CreateContainerRequest, createConfig *dockertypes.ContainerCreateConfig, cleanupInfo *containerCreationCleanupInfo) error {
 | 
				
			||||||
 | 
						config := request.GetConfig()
 | 
				
			||||||
 | 
						credSpec := config.Annotations[kuberuntime.GMSASpecContainerAnnotationKey]
 | 
				
			||||||
 | 
						if credSpec == "" {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						valueName, err := copyGMSACredSpecToRegistryValue(credSpec, makeContainerName(request.GetSandboxConfig(), config))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if createConfig.HostConfig == nil {
 | 
				
			||||||
 | 
							createConfig.HostConfig = &dockercontainer.HostConfig{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						createConfig.HostConfig.SecurityOpt = append(createConfig.HostConfig.SecurityOpt, "credentialspec=registry://"+valueName)
 | 
				
			||||||
 | 
						cleanupInfo.gMSARegistryValueName = valueName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						registryNamePrefix = "k8s-cred-spec-"
 | 
				
			||||||
 | 
						// same as https://github.com/moby/moby/blob/93d994e29c9cc8d81f1b0477e28d705fa7e2cd72/daemon/oci_windows.go#L23
 | 
				
			||||||
 | 
						credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// useful to allow mocking the registry in tests
 | 
				
			||||||
 | 
					type registryKey interface {
 | 
				
			||||||
 | 
						SetStringValue(name, value string) error
 | 
				
			||||||
 | 
						DeleteValue(name string) error
 | 
				
			||||||
 | 
						Close() error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var registryCreateKeyFunc = func(baseKey registry.Key, path string, access uint32) (registryKey, bool, error) {
 | 
				
			||||||
 | 
						return registry.CreateKey(baseKey, path, access)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// and same for random
 | 
				
			||||||
 | 
					var randomReader = rand.Reader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					// it up after creating the container.
 | 
				
			||||||
 | 
					func copyGMSACredSpecToRegistryValue(credSpec string, dockerContainerName string) (string, error) {
 | 
				
			||||||
 | 
						valueName, err := gMSARegistryValueName(credSpec, dockerContainerName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// write to the registry
 | 
				
			||||||
 | 
						key, _, err := registryCreateKeyFunc(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.SET_VALUE)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("unable to open registry key %s: %v", credentialSpecRegistryLocation, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer key.Close()
 | 
				
			||||||
 | 
						if err = key.SetStringValue(valueName, credSpec); err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("unable to write into registry value %s/%s: %v", credentialSpecRegistryLocation, valueName, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return valueName, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					// container's lifetime), the value itself, and an additional 64 random bytes.
 | 
				
			||||||
 | 
					func gMSARegistryValueName(inputs ...string) (string, error) {
 | 
				
			||||||
 | 
						hasher := sha256.New()
 | 
				
			||||||
 | 
						for _, s := range inputs {
 | 
				
			||||||
 | 
							// according to the doc, that can never return an error
 | 
				
			||||||
 | 
							io.WriteString(hasher, s)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						randBytes := make([]byte, 64)
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// performPlatformSpecificContainerCreationCleanup is responsible for doing any platform-specific cleanup
 | 
				
			||||||
 | 
					// after a container creation.
 | 
				
			||||||
 | 
					func (ds *dockerService) performPlatformSpecificContainerCreationCleanup(cleanupInfo *containerCreationCleanupInfo) error {
 | 
				
			||||||
 | 
						if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WindowsGMSA) {
 | 
				
			||||||
 | 
							// this is best effort, we don't bubble errors upstream as failing to remove the GMSA registry keys shouldn't
 | 
				
			||||||
 | 
							// prevent k8s from working correctly, and the leaked registry keys are not a major concern anyway:
 | 
				
			||||||
 | 
							// they don't contain any secret, and they're sufficiently random to prevent collisions with
 | 
				
			||||||
 | 
							// future ones
 | 
				
			||||||
 | 
							if err := removeGMSARegistryValue(cleanupInfo); err != nil {
 | 
				
			||||||
 | 
								klog.Warningf("won't remove GMSA cred spec registry value: %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// removeGMSARegistryValue removes the registry value containing the GMSA cred spec for this container, if any.
 | 
				
			||||||
 | 
					func removeGMSARegistryValue(cleanupInfo *containerCreationCleanupInfo) error {
 | 
				
			||||||
 | 
						if cleanupInfo.gMSARegistryValueName == "" {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						key, _, err := registryCreateKeyFunc(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.SET_VALUE)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to open registry key %s: %v", credentialSpecRegistryLocation, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer key.Close()
 | 
				
			||||||
 | 
						if err = key.DeleteValue(cleanupInfo.gMSARegistryValueName); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to remove registry value %s/%s: %v", credentialSpecRegistryLocation, cleanupInfo.gMSARegistryValueName, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										233
									
								
								pkg/kubelet/dockershim/docker_container_windows_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								pkg/kubelet/dockershim/docker_container_windows_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,233 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2019 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 dockershim
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dockertypes "github.com/docker/docker/api/types"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"golang.org/x/sys/windows/registry"
 | 
				
			||||||
 | 
						runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type dummyRegistryKey struct {
 | 
				
			||||||
 | 
						setStringError    error
 | 
				
			||||||
 | 
						stringValues      [][]string
 | 
				
			||||||
 | 
						deleteValueError  error
 | 
				
			||||||
 | 
						deletedValueNames []string
 | 
				
			||||||
 | 
						closed            bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k *dummyRegistryKey) SetStringValue(name, value string) error {
 | 
				
			||||||
 | 
						k.stringValues = append(k.stringValues, []string{name, value})
 | 
				
			||||||
 | 
						return k.setStringError
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k *dummyRegistryKey) DeleteValue(name string) error {
 | 
				
			||||||
 | 
						k.deletedValueNames = append(k.deletedValueNames, name)
 | 
				
			||||||
 | 
						return k.deleteValueError
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (k *dummyRegistryKey) Close() error {
 | 
				
			||||||
 | 
						k.closed = true
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestApplyGMSAConfig(t *testing.T) {
 | 
				
			||||||
 | 
						dummyCredSpec := "test cred spec contents"
 | 
				
			||||||
 | 
						randomBytes := []byte{85, 205, 157, 137, 41, 50, 187, 175, 242, 115, 92, 212, 181, 70, 56, 20, 172, 17, 100, 178, 19, 42, 217, 177, 240, 37, 127, 123, 53, 250, 61, 157, 11, 41, 69, 160, 117, 163, 51, 118, 53, 86, 167, 111, 137, 78, 195, 229, 50, 144, 178, 209, 66, 107, 144, 165, 184, 92, 10, 17, 229, 163, 194, 12}
 | 
				
			||||||
 | 
						expectedHash := "8975ef53024af213c1aca6dfc6e2e48f42c3a984a79e67b140627b8d96007c2a"
 | 
				
			||||||
 | 
						expectedValueName := "k8s-cred-spec-" + expectedHash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sandboxConfig := &runtimeapi.PodSandboxConfig{
 | 
				
			||||||
 | 
							Metadata: &runtimeapi.PodSandboxMetadata{
 | 
				
			||||||
 | 
								Namespace: "namespace",
 | 
				
			||||||
 | 
								Uid:       "uid",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						containerMeta := &runtimeapi.ContainerMetadata{
 | 
				
			||||||
 | 
							Name:    "container_name",
 | 
				
			||||||
 | 
							Attempt: 12,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requestWithoutGMSAAnnotation := &runtimeapi.CreateContainerRequest{
 | 
				
			||||||
 | 
							Config:        &runtimeapi.ContainerConfig{Metadata: containerMeta},
 | 
				
			||||||
 | 
							SandboxConfig: sandboxConfig,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requestWithGMSAAnnotation := &runtimeapi.CreateContainerRequest{
 | 
				
			||||||
 | 
							Config: &runtimeapi.ContainerConfig{
 | 
				
			||||||
 | 
								Metadata:    containerMeta,
 | 
				
			||||||
 | 
								Annotations: map[string]string{"container.alpha.windows.kubernetes.io/gmsa-credential-spec": dummyCredSpec},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							SandboxConfig: sandboxConfig,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("happy path", func(t *testing.T) {
 | 
				
			||||||
 | 
							key := &dummyRegistryKey{}
 | 
				
			||||||
 | 
							defer setRegistryCreateKeyFunc(t, key)()
 | 
				
			||||||
 | 
							defer setRandomReader(randomBytes)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							createConfig := &dockertypes.ContainerCreateConfig{}
 | 
				
			||||||
 | 
							cleanupInfo := &containerCreationCleanupInfo{}
 | 
				
			||||||
 | 
							err := applyGMSAConfig(requestWithGMSAAnnotation, createConfig, cleanupInfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Nil(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// the registry key should have been properly created
 | 
				
			||||||
 | 
							assert.Equal(t, 1, len(key.stringValues))
 | 
				
			||||||
 | 
							assert.Equal(t, []string{expectedValueName, dummyCredSpec}, key.stringValues[0])
 | 
				
			||||||
 | 
							assert.True(t, key.closed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// the create config's security opt should have been populated
 | 
				
			||||||
 | 
							assert.Equal(t, createConfig.HostConfig.SecurityOpt, []string{"credentialspec=registry://" + expectedValueName})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// and the name of that value should have been saved to the cleanup info
 | 
				
			||||||
 | 
							assert.Equal(t, expectedValueName, cleanupInfo.gMSARegistryValueName)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						t.Run("happy path with a truly random string", func(t *testing.T) {
 | 
				
			||||||
 | 
							defer setRegistryCreateKeyFunc(t, &dummyRegistryKey{})()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							createConfig := &dockertypes.ContainerCreateConfig{}
 | 
				
			||||||
 | 
							cleanupInfo := &containerCreationCleanupInfo{}
 | 
				
			||||||
 | 
							err := applyGMSAConfig(requestWithGMSAAnnotation, createConfig, cleanupInfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Nil(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							secOpt := createConfig.HostConfig.SecurityOpt[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectedPrefix := "credentialspec=registry://k8s-cred-spec-"
 | 
				
			||||||
 | 
							assert.Equal(t, expectedPrefix, secOpt[:len(expectedPrefix)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							hash := secOpt[len(expectedPrefix):]
 | 
				
			||||||
 | 
							hexRegex, _ := regexp.Compile("^[0-9a-f]{64}$")
 | 
				
			||||||
 | 
							assert.True(t, hexRegex.MatchString(hash))
 | 
				
			||||||
 | 
							assert.NotEqual(t, expectedHash, hash)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Equal(t, "k8s-cred-spec-"+hash, cleanupInfo.gMSARegistryValueName)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						t.Run("if there's an error opening the registry key", func(t *testing.T) {
 | 
				
			||||||
 | 
							defer setRegistryCreateKeyFunc(t, &dummyRegistryKey{}, fmt.Errorf("dummy error"))()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err := applyGMSAConfig(requestWithGMSAAnnotation, &dockertypes.ContainerCreateConfig{}, &containerCreationCleanupInfo{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.NotNil(t, err)
 | 
				
			||||||
 | 
							assert.True(t, strings.Contains(err.Error(), "unable to open registry key"))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						t.Run("if there's an error writing the registry key", func(t *testing.T) {
 | 
				
			||||||
 | 
							key := &dummyRegistryKey{}
 | 
				
			||||||
 | 
							key.setStringError = fmt.Errorf("dummy error")
 | 
				
			||||||
 | 
							defer setRegistryCreateKeyFunc(t, key)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err := applyGMSAConfig(requestWithGMSAAnnotation, &dockertypes.ContainerCreateConfig{}, &containerCreationCleanupInfo{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.NotNil(t, err)
 | 
				
			||||||
 | 
							assert.True(t, strings.Contains(err.Error(), "unable to write into registry value"))
 | 
				
			||||||
 | 
							assert.True(t, key.closed)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						t.Run("if there is no GMSA annotation", func(t *testing.T) {
 | 
				
			||||||
 | 
							createConfig := &dockertypes.ContainerCreateConfig{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err := applyGMSAConfig(requestWithoutGMSAAnnotation, createConfig, &containerCreationCleanupInfo{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Nil(t, err)
 | 
				
			||||||
 | 
							assert.Nil(t, createConfig.HostConfig)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestRemoveGMSARegistryValue(t *testing.T) {
 | 
				
			||||||
 | 
						emptyCleanupInfo := &containerCreationCleanupInfo{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						valueName := "k8s-cred-spec-8975ef53024af213c1aca6dfc6e2e48f42c3a984a79e67b140627b8d96007c2a"
 | 
				
			||||||
 | 
						cleanupInfoWithValue := &containerCreationCleanupInfo{gMSARegistryValueName: valueName}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("it does remove the registry value", func(t *testing.T) {
 | 
				
			||||||
 | 
							key := &dummyRegistryKey{}
 | 
				
			||||||
 | 
							defer setRegistryCreateKeyFunc(t, key)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err := removeGMSARegistryValue(cleanupInfoWithValue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Nil(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// the registry key should have been properly deleted
 | 
				
			||||||
 | 
							assert.Equal(t, 1, len(key.deletedValueNames))
 | 
				
			||||||
 | 
							assert.Equal(t, []string{valueName}, key.deletedValueNames)
 | 
				
			||||||
 | 
							assert.True(t, key.closed)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						t.Run("if there's an error opening the registry key", func(t *testing.T) {
 | 
				
			||||||
 | 
							defer setRegistryCreateKeyFunc(t, &dummyRegistryKey{}, fmt.Errorf("dummy error"))()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err := removeGMSARegistryValue(cleanupInfoWithValue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.NotNil(t, err)
 | 
				
			||||||
 | 
							assert.True(t, strings.Contains(err.Error(), "unable to open registry key"))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						t.Run("if there's an error writing the registry key", func(t *testing.T) {
 | 
				
			||||||
 | 
							key := &dummyRegistryKey{}
 | 
				
			||||||
 | 
							key.deleteValueError = fmt.Errorf("dummy error")
 | 
				
			||||||
 | 
							defer setRegistryCreateKeyFunc(t, key)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err := removeGMSARegistryValue(cleanupInfoWithValue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.NotNil(t, err)
 | 
				
			||||||
 | 
							assert.True(t, strings.Contains(err.Error(), "unable to remove registry value"))
 | 
				
			||||||
 | 
							assert.True(t, key.closed)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						t.Run("if there's no registry value to be removed", func(t *testing.T) {
 | 
				
			||||||
 | 
							err := removeGMSARegistryValue(emptyCleanupInfo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Nil(t, err)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// setRegistryCreateKeyFunc replaces the registryCreateKeyFunc package variable, and returns a function
 | 
				
			||||||
 | 
					// to be called to revert the change when done with testing.
 | 
				
			||||||
 | 
					func setRegistryCreateKeyFunc(t *testing.T, key *dummyRegistryKey, err ...error) func() {
 | 
				
			||||||
 | 
						previousRegistryCreateKeyFunc := registryCreateKeyFunc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						registryCreateKeyFunc = func(baseKey registry.Key, path string, access uint32) (registryKey, bool, error) {
 | 
				
			||||||
 | 
							// this should always be called with exactly the same arguments
 | 
				
			||||||
 | 
							assert.Equal(t, registry.LOCAL_MACHINE, baseKey)
 | 
				
			||||||
 | 
							assert.Equal(t, credentialSpecRegistryLocation, path)
 | 
				
			||||||
 | 
							assert.Equal(t, uint32(registry.SET_VALUE), access)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(err) > 0 {
 | 
				
			||||||
 | 
								return nil, false, err[0]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return key, false, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return func() {
 | 
				
			||||||
 | 
							registryCreateKeyFunc = previousRegistryCreateKeyFunc
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// setRandomReader replaces the randomReader package variable with a dummy reader that returns the provided
 | 
				
			||||||
 | 
					// byte slice, and returns a function to be called to revert the change when done with testing.
 | 
				
			||||||
 | 
					func setRandomReader(b []byte) func() {
 | 
				
			||||||
 | 
						previousRandomReader := randomReader
 | 
				
			||||||
 | 
						randomReader = bytes.NewReader(b)
 | 
				
			||||||
 | 
						return func() {
 | 
				
			||||||
 | 
							randomReader = previousRandomReader
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -66,6 +66,7 @@ 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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -89,6 +89,7 @@ go_test(
 | 
				
			|||||||
        "instrumented_services_test.go",
 | 
					        "instrumented_services_test.go",
 | 
				
			||||||
        "kuberuntime_container_linux_test.go",
 | 
					        "kuberuntime_container_linux_test.go",
 | 
				
			||||||
        "kuberuntime_container_test.go",
 | 
					        "kuberuntime_container_test.go",
 | 
				
			||||||
 | 
					        "kuberuntime_container_windows_test.go",
 | 
				
			||||||
        "kuberuntime_gc_test.go",
 | 
					        "kuberuntime_gc_test.go",
 | 
				
			||||||
        "kuberuntime_image_test.go",
 | 
					        "kuberuntime_image_test.go",
 | 
				
			||||||
        "kuberuntime_manager_test.go",
 | 
					        "kuberuntime_manager_test.go",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,8 @@ import (
 | 
				
			|||||||
	"github.com/docker/docker/pkg/sysinfo"
 | 
						"github.com/docker/docker/pkg/sysinfo"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"k8s.io/api/core/v1"
 | 
						"k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
 | 
						kubefeatures "k8s.io/kubernetes/pkg/features"
 | 
				
			||||||
	kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
 | 
						kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
 | 
				
			||||||
	runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
 | 
						runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/securitycontext"
 | 
						"k8s.io/kubernetes/pkg/securitycontext"
 | 
				
			||||||
@@ -35,6 +37,10 @@ func (m *kubeGenericRuntimeManager) applyPlatformSpecificContainerConfig(config
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WindowsGMSA) {
 | 
				
			||||||
 | 
							determineEffectiveSecurityContext(config, container, pod)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	config.Windows = windowsConfig
 | 
						config.Windows = windowsConfig
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -97,3 +103,40 @@ func (m *kubeGenericRuntimeManager) generateWindowsContainerConfig(container *v1
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return wc, nil
 | 
						return wc, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// GMSASpecContainerAnnotationKey is the container annotation where we store the contents of the GMSA credential spec to use.
 | 
				
			||||||
 | 
						GMSASpecContainerAnnotationKey = "container.alpha.windows.kubernetes.io/gmsa-credential-spec"
 | 
				
			||||||
 | 
						// gMSAContainerSpecPodAnnotationKeySuffix is the suffix of the pod annotation where the GMSA webhook admission controller
 | 
				
			||||||
 | 
						// stores the contents of the GMSA credential spec for a given container (the full annotation being the container's name
 | 
				
			||||||
 | 
						// with this suffix appended).
 | 
				
			||||||
 | 
						gMSAContainerSpecPodAnnotationKeySuffix = "." + GMSASpecContainerAnnotationKey
 | 
				
			||||||
 | 
						// gMSAPodSpecPodAnnotationKey is the pod annotation where the GMSA webhook admission controller stores the contents of the GMSA
 | 
				
			||||||
 | 
						// credential spec to use for containers that do not have their own specific GMSA cred spec set via a
 | 
				
			||||||
 | 
						// gMSAContainerSpecPodAnnotationKeySuffix annotation as explained above
 | 
				
			||||||
 | 
						gMSAPodSpecPodAnnotationKey = "pod.alpha.windows.kubernetes.io/gmsa-credential-spec"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// determineEffectiveSecurityContext determines the effective GMSA credential spec and, if any, copies it to the container's
 | 
				
			||||||
 | 
					// GMSASpecContainerAnnotationKey annotation.
 | 
				
			||||||
 | 
					func determineEffectiveSecurityContext(config *runtimeapi.ContainerConfig, container *v1.Container, pod *v1.Pod) {
 | 
				
			||||||
 | 
						var containerCredSpec string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						containerGMSAPodAnnotation := container.Name + gMSAContainerSpecPodAnnotationKeySuffix
 | 
				
			||||||
 | 
						if pod.Annotations[containerGMSAPodAnnotation] != "" {
 | 
				
			||||||
 | 
							containerCredSpec = pod.Annotations[containerGMSAPodAnnotation]
 | 
				
			||||||
 | 
						} else if pod.Annotations[gMSAPodSpecPodAnnotationKey] != "" {
 | 
				
			||||||
 | 
							containerCredSpec = pod.Annotations[gMSAPodSpecPodAnnotationKey]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if containerCredSpec != "" {
 | 
				
			||||||
 | 
							if config.Annotations == nil {
 | 
				
			||||||
 | 
								config.Annotations = make(map[string]string)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							config.Annotations[GMSASpecContainerAnnotationKey] = containerCredSpec
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// the annotation shouldn't be present, but let's err on the side of caution:
 | 
				
			||||||
 | 
							// it should only be set here and nowhere else
 | 
				
			||||||
 | 
							delete(config.Annotations, GMSASpecContainerAnnotationKey)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2019 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 kuberuntime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestDetermineEffectiveSecurityContext(t *testing.T) {
 | 
				
			||||||
 | 
						containerName := "container_name"
 | 
				
			||||||
 | 
						container := &corev1.Container{Name: containerName}
 | 
				
			||||||
 | 
						dummyCredSpec := "test cred spec contents"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						buildPod := func(annotations map[string]string) *corev1.Pod {
 | 
				
			||||||
 | 
							return &corev1.Pod{
 | 
				
			||||||
 | 
								ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
									Annotations: annotations,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("when there's a specific GMSA for that pod, and no pod-wide GMSA", func(t *testing.T) {
 | 
				
			||||||
 | 
							containerConfig := &runtimeapi.ContainerConfig{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pod := buildPod(map[string]string{
 | 
				
			||||||
 | 
								"container_name.container.alpha.windows.kubernetes.io/gmsa-credential-spec": dummyCredSpec,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							determineEffectiveSecurityContext(containerConfig, container, pod)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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) {
 | 
				
			||||||
 | 
							containerConfig := &runtimeapi.ContainerConfig{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pod := buildPod(map[string]string{
 | 
				
			||||||
 | 
								"container_name.container.alpha.windows.kubernetes.io/gmsa-credential-spec": dummyCredSpec,
 | 
				
			||||||
 | 
								"pod.alpha.windows.kubernetes.io/gmsa-credential-spec":                      "should be ignored",
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							determineEffectiveSecurityContext(containerConfig, container, pod)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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) {
 | 
				
			||||||
 | 
							containerConfig := &runtimeapi.ContainerConfig{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pod := buildPod(map[string]string{
 | 
				
			||||||
 | 
								"pod.alpha.windows.kubernetes.io/gmsa-credential-spec": dummyCredSpec,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							determineEffectiveSecurityContext(containerConfig, container, pod)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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) {
 | 
				
			||||||
 | 
							containerConfig := &runtimeapi.ContainerConfig{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							determineEffectiveSecurityContext(containerConfig, container, &corev1.Pod{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.Nil(t, containerConfig.Annotations)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user