Skip to content

Commit

Permalink
Kubelet updates for Windows HostProcess Containers
Browse files Browse the repository at this point in the history
  • Loading branch information
marosset authored and jsturtevant committed May 19, 2021
1 parent 51a02fd commit fd94032
Show file tree
Hide file tree
Showing 6 changed files with 340 additions and 4 deletions.
29 changes: 29 additions & 0 deletions pkg/kubelet/container/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"k8s.io/client-go/tools/record"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
sc "k8s.io/kubernetes/pkg/securitycontext"
hashutil "k8s.io/kubernetes/pkg/util/hash"
"k8s.io/kubernetes/third_party/forked/golang/expansion"
utilsnet "k8s.io/utils/net"
Expand Down Expand Up @@ -310,6 +311,34 @@ func HasPrivilegedContainer(pod *v1.Pod) bool {
return hasPrivileged
}

// HasWindowsHostProcessContainer returns true if any of the containers in a pod are HostProcess containers.
func HasWindowsHostProcessContainer(pod *v1.Pod) bool {
var hasHostProcess bool
podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *v1.Container, containerType podutil.ContainerType) bool {
if sc.HasWindowsHostProcessRequest(pod, c) {
hasHostProcess = true
return false
}
return true
})

return hasHostProcess
}

// AllContainersAreWindowsHostProcess returns true if all containres in a pod are HostProcess containers.
func AllContainersAreWindowsHostProcess(pod *v1.Pod) bool {
allHostProcess := true
podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *v1.Container, containerType podutil.ContainerType) bool {
if !sc.HasWindowsHostProcessRequest(pod, c) {
allHostProcess = false
return false
}
return true
})

return allHostProcess
}

// MakePortMappings creates internal port mapping from api port mapping.
func MakePortMappings(container *v1.Container) (ports []PortMapping) {
names := make(map[string]struct{})
Expand Down
15 changes: 12 additions & 3 deletions pkg/kubelet/kuberuntime/kuberuntime_container_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ limitations under the License.
package kuberuntime

import (
"fmt"
"runtime"

"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/features"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/securitycontext"

"k8s.io/klog/v2"
)

// applyPlatformSpecificContainerConfig applies platform specific configurations to runtimeapi.ContainerConfig.
Expand Down Expand Up @@ -122,5 +124,12 @@ func (m *kubeGenericRuntimeManager) generateWindowsContainerConfig(container *v1
wc.SecurityContext.RunAsUsername = *effectiveSc.WindowsOptions.RunAsUserName
}

if securitycontext.HasWindowsHostProcessRequest(pod, container) {
if !utilfeature.DefaultFeatureGate.Enabled(features.WindowsHostProcessContainers) {
return nil, fmt.Errorf("pod contains HostProcess containers but feature 'WindowsHostProcessContainers' is not enabled")
}
wc.SecurityContext.HostProcess = true
}

return wc, nil
}
58 changes: 58 additions & 0 deletions pkg/kubelet/kuberuntime/kuberuntime_sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import (

v1 "k8s.io/api/core/v1"
kubetypes "k8s.io/apimachinery/pkg/types"
utilfeature "k8s.io/apiserver/pkg/util/feature"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/features"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/kubelet/types"
"k8s.io/kubernetes/pkg/kubelet/util"
Expand Down Expand Up @@ -138,6 +140,14 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxConfig(pod *v1.Pod, attemp
}
podSandboxConfig.Linux = lc

if runtime.GOOS == "windows" {
wc, err := m.generatePodSandboxWindowsConfig(pod)
if err != nil {
return nil, err
}
podSandboxConfig.Windows = wc
}

return podSandboxConfig, nil
}

Expand Down Expand Up @@ -206,6 +216,54 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxLinuxConfig(pod *v1.Pod) (
return lc, nil
}

// generatePodSandboxWindowsConfig generates WindowsPodSandboxConfig from v1.Pod.
// On Windows this will get called in addition to LinuxPodSandboxConfig because not all relevant fields have been added to
// WindowsPodSandboxConfig at this time.
func (m *kubeGenericRuntimeManager) generatePodSandboxWindowsConfig(pod *v1.Pod) (*runtimeapi.WindowsPodSandboxConfig, error) {
wc := &runtimeapi.WindowsPodSandboxConfig{
SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{},
}

sc := pod.Spec.SecurityContext
if sc == nil || sc.WindowsOptions == nil {
return wc, nil
}

wo := sc.WindowsOptions
if wo.GMSACredentialSpec != nil {
wc.SecurityContext.CredentialSpec = *wo.GMSACredentialSpec
}

if wo.RunAsUserName != nil {
wc.SecurityContext.RunAsUsername = *wo.RunAsUserName
}

if kubecontainer.HasWindowsHostProcessContainer(pod) {
// Pods containing HostProcess containers should fail to schedule if feature is not
// enabled instead of trying to schedule containers as regular containers as stated in
// PRR review.
if !utilfeature.DefaultFeatureGate.Enabled(features.WindowsHostProcessContainers) {
return nil, fmt.Errorf("pod contains HostProcess containers but feature 'WindowsHostProcessContainers' is not enabled")
}

if wo.HostProcess != nil && !*wo.HostProcess {
return nil, fmt.Errorf("pod must not contain any HostProcess containers if Pod's WindowsOptions.HostProcess is set to false")
}
// At present Windows all containers in a Windows pod must be HostProcess containers
// and HostNetwork is required to be set.
if !kubecontainer.AllContainersAreWindowsHostProcess(pod) {
return nil, fmt.Errorf("pod must not contain both HostProcess and non-HostProcess containers")
}
if !kubecontainer.IsHostNetworkPod(pod) {
return nil, fmt.Errorf("hostNetwork is required if Pod contains HostProcess containers")
}

wc.SecurityContext.HostProcess = true
}

return wc, nil
}

// getKubeletSandboxes lists all (or just the running) sandboxes managed by kubelet.
func (m *kubeGenericRuntimeManager) getKubeletSandboxes(all bool) ([]*runtimeapi.PodSandbox, error) {
var filter *runtimeapi.PodSandboxFilter
Expand Down
188 changes: 188 additions & 0 deletions pkg/kubelet/kuberuntime/kuberuntime_sandbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package kuberuntime

import (
"fmt"
"os"
"path/filepath"
"testing"
Expand All @@ -25,7 +26,10 @@ import (
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/kubernetes/pkg/features"
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
"k8s.io/kubernetes/pkg/kubelet/runtimeclass"
rctest "k8s.io/kubernetes/pkg/kubelet/runtimeclass/testing"
Expand Down Expand Up @@ -172,3 +176,187 @@ func newSeccompPod(podFieldProfile, containerFieldProfile *v1.SeccompProfile, po
}
return pod
}

func TestGeneratePodSandboxWindowsConfig(t *testing.T) {
_, _, m, err := createTestRuntimeManager()
require.NoError(t, err)

const containerName = "container"
gmsaCreds := "gmsa-creds"
userName := "SYSTEM"
trueVar := true
falseVar := false

testCases := []struct {
name string
hostProcessFeatureEnabled bool
podSpec *v1.PodSpec
expectedWindowsConfig *runtimeapi.WindowsPodSandboxConfig
expectedError error
}{
{
name: "Empty PodSecurityContext",
hostProcessFeatureEnabled: false,
podSpec: &v1.PodSpec{
Containers: []v1.Container{{
Name: containerName,
}},
},
expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{},
},
expectedError: nil,
},
{
name: "GMSACredentialSpec in PodSecurityContext",
hostProcessFeatureEnabled: false,
podSpec: &v1.PodSpec{
SecurityContext: &v1.PodSecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
GMSACredentialSpec: &gmsaCreds,
},
},
Containers: []v1.Container{{
Name: containerName,
}},
},
expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{
CredentialSpec: "gmsa-creds",
},
},
expectedError: nil,
},
{
name: "RunAsUserName in PodSecurityContext",
hostProcessFeatureEnabled: false,
podSpec: &v1.PodSpec{
SecurityContext: &v1.PodSecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
RunAsUserName: &userName,
},
},
Containers: []v1.Container{{
Name: containerName,
}},
},
expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{
RunAsUsername: "SYSTEM",
},
},
expectedError: nil,
},
{
name: "Pod with HostProcess containers and feature gate disabled",
hostProcessFeatureEnabled: false,
podSpec: &v1.PodSpec{
SecurityContext: &v1.PodSecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
HostProcess: &trueVar,
},
},
Containers: []v1.Container{{
Name: containerName,
}},
},
expectedWindowsConfig: nil,
expectedError: fmt.Errorf("pod contains HostProcess containers but feature 'WindowsHostProcessContainers' is not enabled"),
},
{
name: "Pod with HostProcess containers and non-HostProcess containers",
hostProcessFeatureEnabled: true,
podSpec: &v1.PodSpec{
SecurityContext: &v1.PodSecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
HostProcess: &trueVar,
},
},
Containers: []v1.Container{{
Name: containerName,
}, {
Name: containerName,
SecurityContext: &v1.SecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
HostProcess: &falseVar,
},
},
}},
},
expectedWindowsConfig: nil,
expectedError: fmt.Errorf("pod must not contain both HostProcess and non-HostProcess containers"),
},
{
name: "Pod with HostProcess containers and HostNetwork not set",
hostProcessFeatureEnabled: true,
podSpec: &v1.PodSpec{
SecurityContext: &v1.PodSecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
HostProcess: &trueVar,
},
},
Containers: []v1.Container{{
Name: containerName,
}},
},
expectedWindowsConfig: nil,
expectedError: fmt.Errorf("hostNetwork is required if Pod contains HostProcess containers"),
},
{
name: "Pod with HostProcess containers and HostNetwork set",
hostProcessFeatureEnabled: true,
podSpec: &v1.PodSpec{
HostNetwork: true,
SecurityContext: &v1.PodSecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
HostProcess: &trueVar,
},
},
Containers: []v1.Container{{
Name: containerName,
}},
},
expectedWindowsConfig: &runtimeapi.WindowsPodSandboxConfig{
SecurityContext: &runtimeapi.WindowsSandboxSecurityContext{
HostProcess: true,
},
},
expectedError: nil,
},
{
name: "Pod's WindowsOptions.HostProcess set to false and pod has HostProcess containers",
hostProcessFeatureEnabled: true,
podSpec: &v1.PodSpec{
HostNetwork: true,
SecurityContext: &v1.PodSecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
HostProcess: &falseVar,
},
},
Containers: []v1.Container{{
Name: containerName,
SecurityContext: &v1.SecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
HostProcess: &trueVar,
},
},
}},
},
expectedWindowsConfig: nil,
expectedError: fmt.Errorf("pod must not contain any HostProcess containers if Pod's WindowsOptions.HostProcess is set to false"),
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.WindowsHostProcessContainers, testCase.hostProcessFeatureEnabled)()
pod := &v1.Pod{}
pod.Spec = *testCase.podSpec

wc, err := m.generatePodSandboxWindowsConfig(pod)

assert.Equal(t, wc, testCase.expectedWindowsConfig)
assert.Equal(t, err, testCase.expectedError)
})
}
}

0 comments on commit fd94032

Please sign in to comment.