vendor cleanup: remove unused,non-go and test files

This commit is contained in:
Madhu Rajanna
2019-01-16 00:05:52 +05:30
parent 52cf4aa902
commit b10ba188e7
15421 changed files with 17 additions and 4208853 deletions

View File

@ -1,29 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["doc.go"],
importpath = "k8s.io/kubernetes/pkg/security",
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/security/apparmor:all-srcs",
"//pkg/security/podsecuritypolicy:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -1,82 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"helpers.go",
"validate.go",
] + select({
"@io_bazel_rules_go//go/platform:android": [
"validate_disabled.go",
],
"@io_bazel_rules_go//go/platform:darwin": [
"validate_disabled.go",
],
"@io_bazel_rules_go//go/platform:dragonfly": [
"validate_disabled.go",
],
"@io_bazel_rules_go//go/platform:freebsd": [
"validate_disabled.go",
],
"@io_bazel_rules_go//go/platform:nacl": [
"validate_disabled.go",
],
"@io_bazel_rules_go//go/platform:netbsd": [
"validate_disabled.go",
],
"@io_bazel_rules_go//go/platform:openbsd": [
"validate_disabled.go",
],
"@io_bazel_rules_go//go/platform:plan9": [
"validate_disabled.go",
],
"@io_bazel_rules_go//go/platform:solaris": [
"validate_disabled.go",
],
"@io_bazel_rules_go//go/platform:windows": [
"validate_disabled.go",
],
"//conditions:default": [],
}),
importpath = "k8s.io/kubernetes/pkg/security/apparmor",
deps = [
"//pkg/features:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//pkg/util/file:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["validate_test.go"],
data = [
"testdata/profiles",
],
embed = [":go_default_library"],
deps = [
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,80 +0,0 @@
/*
Copyright 2016 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 apparmor
import (
"strings"
"k8s.io/api/core/v1"
)
// TODO: Move these values into the API package.
const (
// The prefix to an annotation key specifying a container profile.
ContainerAnnotationKeyPrefix = "container.apparmor.security.beta.kubernetes.io/"
// The annotation key specifying the default AppArmor profile.
DefaultProfileAnnotationKey = "apparmor.security.beta.kubernetes.io/defaultProfileName"
// The annotation key specifying the allowed AppArmor profiles.
AllowedProfilesAnnotationKey = "apparmor.security.beta.kubernetes.io/allowedProfileNames"
// The profile specifying the runtime default.
ProfileRuntimeDefault = "runtime/default"
// The prefix for specifying profiles loaded on the node.
ProfileNamePrefix = "localhost/"
// Unconfined profile
ProfileNameUnconfined = "unconfined"
)
// Checks whether app armor is required for pod to be run.
func isRequired(pod *v1.Pod) bool {
for key, value := range pod.Annotations {
if strings.HasPrefix(key, ContainerAnnotationKeyPrefix) {
return value != ProfileNameUnconfined
}
}
return false
}
// Returns the name of the profile to use with the container.
func GetProfileName(pod *v1.Pod, containerName string) string {
return GetProfileNameFromPodAnnotations(pod.Annotations, containerName)
}
// GetProfileNameFromPodAnnotations gets the name of the profile to use with container from
// pod annotations
func GetProfileNameFromPodAnnotations(annotations map[string]string, containerName string) string {
return annotations[ContainerAnnotationKeyPrefix+containerName]
}
// Sets the name of the profile to use with the container.
func SetProfileName(pod *v1.Pod, containerName, profileName string) error {
if pod.Annotations == nil {
pod.Annotations = map[string]string{}
}
pod.Annotations[ContainerAnnotationKeyPrefix+containerName] = profileName
return nil
}
// Sets the name of the profile to use with the container.
func SetProfileNameFromPodAnnotations(annotations map[string]string, containerName, profileName string) error {
if annotations == nil {
return nil
}
annotations[ContainerAnnotationKeyPrefix+containerName] = profileName
return nil
}

View File

@ -1,24 +0,0 @@
/usr/bin/evince-thumbnailer (enforce)
/usr/bin/evince-thumbnailer//sanitized_helper (enforce)
/usr/bin/evince-previewer (enforce)
/usr/bin/evince-previewer//sanitized_helper (enforce)
/usr/bin/evince (enforce)
/usr/bin/evince//sanitized_helper (enforce)
/usr/lib/telepathy/telepathy-ofono (enforce)
/usr/lib/telepathy/telepathy-* (enforce)
/usr/lib/telepathy/telepathy-*//sanitized_helper (enforce)
/usr/lib/telepathy/telepathy-*//pxgsettings (enforce)
/usr/lib/telepathy/mission-control-5 (enforce)
/usr/lib/lightdm/lightdm-guest-session (enforce)
/usr/lib/lightdm/lightdm-guest-session//chromium (enforce)
/usr/sbin/tcpdump (enforce)
docker-default (enforce)
/usr/sbin/cups-browsed (enforce)
/usr/sbin/cupsd (enforce)
/usr/lib/cups/backend/cups-pdf (enforce)
/usr/sbin/ntpd (enforce)
/usr/lib/connman/scripts/dhclient-script (enforce)
/usr/lib/NetworkManager/nm-dhcp-client.action (enforce)
/sbin/dhclient (enforce)
foo-container (complain)
foo://namespaced-profile (enforce)

View File

@ -1,229 +0,0 @@
/*
Copyright 2016 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 apparmor
import (
"bufio"
"errors"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"k8s.io/api/core/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/features"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
utilfile "k8s.io/kubernetes/pkg/util/file"
)
// Whether AppArmor should be disabled by default.
// Set to true if the wrong build tags are set (see validate_disabled.go).
var isDisabledBuild bool
// Interface for validating that a pod with with an AppArmor profile can be run by a Node.
type Validator interface {
Validate(pod *v1.Pod) error
ValidateHost() error
}
func NewValidator(runtime string) Validator {
if err := validateHost(runtime); err != nil {
return &validator{validateHostErr: err}
}
appArmorFS, err := getAppArmorFS()
if err != nil {
return &validator{
validateHostErr: fmt.Errorf("error finding AppArmor FS: %v", err),
}
}
return &validator{
appArmorFS: appArmorFS,
}
}
type validator struct {
validateHostErr error
appArmorFS string
}
func (v *validator) Validate(pod *v1.Pod) error {
if !isRequired(pod) {
return nil
}
if v.ValidateHost() != nil {
return v.validateHostErr
}
loadedProfiles, err := v.getLoadedProfiles()
if err != nil {
return fmt.Errorf("could not read loaded profiles: %v", err)
}
for _, container := range pod.Spec.InitContainers {
if err := validateProfile(GetProfileName(pod, container.Name), loadedProfiles); err != nil {
return err
}
}
for _, container := range pod.Spec.Containers {
if err := validateProfile(GetProfileName(pod, container.Name), loadedProfiles); err != nil {
return err
}
}
return nil
}
func (v *validator) ValidateHost() error {
return v.validateHostErr
}
// Verify that the host and runtime is capable of enforcing AppArmor profiles.
func validateHost(runtime string) error {
// Check feature-gates
if !utilfeature.DefaultFeatureGate.Enabled(features.AppArmor) {
return errors.New("AppArmor disabled by feature-gate")
}
// Check build support.
if isDisabledBuild {
return errors.New("Binary not compiled for linux")
}
// Check kernel support.
if !IsAppArmorEnabled() {
return errors.New("AppArmor is not enabled on the host")
}
// Check runtime support. Currently only Docker is supported.
if runtime != kubetypes.DockerContainerRuntime && runtime != kubetypes.RemoteContainerRuntime {
return fmt.Errorf("AppArmor is only enabled for 'docker' and 'remote' runtimes. Found: %q.", runtime)
}
return nil
}
// Verify that the profile is valid and loaded.
func validateProfile(profile string, loadedProfiles map[string]bool) error {
if err := ValidateProfileFormat(profile); err != nil {
return err
}
if strings.HasPrefix(profile, ProfileNamePrefix) {
profileName := strings.TrimPrefix(profile, ProfileNamePrefix)
if !loadedProfiles[profileName] {
return fmt.Errorf("profile %q is not loaded", profileName)
}
}
return nil
}
func ValidateProfileFormat(profile string) error {
if profile == "" || profile == ProfileRuntimeDefault || profile == ProfileNameUnconfined {
return nil
}
if !strings.HasPrefix(profile, ProfileNamePrefix) {
return fmt.Errorf("invalid AppArmor profile name: %q", profile)
}
return nil
}
func (v *validator) getLoadedProfiles() (map[string]bool, error) {
profilesPath := path.Join(v.appArmorFS, "profiles")
profilesFile, err := os.Open(profilesPath)
if err != nil {
return nil, fmt.Errorf("failed to open %s: %v", profilesPath, err)
}
defer profilesFile.Close()
profiles := map[string]bool{}
scanner := bufio.NewScanner(profilesFile)
for scanner.Scan() {
profileName := parseProfileName(scanner.Text())
if profileName == "" {
// Unknown line format; skip it.
continue
}
profiles[profileName] = true
}
return profiles, nil
}
// The profiles file is formatted with one profile per line, matching a form:
// namespace://profile-name (mode)
// profile-name (mode)
// Where mode is {enforce, complain, kill}. The "namespace://" is only included for namespaced
// profiles. For the purposes of Kubernetes, we consider the namespace part of the profile name.
func parseProfileName(profileLine string) string {
modeIndex := strings.IndexRune(profileLine, '(')
if modeIndex < 0 {
return ""
}
return strings.TrimSpace(profileLine[:modeIndex])
}
func getAppArmorFS() (string, error) {
mountsFile, err := os.Open("/proc/mounts")
if err != nil {
return "", fmt.Errorf("could not open /proc/mounts: %v", err)
}
defer mountsFile.Close()
scanner := bufio.NewScanner(mountsFile)
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) < 3 {
// Unknown line format; skip it.
continue
}
if fields[2] == "securityfs" {
appArmorFS := path.Join(fields[1], "apparmor")
if ok, err := utilfile.FileExists(appArmorFS); !ok {
msg := fmt.Sprintf("path %s does not exist", appArmorFS)
if err != nil {
return "", fmt.Errorf("%s: %v", msg, err)
} else {
return "", errors.New(msg)
}
} else {
return appArmorFS, nil
}
}
}
if err := scanner.Err(); err != nil {
return "", fmt.Errorf("error scanning mounts: %v", err)
}
return "", errors.New("securityfs not found")
}
// IsAppArmorEnabled returns true if apparmor is enabled for the host.
// This function is forked from
// https://github.com/opencontainers/runc/blob/1a81e9ab1f138c091fe5c86d0883f87716088527/libcontainer/apparmor/apparmor.go
// to avoid the libapparmor dependency.
func IsAppArmorEnabled() bool {
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
return err == nil && len(buf) > 1 && buf[0] == 'Y'
}
}
return false
}

View File

@ -1,24 +0,0 @@
// +build !linux
/*
Copyright 2016 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 apparmor
func init() {
// If Kubernetes was not built for linux, apparmor is always disabled.
isDisabledBuild = true
}

View File

@ -1,198 +0,0 @@
/*
Copyright 2016 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 apparmor
import (
"errors"
"fmt"
"testing"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/stretchr/testify/assert"
)
func TestGetAppArmorFS(t *testing.T) {
// This test only passes on systems running AppArmor with the default configuration.
// The test should be manually run if modifying the getAppArmorFS function.
t.Skip()
const expectedPath = "/sys/kernel/security/apparmor"
actualPath, err := getAppArmorFS()
assert.NoError(t, err)
assert.Equal(t, expectedPath, actualPath)
}
func TestValidateHost(t *testing.T) {
// This test only passes on systems running AppArmor with the default configuration.
// The test should be manually run if modifying the getAppArmorFS function.
t.Skip()
assert.NoError(t, validateHost("docker"))
assert.Error(t, validateHost("rkt"))
}
func TestValidateProfile(t *testing.T) {
loadedProfiles := map[string]bool{
"docker-default": true,
"foo-bar": true,
"baz": true,
"/usr/sbin/ntpd": true,
"/usr/lib/connman/scripts/dhclient-script": true,
"/usr/lib/NetworkManager/nm-dhcp-client.action": true,
"/usr/bin/evince-previewer//sanitized_helper": true,
}
tests := []struct {
profile string
expectValid bool
}{
{"", true},
{ProfileRuntimeDefault, true},
{ProfileNameUnconfined, true},
{"baz", false}, // Missing local prefix.
{ProfileNamePrefix + "/usr/sbin/ntpd", true},
{ProfileNamePrefix + "foo-bar", true},
{ProfileNamePrefix + "unloaded", false}, // Not loaded.
{ProfileNamePrefix + "", false},
}
for _, test := range tests {
err := validateProfile(test.profile, loadedProfiles)
if test.expectValid {
assert.NoError(t, err, "Profile %s should be valid", test.profile)
} else {
assert.Error(t, err, fmt.Sprintf("Profile %s should not be valid", test.profile))
}
}
}
func TestValidateBadHost(t *testing.T) {
hostErr := errors.New("expected host error")
v := &validator{
validateHostErr: hostErr,
}
tests := []struct {
profile string
expectValid bool
}{
{"", true},
{ProfileRuntimeDefault, false},
{ProfileNamePrefix + "docker-default", false},
}
for _, test := range tests {
err := v.Validate(getPodWithProfile(test.profile))
if test.expectValid {
assert.NoError(t, err, "Pod with profile %q should be valid", test.profile)
} else {
assert.Equal(t, hostErr, err, "Pod with profile %q should trigger a host validation error", test.profile)
}
}
}
func TestValidateValidHost(t *testing.T) {
v := &validator{
appArmorFS: "./testdata/",
}
tests := []struct {
profile string
expectValid bool
}{
{"", true},
{ProfileRuntimeDefault, true},
{ProfileNamePrefix + "docker-default", true},
{ProfileNamePrefix + "foo-container", true},
{ProfileNamePrefix + "/usr/sbin/ntpd", true},
{"docker-default", false},
{ProfileNamePrefix + "foo", false},
{ProfileNamePrefix + "", false},
}
for _, test := range tests {
err := v.Validate(getPodWithProfile(test.profile))
if test.expectValid {
assert.NoError(t, err, "Pod with profile %q should be valid", test.profile)
} else {
assert.Error(t, err, fmt.Sprintf("Pod with profile %q should trigger a validation error", test.profile))
}
}
// Test multi-container pod.
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
ContainerAnnotationKeyPrefix + "init": ProfileNamePrefix + "foo-container",
ContainerAnnotationKeyPrefix + "test1": ProfileRuntimeDefault,
ContainerAnnotationKeyPrefix + "test2": ProfileNamePrefix + "docker-default",
},
},
Spec: v1.PodSpec{
InitContainers: []v1.Container{
{Name: "init"},
},
Containers: []v1.Container{
{Name: "test1"},
{Name: "test2"},
{Name: "no-profile"},
},
},
}
assert.NoError(t, v.Validate(pod), "Multi-container pod should validate")
for k, val := range pod.Annotations {
pod.Annotations[k] = val + "-bad"
assert.Error(t, v.Validate(pod), fmt.Sprintf("Multi-container pod with invalid profile %s:%s", k, pod.Annotations[k]))
pod.Annotations[k] = val // Restore.
}
}
func TestParseProfileName(t *testing.T) {
tests := []struct{ line, expected string }{
{"foo://bar/baz (kill)", "foo://bar/baz"},
{"foo-bar (enforce)", "foo-bar"},
{"/usr/foo/bar/baz (complain)", "/usr/foo/bar/baz"},
}
for _, test := range tests {
name := parseProfileName(test.line)
assert.Equal(t, test.expected, name, "Parsing %s", test.line)
}
}
func getPodWithProfile(profile string) *v1.Pod {
annotations := map[string]string{
ContainerAnnotationKeyPrefix + "test": profile,
}
if profile == "" {
annotations = map[string]string{
"foo": "bar",
}
}
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: annotations,
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "test",
},
},
},
}
}

View File

@ -1,18 +0,0 @@
/*
Copyright 2016 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 security contains security apis and implementations.
package security // import "k8s.io/kubernetes/pkg/security"

View File

@ -1,75 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"factory.go",
"provider.go",
"types.go",
],
importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/security/podsecuritypolicy/apparmor:go_default_library",
"//pkg/security/podsecuritypolicy/capabilities:go_default_library",
"//pkg/security/podsecuritypolicy/group:go_default_library",
"//pkg/security/podsecuritypolicy/seccomp:go_default_library",
"//pkg/security/podsecuritypolicy/selinux:go_default_library",
"//pkg/security/podsecuritypolicy/sysctl:go_default_library",
"//pkg/security/podsecuritypolicy/user:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library",
"//pkg/securitycontext:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["provider_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/v1:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/security/apparmor:go_default_library",
"//pkg/security/podsecuritypolicy/seccomp:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library",
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/security/podsecuritypolicy/apparmor:all-srcs",
"//pkg/security/podsecuritypolicy/capabilities:all-srcs",
"//pkg/security/podsecuritypolicy/group:all-srcs",
"//pkg/security/podsecuritypolicy/seccomp:all-srcs",
"//pkg/security/podsecuritypolicy/selinux:all-srcs",
"//pkg/security/podsecuritypolicy/sysctl:all-srcs",
"//pkg/security/podsecuritypolicy/user:all-srcs",
"//pkg/security/podsecuritypolicy/util:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -1,13 +0,0 @@
approvers:
- deads2k
- ericchiang
- liggitt
- pweil-
- tallclair
reviewers:
- deads2k
- ericchiang
- liggitt
- php-coder
- pweil-
- tallclair

View File

@ -1,46 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["strategy.go"],
importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/security/apparmor:go_default_library",
"//pkg/util/maps:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["strategy_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/security/apparmor:go_default_library",
"//pkg/util/maps:go_default_library",
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,110 +0,0 @@
/*
Copyright 2016 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 apparmor
import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/util/maps"
)
// Strategy defines the interface for all AppArmor constraint strategies.
type Strategy interface {
// Generate updates the annotations based on constraint rules. The updates are applied to a copy
// of the annotations, and returned.
Generate(annotations map[string]string, container *api.Container) (map[string]string, error)
// Validate ensures that the specified values fall within the range of the strategy.
Validate(pod *api.Pod, container *api.Container) field.ErrorList
}
type strategy struct {
defaultProfile string
allowedProfiles map[string]bool
// For printing error messages (preserves order).
allowedProfilesString string
}
var _ Strategy = &strategy{}
// NewStrategy creates a new strategy that enforces AppArmor profile constraints.
func NewStrategy(pspAnnotations map[string]string) Strategy {
var allowedProfiles map[string]bool
if allowed, ok := pspAnnotations[apparmor.AllowedProfilesAnnotationKey]; ok {
profiles := strings.Split(allowed, ",")
allowedProfiles = make(map[string]bool, len(profiles))
for _, p := range profiles {
allowedProfiles[p] = true
}
}
return &strategy{
defaultProfile: pspAnnotations[apparmor.DefaultProfileAnnotationKey],
allowedProfiles: allowedProfiles,
allowedProfilesString: pspAnnotations[apparmor.AllowedProfilesAnnotationKey],
}
}
func (s *strategy) Generate(annotations map[string]string, container *api.Container) (map[string]string, error) {
copy := maps.CopySS(annotations)
if annotations[apparmor.ContainerAnnotationKeyPrefix+container.Name] != "" {
// Profile already set, nothing to do.
return copy, nil
}
if s.defaultProfile == "" {
// No default set.
return copy, nil
}
if copy == nil {
copy = map[string]string{}
}
// Add the default profile.
copy[apparmor.ContainerAnnotationKeyPrefix+container.Name] = s.defaultProfile
return copy, nil
}
func (s *strategy) Validate(pod *api.Pod, container *api.Container) field.ErrorList {
if s.allowedProfiles == nil {
// Unrestricted: allow all.
return nil
}
allErrs := field.ErrorList{}
fieldPath := field.NewPath("pod", "metadata", "annotations").Key(apparmor.ContainerAnnotationKeyPrefix + container.Name)
profile := apparmor.GetProfileNameFromPodAnnotations(pod.Annotations, container.Name)
if profile == "" {
if len(s.allowedProfiles) > 0 {
allErrs = append(allErrs, field.Forbidden(fieldPath, "AppArmor profile must be set"))
return allErrs
}
return nil
}
if !s.allowedProfiles[profile] {
msg := fmt.Sprintf("%s is not an allowed profile. Allowed values: %q", profile, s.allowedProfilesString)
allErrs = append(allErrs, field.Forbidden(fieldPath, msg))
}
return allErrs
}

View File

@ -1,174 +0,0 @@
/*
Copyright 2016 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 apparmor
import (
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/security/apparmor"
"k8s.io/kubernetes/pkg/util/maps"
)
const (
containerName = "test-c"
)
var (
withoutAppArmor = map[string]string{"foo": "bar"}
withDefault = map[string]string{
"foo": "bar",
apparmor.ContainerAnnotationKeyPrefix + containerName: apparmor.ProfileRuntimeDefault,
}
withLocal = map[string]string{
"foo": "bar",
apparmor.ContainerAnnotationKeyPrefix + containerName: apparmor.ProfileNamePrefix + "foo",
}
withDisallowed = map[string]string{
"foo": "bar",
apparmor.ContainerAnnotationKeyPrefix + containerName: apparmor.ProfileNamePrefix + "bad",
}
noAppArmor = map[string]string{"foo": "bar"}
unconstrainedWithDefault = map[string]string{
apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault,
}
constrained = map[string]string{
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + "," +
apparmor.ProfileNamePrefix + "foo",
}
constrainedWithDefault = map[string]string{
apparmor.DefaultProfileAnnotationKey: apparmor.ProfileRuntimeDefault,
apparmor.AllowedProfilesAnnotationKey: apparmor.ProfileRuntimeDefault + "," +
apparmor.ProfileNamePrefix + "foo",
}
container = api.Container{
Name: containerName,
Image: "busybox",
}
)
func TestGenerate(t *testing.T) {
type testcase struct {
pspAnnotations map[string]string
podAnnotations map[string]string
expected map[string]string
}
tests := []testcase{{
pspAnnotations: noAppArmor,
podAnnotations: withoutAppArmor,
expected: withoutAppArmor,
}, {
pspAnnotations: unconstrainedWithDefault,
podAnnotations: withoutAppArmor,
expected: withDefault,
}, {
pspAnnotations: constrained,
podAnnotations: withoutAppArmor,
expected: withoutAppArmor,
}, {
pspAnnotations: constrainedWithDefault,
podAnnotations: withoutAppArmor,
expected: withDefault,
}}
// Add unchanging permutations.
for _, podAnnotations := range []map[string]string{withDefault, withLocal} {
for _, pspAnnotations := range []map[string]string{noAppArmor, unconstrainedWithDefault, constrained, constrainedWithDefault} {
tests = append(tests, testcase{
pspAnnotations: pspAnnotations,
podAnnotations: podAnnotations,
expected: podAnnotations,
})
}
}
for i, test := range tests {
s := NewStrategy(test.pspAnnotations)
msgAndArgs := []interface{}{"testcase[%d]: %s", i, spew.Sdump(test)}
actual, err := s.Generate(test.podAnnotations, &container)
assert.NoError(t, err, msgAndArgs...)
assert.Equal(t, test.expected, actual, msgAndArgs...)
}
}
func TestValidate(t *testing.T) {
type testcase struct {
pspAnnotations map[string]string
podAnnotations map[string]string
expectErr bool
}
tests := []testcase{}
// Valid combinations
for _, podAnnotations := range []map[string]string{withDefault, withLocal} {
for _, pspAnnotations := range []map[string]string{noAppArmor, unconstrainedWithDefault, constrained, constrainedWithDefault} {
tests = append(tests, testcase{
pspAnnotations: pspAnnotations,
podAnnotations: podAnnotations,
expectErr: false,
})
}
}
for _, podAnnotations := range []map[string]string{withoutAppArmor, withDisallowed} {
for _, pspAnnotations := range []map[string]string{noAppArmor, unconstrainedWithDefault} {
tests = append(tests, testcase{
pspAnnotations: pspAnnotations,
podAnnotations: podAnnotations,
expectErr: false,
})
}
}
// Invalid combinations
for _, podAnnotations := range []map[string]string{withoutAppArmor, withDisallowed} {
for _, pspAnnotations := range []map[string]string{constrained, constrainedWithDefault} {
tests = append(tests, testcase{
pspAnnotations: pspAnnotations,
podAnnotations: podAnnotations,
expectErr: true,
})
}
}
for i, test := range tests {
s := NewStrategy(test.pspAnnotations)
pod, container := makeTestPod(test.podAnnotations)
msgAndArgs := []interface{}{"testcase[%d]: %s", i, spew.Sdump(test)}
errs := s.Validate(pod, container)
if test.expectErr {
assert.Len(t, errs, 1, msgAndArgs...)
} else {
assert.Len(t, errs, 0, msgAndArgs...)
}
}
}
func makeTestPod(annotations map[string]string) (*api.Pod, *api.Container) {
return &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pod",
Annotations: maps.CopySS(annotations),
},
Spec: api.PodSpec{
Containers: []api.Container{container},
},
}, &container
}

View File

@ -1,46 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"capabilities.go",
"doc.go",
"types.go",
],
importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/policy:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["capabilities_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/policy:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,152 +0,0 @@
/*
Copyright 2016 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 capabilities
import (
"fmt"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
)
// defaultCapabilities implements the Strategy interface
type defaultCapabilities struct {
defaultAddCapabilities []api.Capability
requiredDropCapabilities []api.Capability
allowedCaps []api.Capability
}
var _ Strategy = &defaultCapabilities{}
// NewDefaultCapabilities creates a new defaultCapabilities strategy that will provide defaults and validation
// based on the configured initial caps and allowed caps.
func NewDefaultCapabilities(defaultAddCapabilities, requiredDropCapabilities, allowedCaps []api.Capability) (Strategy, error) {
return &defaultCapabilities{
defaultAddCapabilities: defaultAddCapabilities,
requiredDropCapabilities: requiredDropCapabilities,
allowedCaps: allowedCaps,
}, nil
}
// Generate creates the capabilities based on policy rules. Generate will produce the following:
// 1. a capabilities.Add set containing all the required adds (unless the
// container specifically is dropping the cap) and container requested adds
// 2. a capabilities.Drop set containing all the required drops and container requested drops
//
// Returns the original container capabilities if no changes are required.
func (s *defaultCapabilities) Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error) {
defaultAdd := makeCapSet(s.defaultAddCapabilities)
requiredDrop := makeCapSet(s.requiredDropCapabilities)
containerAdd := sets.NewString()
containerDrop := sets.NewString()
var containerCapabilities *api.Capabilities
if container.SecurityContext != nil && container.SecurityContext.Capabilities != nil {
containerCapabilities = container.SecurityContext.Capabilities
containerAdd = makeCapSet(container.SecurityContext.Capabilities.Add)
containerDrop = makeCapSet(container.SecurityContext.Capabilities.Drop)
}
// remove any default adds that the container is specifically dropping
defaultAdd = defaultAdd.Difference(containerDrop)
combinedAdd := defaultAdd.Union(containerAdd)
combinedDrop := requiredDrop.Union(containerDrop)
// no changes? return the original capabilities
if (len(combinedAdd) == len(containerAdd)) && (len(combinedDrop) == len(containerDrop)) {
return containerCapabilities, nil
}
return &api.Capabilities{
Add: capabilityFromStringSlice(combinedAdd.List()),
Drop: capabilityFromStringSlice(combinedDrop.List()),
}, nil
}
// Validate ensures that the specified values fall within the range of the strategy.
func (s *defaultCapabilities) Validate(pod *api.Pod, container *api.Container, capabilities *api.Capabilities) field.ErrorList {
allErrs := field.ErrorList{}
if capabilities == nil {
// if container.SC.Caps is nil then nothing was defaulted by the strategy or requested by the pod author
// if there are no required caps on the strategy and nothing is requested on the pod
// then we can safely return here without further validation.
if len(s.defaultAddCapabilities) == 0 && len(s.requiredDropCapabilities) == 0 {
return allErrs
}
// container has no requested caps but we have required caps. We should have something in
// at least the drops on the container.
allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities"), capabilities,
"required capabilities are not set on the securityContext"))
return allErrs
}
allowedAdd := makeCapSet(s.allowedCaps)
allowAllCaps := allowedAdd.Has(string(policy.AllowAllCapabilities))
if allowAllCaps {
// skip validation against allowed/defaultAdd/requiredDrop because all capabilities are allowed by a wildcard
return allErrs
}
// validate that anything being added is in the default or allowed sets
defaultAdd := makeCapSet(s.defaultAddCapabilities)
for _, cap := range capabilities.Add {
sCap := string(cap)
if !defaultAdd.Has(sCap) && !allowedAdd.Has(sCap) {
allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities", "add"), sCap, "capability may not be added"))
}
}
// validate that anything that is required to be dropped is in the drop set
containerDrops := makeCapSet(capabilities.Drop)
for _, requiredDrop := range s.requiredDropCapabilities {
sDrop := string(requiredDrop)
if !containerDrops.Has(sDrop) {
allErrs = append(allErrs, field.Invalid(field.NewPath("capabilities", "drop"), capabilities.Drop,
fmt.Sprintf("%s is required to be dropped but was not found", sDrop)))
}
}
return allErrs
}
// capabilityFromStringSlice creates a capability slice from a string slice.
func capabilityFromStringSlice(slice []string) []api.Capability {
if len(slice) == 0 {
return nil
}
caps := []api.Capability{}
for _, c := range slice {
caps = append(caps, api.Capability(c))
}
return caps
}
// makeCapSet makes a string set from capabilities.
func makeCapSet(caps []api.Capability) sets.String {
s := sets.NewString()
for _, c := range caps {
s.Insert(string(c))
}
return s
}

View File

@ -1,410 +0,0 @@
/*
Copyright 2016 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 capabilities
import (
"reflect"
"testing"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
)
func TestGenerateAdds(t *testing.T) {
tests := map[string]struct {
defaultAddCaps []api.Capability
containerCaps *api.Capabilities
expectedCaps *api.Capabilities
}{
"no required, no container requests": {},
"no required, no container requests, non-nil": {
containerCaps: &api.Capabilities{},
expectedCaps: &api.Capabilities{},
},
"required, no container requests": {
defaultAddCaps: []api.Capability{"foo"},
expectedCaps: &api.Capabilities{
Add: []api.Capability{"foo"},
},
},
"required, container requests add required": {
defaultAddCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"foo"},
},
expectedCaps: &api.Capabilities{
Add: []api.Capability{"foo"},
},
},
"multiple required, container requests add required": {
defaultAddCaps: []api.Capability{"foo", "bar", "baz"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"foo"},
},
expectedCaps: &api.Capabilities{
Add: []api.Capability{"bar", "baz", "foo"},
},
},
"required, container requests add non-required": {
defaultAddCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"bar"},
},
expectedCaps: &api.Capabilities{
Add: []api.Capability{"bar", "foo"},
},
},
"generation does not mutate unnecessarily": {
defaultAddCaps: []api.Capability{"foo", "bar"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"foo", "foo", "bar", "baz"},
},
expectedCaps: &api.Capabilities{
Add: []api.Capability{"foo", "foo", "bar", "baz"},
},
},
"generation dedupes": {
defaultAddCaps: []api.Capability{"foo", "bar"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"foo", "baz"},
},
expectedCaps: &api.Capabilities{
Add: []api.Capability{"bar", "baz", "foo"},
},
},
"generation is case sensitive - will not dedupe": {
defaultAddCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"FOO"},
},
expectedCaps: &api.Capabilities{
Add: []api.Capability{"FOO", "foo"},
},
},
}
for k, v := range tests {
container := &api.Container{
SecurityContext: &api.SecurityContext{
Capabilities: v.containerCaps,
},
}
strategy, err := NewDefaultCapabilities(v.defaultAddCaps, nil, nil)
if err != nil {
t.Errorf("%s failed: %v", k, err)
continue
}
generatedCaps, err := strategy.Generate(nil, container)
if err != nil {
t.Errorf("%s failed generating: %v", k, err)
continue
}
if v.expectedCaps == nil && generatedCaps != nil {
t.Errorf("%s expected nil caps to be generated but got %v", k, generatedCaps)
continue
}
if !reflect.DeepEqual(v.expectedCaps, generatedCaps) {
t.Errorf("%s did not generate correctly. Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps)
}
}
}
func TestGenerateDrops(t *testing.T) {
tests := map[string]struct {
defaultAddCaps []api.Capability
requiredDropCaps []api.Capability
containerCaps *api.Capabilities
expectedCaps *api.Capabilities
}{
"no required, no container requests": {
expectedCaps: nil,
},
"no required, no container requests, non-nil": {
containerCaps: &api.Capabilities{},
expectedCaps: &api.Capabilities{},
},
"required drops are defaulted": {
requiredDropCaps: []api.Capability{"foo"},
expectedCaps: &api.Capabilities{
Drop: []api.Capability{"foo"},
},
},
"required drops are defaulted when making container requests": {
requiredDropCaps: []api.Capability{"baz"},
containerCaps: &api.Capabilities{
Drop: []api.Capability{"foo", "bar"},
},
expectedCaps: &api.Capabilities{
Drop: []api.Capability{"bar", "baz", "foo"},
},
},
"required drops do not mutate unnecessarily": {
requiredDropCaps: []api.Capability{"baz"},
containerCaps: &api.Capabilities{
Drop: []api.Capability{"foo", "bar", "baz"},
},
expectedCaps: &api.Capabilities{
Drop: []api.Capability{"foo", "bar", "baz"},
},
},
"can drop a required add": {
defaultAddCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Drop: []api.Capability{"foo"},
},
expectedCaps: &api.Capabilities{
Drop: []api.Capability{"foo"},
},
},
"can drop non-required add": {
defaultAddCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Drop: []api.Capability{"bar"},
},
expectedCaps: &api.Capabilities{
Add: []api.Capability{"foo"},
Drop: []api.Capability{"bar"},
},
},
"defaulting adds and drops, dropping a required add": {
defaultAddCaps: []api.Capability{"foo", "bar", "baz"},
requiredDropCaps: []api.Capability{"abc"},
containerCaps: &api.Capabilities{
Drop: []api.Capability{"foo"},
},
expectedCaps: &api.Capabilities{
Add: []api.Capability{"bar", "baz"},
Drop: []api.Capability{"abc", "foo"},
},
},
"generation dedupes": {
requiredDropCaps: []api.Capability{"baz", "foo"},
containerCaps: &api.Capabilities{
Drop: []api.Capability{"bar", "foo"},
},
expectedCaps: &api.Capabilities{
Drop: []api.Capability{"bar", "baz", "foo"},
},
},
"generation is case sensitive - will not dedupe": {
requiredDropCaps: []api.Capability{"bar"},
containerCaps: &api.Capabilities{
Drop: []api.Capability{"BAR"},
},
expectedCaps: &api.Capabilities{
Drop: []api.Capability{"BAR", "bar"},
},
},
}
for k, v := range tests {
container := &api.Container{
SecurityContext: &api.SecurityContext{
Capabilities: v.containerCaps,
},
}
strategy, err := NewDefaultCapabilities(v.defaultAddCaps, v.requiredDropCaps, nil)
if err != nil {
t.Errorf("%s failed: %v", k, err)
continue
}
generatedCaps, err := strategy.Generate(nil, container)
if err != nil {
t.Errorf("%s failed generating: %v", k, err)
continue
}
if v.expectedCaps == nil && generatedCaps != nil {
t.Errorf("%s expected nil caps to be generated but got %#v", k, generatedCaps)
continue
}
if !reflect.DeepEqual(v.expectedCaps, generatedCaps) {
t.Errorf("%s did not generate correctly. Expected: %#v, Actual: %#v", k, v.expectedCaps, generatedCaps)
}
}
}
func TestValidateAdds(t *testing.T) {
tests := map[string]struct {
defaultAddCaps []api.Capability
allowedCaps []api.Capability
containerCaps *api.Capabilities
expectedError string
}{
// no container requests
"no required, no allowed, no container requests": {},
"no required, allowed, no container requests": {
allowedCaps: []api.Capability{"foo"},
},
"required, no allowed, no container requests": {
defaultAddCaps: []api.Capability{"foo"},
expectedError: `capabilities: Invalid value: "null": required capabilities are not set on the securityContext`,
},
// container requests match required
"required, no allowed, container requests valid": {
defaultAddCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"foo"},
},
},
"required, no allowed, container requests invalid": {
defaultAddCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"bar"},
},
expectedError: `capabilities.add: Invalid value: "bar": capability may not be added`,
},
// container requests match allowed
"no required, allowed, container requests valid": {
allowedCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"foo"},
},
},
"no required, all allowed, container requests valid": {
allowedCaps: []api.Capability{policy.AllowAllCapabilities},
containerCaps: &api.Capabilities{
Add: []api.Capability{"foo"},
},
},
"no required, allowed, container requests invalid": {
allowedCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"bar"},
},
expectedError: `capabilities.add: Invalid value: "bar": capability may not be added`,
},
// required and allowed
"required, allowed, container requests valid required": {
defaultAddCaps: []api.Capability{"foo"},
allowedCaps: []api.Capability{"bar"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"foo"},
},
},
"required, allowed, container requests valid allowed": {
defaultAddCaps: []api.Capability{"foo"},
allowedCaps: []api.Capability{"bar"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"bar"},
},
},
"required, allowed, container requests invalid": {
defaultAddCaps: []api.Capability{"foo"},
allowedCaps: []api.Capability{"bar"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"baz"},
},
expectedError: `capabilities.add: Invalid value: "baz": capability may not be added`,
},
"validation is case sensitive": {
defaultAddCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Add: []api.Capability{"FOO"},
},
expectedError: `capabilities.add: Invalid value: "FOO": capability may not be added`,
},
}
for k, v := range tests {
strategy, err := NewDefaultCapabilities(v.defaultAddCaps, nil, v.allowedCaps)
if err != nil {
t.Errorf("%s failed: %v", k, err)
continue
}
errs := strategy.Validate(nil, nil, v.containerCaps)
if v.expectedError == "" && len(errs) > 0 {
t.Errorf("%s should have passed but had errors %v", k, errs)
continue
}
if v.expectedError != "" && len(errs) == 0 {
t.Errorf("%s should have failed but received no errors", k)
continue
}
if len(errs) == 1 && errs[0].Error() != v.expectedError {
t.Errorf("%s should have failed with %v but received %v", k, v.expectedError, errs[0])
continue
}
if len(errs) > 1 {
t.Errorf("%s should have failed with at most one error, but received %v: %v", k, len(errs), errs)
}
}
}
func TestValidateDrops(t *testing.T) {
tests := map[string]struct {
requiredDropCaps []api.Capability
containerCaps *api.Capabilities
expectedError string
}{
// no container requests
"no required, no container requests": {},
"required, no container requests": {
requiredDropCaps: []api.Capability{"foo"},
expectedError: `capabilities: Invalid value: "null": required capabilities are not set on the securityContext`,
},
// container requests match required
"required, container requests valid": {
requiredDropCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Drop: []api.Capability{"foo"},
},
},
"required, container requests invalid": {
requiredDropCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Drop: []api.Capability{"bar"},
},
expectedError: `capabilities.drop: Invalid value: []core.Capability{"bar"}: foo is required to be dropped but was not found`,
},
"validation is case sensitive": {
requiredDropCaps: []api.Capability{"foo"},
containerCaps: &api.Capabilities{
Drop: []api.Capability{"FOO"},
},
expectedError: `capabilities.drop: Invalid value: []core.Capability{"FOO"}: foo is required to be dropped but was not found`,
},
}
for k, v := range tests {
strategy, err := NewDefaultCapabilities(nil, v.requiredDropCaps, nil)
if err != nil {
t.Errorf("%s failed: %v", k, err)
continue
}
errs := strategy.Validate(nil, nil, v.containerCaps)
if v.expectedError == "" && len(errs) > 0 {
t.Errorf("%s should have passed but had errors %v", k, errs)
continue
}
if v.expectedError != "" && len(errs) == 0 {
t.Errorf("%s should have failed but received no errors", k)
continue
}
if len(errs) == 1 && errs[0].Error() != v.expectedError {
t.Errorf("%s should have failed with %v but received %v", k, v.expectedError, errs[0])
continue
}
if len(errs) > 1 {
t.Errorf("%s should have failed with at most one error, but received %v: %v", k, len(errs), errs)
}
}
}

View File

@ -1,19 +0,0 @@
/*
Copyright 2016 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 capabilities contains code for validating and defaulting a pod's
// kernel capabilities according to a security policy.
package capabilities

View File

@ -1,30 +0,0 @@
/*
Copyright 2016 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 capabilities
import (
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
)
// Strategy defines the interface for all cap constraint strategies.
type Strategy interface {
// Generate creates the capabilities based on policy rules.
Generate(pod *api.Pod, container *api.Container) (*api.Capabilities, error)
// Validate ensures that the specified values fall within the range of the strategy.
Validate(pod *api.Pod, container *api.Container, capabilities *api.Capabilities) field.ErrorList
}

View File

@ -1,20 +0,0 @@
/*
Copyright 2016 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 podsecuritypolicy contains code for validating and defaulting the
// security context of a pod and its containers according to a security
// policy.
package podsecuritypolicy

View File

@ -1,168 +0,0 @@
/*
Copyright 2016 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 podsecuritypolicy
import (
"fmt"
"k8s.io/apimachinery/pkg/util/errors"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/user"
)
type simpleStrategyFactory struct{}
var _ StrategyFactory = &simpleStrategyFactory{}
func NewSimpleStrategyFactory() StrategyFactory {
return &simpleStrategyFactory{}
}
func (f *simpleStrategyFactory) CreateStrategies(psp *policy.PodSecurityPolicy, namespace string) (*ProviderStrategies, error) {
errs := []error{}
userStrat, err := createUserStrategy(&psp.Spec.RunAsUser)
if err != nil {
errs = append(errs, err)
}
seLinuxStrat, err := createSELinuxStrategy(&psp.Spec.SELinux)
if err != nil {
errs = append(errs, err)
}
appArmorStrat, err := createAppArmorStrategy(psp)
if err != nil {
errs = append(errs, err)
}
seccompStrat, err := createSeccompStrategy(psp)
if err != nil {
errs = append(errs, err)
}
fsGroupStrat, err := createFSGroupStrategy(&psp.Spec.FSGroup)
if err != nil {
errs = append(errs, err)
}
supGroupStrat, err := createSupplementalGroupStrategy(&psp.Spec.SupplementalGroups)
if err != nil {
errs = append(errs, err)
}
capStrat, err := createCapabilitiesStrategy(psp.Spec.DefaultAddCapabilities, psp.Spec.RequiredDropCapabilities, psp.Spec.AllowedCapabilities)
if err != nil {
errs = append(errs, err)
}
sysctlsStrat := createSysctlsStrategy(sysctl.SafeSysctlWhitelist(), psp.Spec.AllowedUnsafeSysctls, psp.Spec.ForbiddenSysctls)
if len(errs) > 0 {
return nil, errors.NewAggregate(errs)
}
strategies := &ProviderStrategies{
RunAsUserStrategy: userStrat,
SELinuxStrategy: seLinuxStrat,
AppArmorStrategy: appArmorStrat,
FSGroupStrategy: fsGroupStrat,
SupplementalGroupStrategy: supGroupStrat,
CapabilitiesStrategy: capStrat,
SeccompStrategy: seccompStrat,
SysctlsStrategy: sysctlsStrat,
}
return strategies, nil
}
// createUserStrategy creates a new user strategy.
func createUserStrategy(opts *policy.RunAsUserStrategyOptions) (user.RunAsUserStrategy, error) {
switch opts.Rule {
case policy.RunAsUserStrategyMustRunAs:
return user.NewMustRunAs(opts)
case policy.RunAsUserStrategyMustRunAsNonRoot:
return user.NewRunAsNonRoot(opts)
case policy.RunAsUserStrategyRunAsAny:
return user.NewRunAsAny(opts)
default:
return nil, fmt.Errorf("Unrecognized RunAsUser strategy type %s", opts.Rule)
}
}
// createSELinuxStrategy creates a new selinux strategy.
func createSELinuxStrategy(opts *policy.SELinuxStrategyOptions) (selinux.SELinuxStrategy, error) {
switch opts.Rule {
case policy.SELinuxStrategyMustRunAs:
return selinux.NewMustRunAs(opts)
case policy.SELinuxStrategyRunAsAny:
return selinux.NewRunAsAny(opts)
default:
return nil, fmt.Errorf("Unrecognized SELinuxContext strategy type %s", opts.Rule)
}
}
// createAppArmorStrategy creates a new AppArmor strategy.
func createAppArmorStrategy(psp *policy.PodSecurityPolicy) (apparmor.Strategy, error) {
return apparmor.NewStrategy(psp.Annotations), nil
}
// createSeccompStrategy creates a new seccomp strategy.
func createSeccompStrategy(psp *policy.PodSecurityPolicy) (seccomp.Strategy, error) {
return seccomp.NewStrategy(psp.Annotations), nil
}
// createFSGroupStrategy creates a new fsgroup strategy
func createFSGroupStrategy(opts *policy.FSGroupStrategyOptions) (group.GroupStrategy, error) {
switch opts.Rule {
case policy.FSGroupStrategyRunAsAny:
return group.NewRunAsAny()
case policy.FSGroupStrategyMustRunAs:
return group.NewMustRunAs(opts.Ranges, fsGroupField)
default:
return nil, fmt.Errorf("Unrecognized FSGroup strategy type %s", opts.Rule)
}
}
// createSupplementalGroupStrategy creates a new supplemental group strategy
func createSupplementalGroupStrategy(opts *policy.SupplementalGroupsStrategyOptions) (group.GroupStrategy, error) {
switch opts.Rule {
case policy.SupplementalGroupsStrategyRunAsAny:
return group.NewRunAsAny()
case policy.SupplementalGroupsStrategyMustRunAs:
return group.NewMustRunAs(opts.Ranges, supplementalGroupsField)
default:
return nil, fmt.Errorf("Unrecognized SupplementalGroups strategy type %s", opts.Rule)
}
}
// createCapabilitiesStrategy creates a new capabilities strategy.
func createCapabilitiesStrategy(defaultAddCaps, requiredDropCaps, allowedCaps []api.Capability) (capabilities.Strategy, error) {
return capabilities.NewDefaultCapabilities(defaultAddCaps, requiredDropCaps, allowedCaps)
}
// createSysctlsStrategy creates a new sysctls strategy.
func createSysctlsStrategy(safeWhitelist, allowedUnsafeSysctls, forbiddenSysctls []string) sysctl.SysctlsStrategy {
return sysctl.NewMustMatchPatterns(safeWhitelist, allowedUnsafeSysctls, forbiddenSysctls)
}

View File

@ -1,47 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"mustrunas.go",
"runasany.go",
"types.go",
],
importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/group",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"mustrunas_test.go",
"runasany_test.go",
],
embed = [":go_default_library"],
deps = ["//pkg/apis/policy:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,19 +0,0 @@
/*
Copyright 2016 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 group contains code for validating and defaulting the FSGroup and
// supplemental groups of a pod according to a security policy.
package group

View File

@ -1,88 +0,0 @@
/*
Copyright 2016 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 group
import (
"fmt"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
)
// mustRunAs implements the GroupStrategy interface
type mustRunAs struct {
ranges []policy.IDRange
field string
}
var _ GroupStrategy = &mustRunAs{}
// NewMustRunAs provides a new MustRunAs strategy based on ranges.
func NewMustRunAs(ranges []policy.IDRange, field string) (GroupStrategy, error) {
if len(ranges) == 0 {
return nil, fmt.Errorf("ranges must be supplied for MustRunAs")
}
return &mustRunAs{
ranges: ranges,
field: field,
}, nil
}
// Generate creates the group based on policy rules. By default this returns the first group of the
// first range (min val).
func (s *mustRunAs) Generate(_ *api.Pod) ([]int64, error) {
return []int64{s.ranges[0].Min}, nil
}
// Generate a single value to be applied. This is used for FSGroup. This strategy will return
// the first group of the first range (min val).
func (s *mustRunAs) GenerateSingle(_ *api.Pod) (*int64, error) {
single := new(int64)
*single = s.ranges[0].Min
return single, nil
}
// Validate ensures that the specified values fall within the range of the strategy.
// Groups are passed in here to allow this strategy to support multiple group fields (fsgroup and
// supplemental groups).
func (s *mustRunAs) Validate(_ *api.Pod, groups []int64) field.ErrorList {
allErrs := field.ErrorList{}
if len(groups) == 0 && len(s.ranges) > 0 {
allErrs = append(allErrs, field.Invalid(field.NewPath(s.field), groups, "unable to validate empty groups against required ranges"))
}
for _, group := range groups {
if !s.isGroupValid(group) {
detail := fmt.Sprintf("group %d must be in the ranges: %v", group, s.ranges)
allErrs = append(allErrs, field.Invalid(field.NewPath(s.field), groups, detail))
}
}
return allErrs
}
func (s *mustRunAs) isGroupValid(group int64) bool {
for _, rng := range s.ranges {
if psputil.GroupFallsInRange(group, rng) {
return true
}
}
return false
}

View File

@ -1,179 +0,0 @@
/*
Copyright 2014 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 group
import (
"strings"
"testing"
"k8s.io/kubernetes/pkg/apis/policy"
)
func TestMustRunAsOptions(t *testing.T) {
tests := map[string]struct {
ranges []policy.IDRange
pass bool
}{
"empty": {
ranges: []policy.IDRange{},
},
"ranges": {
ranges: []policy.IDRange{
{Min: 1, Max: 1},
},
pass: true,
},
}
for k, v := range tests {
_, err := NewMustRunAs(v.ranges, "")
if v.pass && err != nil {
t.Errorf("error creating strategy for %s: %v", k, err)
}
if !v.pass && err == nil {
t.Errorf("expected error for %s but got none", k)
}
}
}
func TestGenerate(t *testing.T) {
tests := map[string]struct {
ranges []policy.IDRange
expected []int64
}{
"multi value": {
ranges: []policy.IDRange{
{Min: 1, Max: 2},
},
expected: []int64{1},
},
"single value": {
ranges: []policy.IDRange{
{Min: 1, Max: 1},
},
expected: []int64{1},
},
"multi range": {
ranges: []policy.IDRange{
{Min: 1, Max: 1},
{Min: 2, Max: 500},
},
expected: []int64{1},
},
}
for k, v := range tests {
s, err := NewMustRunAs(v.ranges, "")
if err != nil {
t.Errorf("error creating strategy for %s: %v", k, err)
}
actual, err := s.Generate(nil)
if err != nil {
t.Errorf("unexpected error for %s: %v", k, err)
}
if len(actual) != len(v.expected) {
t.Errorf("unexpected generated values. Expected %v, got %v", v.expected, actual)
continue
}
if len(actual) > 0 && len(v.expected) > 0 {
if actual[0] != v.expected[0] {
t.Errorf("unexpected generated values. Expected %v, got %v", v.expected, actual)
}
}
single, err := s.GenerateSingle(nil)
if err != nil {
t.Errorf("unexpected error for %s: %v", k, err)
}
if single == nil {
t.Errorf("unexpected nil generated value for %s: %v", k, single)
}
if *single != v.expected[0] {
t.Errorf("unexpected generated single value. Expected %v, got %v", v.expected, actual)
}
}
}
func TestValidate(t *testing.T) {
tests := map[string]struct {
ranges []policy.IDRange
groups []int64
expectedError string
}{
"nil security context": {
ranges: []policy.IDRange{
{Min: 1, Max: 3},
},
expectedError: "unable to validate empty groups against required ranges",
},
"empty groups": {
ranges: []policy.IDRange{
{Min: 1, Max: 3},
},
expectedError: "unable to validate empty groups against required ranges",
},
"not in range": {
groups: []int64{5},
ranges: []policy.IDRange{
{Min: 1, Max: 3},
{Min: 4, Max: 4},
},
expectedError: "group 5 must be in the ranges: [{1 3} {4 4}]",
},
"in range 1": {
groups: []int64{2},
ranges: []policy.IDRange{
{Min: 1, Max: 3},
},
},
"in range boundary min": {
groups: []int64{1},
ranges: []policy.IDRange{
{Min: 1, Max: 3},
},
},
"in range boundary max": {
groups: []int64{3},
ranges: []policy.IDRange{
{Min: 1, Max: 3},
},
},
"singular range": {
groups: []int64{4},
ranges: []policy.IDRange{
{Min: 4, Max: 4},
},
},
}
for k, v := range tests {
s, err := NewMustRunAs(v.ranges, "")
if err != nil {
t.Errorf("error creating strategy for %s: %v", k, err)
}
errs := s.Validate(nil, v.groups)
if v.expectedError == "" && len(errs) > 0 {
t.Errorf("unexpected errors for %s: %v", k, errs)
}
if v.expectedError != "" && len(errs) == 0 {
t.Errorf("expected errors for %s but got: %v", k, errs)
}
if v.expectedError != "" && len(errs) > 0 && !strings.Contains(errs[0].Error(), v.expectedError) {
t.Errorf("expected error for %s: %v, but got: %v", k, v.expectedError, errs[0])
}
}
}

View File

@ -1,49 +0,0 @@
/*
Copyright 2016 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 group
import (
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
)
// runAsAny implements the GroupStrategy interface.
type runAsAny struct {
}
var _ GroupStrategy = &runAsAny{}
// NewRunAsAny provides a new RunAsAny strategy.
func NewRunAsAny() (GroupStrategy, error) {
return &runAsAny{}, nil
}
// Generate creates the group based on policy rules. This strategy returns an empty slice.
func (s *runAsAny) Generate(_ *api.Pod) ([]int64, error) {
return nil, nil
}
// Generate a single value to be applied. This is used for FSGroup. This strategy returns nil.
func (s *runAsAny) GenerateSingle(_ *api.Pod) (*int64, error) {
return nil, nil
}
// Validate ensures that the specified values fall within the range of the strategy.
func (s *runAsAny) Validate(_ *api.Pod, groups []int64) field.ErrorList {
return field.ErrorList{}
}

View File

@ -1,60 +0,0 @@
/*
Copyright 2016 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 group
import (
"testing"
)
func TestRunAsAnyGenerate(t *testing.T) {
s, err := NewRunAsAny()
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
}
groups, err := s.Generate(nil)
if len(groups) > 0 {
t.Errorf("expected empty but got %v", groups)
}
if err != nil {
t.Errorf("unexpected error generating groups: %v", err)
}
}
func TestRunAsAnyGenerateSingle(t *testing.T) {
s, err := NewRunAsAny()
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
}
group, err := s.GenerateSingle(nil)
if group != nil {
t.Errorf("expected empty but got %v", group)
}
if err != nil {
t.Errorf("unexpected error generating groups: %v", err)
}
}
func TestRunAsAnyValidte(t *testing.T) {
s, err := NewRunAsAny()
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
}
errs := s.Validate(nil, nil)
if len(errs) != 0 {
t.Errorf("unexpected errors: %v", errs)
}
}

View File

@ -1,35 +0,0 @@
/*
Copyright 2016 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 group
import (
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
)
// GroupStrategy defines the interface for all group constraint strategies.
type GroupStrategy interface {
// Generate creates the group based on policy rules. The underlying implementation can
// decide whether it will return a full range of values or a subset of values from the
// configured ranges.
Generate(pod *api.Pod) ([]int64, error)
// Generate a single value to be applied. The underlying implementation decides which
// value to return if configured with multiple ranges. This is used for FSGroup.
GenerateSingle(pod *api.Pod) (*int64, error)
// Validate ensures that the specified values fall within the range of the strategy.
Validate(pod *api.Pod, groups []int64) field.ErrorList
}

View File

@ -1,373 +0,0 @@
/*
Copyright 2016 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 podsecuritypolicy
import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
"k8s.io/kubernetes/pkg/securitycontext"
)
// used to pass in the field being validated for reusable group strategies so they
// can create informative error messages.
const (
fsGroupField = "fsGroup"
supplementalGroupsField = "supplementalGroups"
)
// simpleProvider is the default implementation of Provider.
type simpleProvider struct {
psp *policy.PodSecurityPolicy
strategies *ProviderStrategies
}
// ensure we implement the interface correctly.
var _ Provider = &simpleProvider{}
// NewSimpleProvider creates a new Provider instance.
func NewSimpleProvider(psp *policy.PodSecurityPolicy, namespace string, strategyFactory StrategyFactory) (Provider, error) {
if psp == nil {
return nil, fmt.Errorf("NewSimpleProvider requires a PodSecurityPolicy")
}
if strategyFactory == nil {
return nil, fmt.Errorf("NewSimpleProvider requires a StrategyFactory")
}
strategies, err := strategyFactory.CreateStrategies(psp, namespace)
if err != nil {
return nil, err
}
return &simpleProvider{
psp: psp,
strategies: strategies,
}, nil
}
// DefaultPodSecurityContext sets the default values of the required but not filled fields.
// It modifies the SecurityContext and annotations of the provided pod. Validation should be
// used after the context is defaulted to ensure it complies with the required restrictions.
func (s *simpleProvider) DefaultPodSecurityContext(pod *api.Pod) error {
sc := securitycontext.NewPodSecurityContextMutator(pod.Spec.SecurityContext)
if sc.SupplementalGroups() == nil {
supGroups, err := s.strategies.SupplementalGroupStrategy.Generate(pod)
if err != nil {
return err
}
sc.SetSupplementalGroups(supGroups)
}
if sc.FSGroup() == nil {
fsGroup, err := s.strategies.FSGroupStrategy.GenerateSingle(pod)
if err != nil {
return err
}
sc.SetFSGroup(fsGroup)
}
if sc.SELinuxOptions() == nil {
seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, nil)
if err != nil {
return err
}
sc.SetSELinuxOptions(seLinux)
}
// This is only generated on the pod level. Containers inherit the pod's profile. If the
// container has a specific profile set then it will be caught in the validation step.
seccompProfile, err := s.strategies.SeccompStrategy.Generate(pod.Annotations, pod)
if err != nil {
return err
}
if seccompProfile != "" {
if pod.Annotations == nil {
pod.Annotations = map[string]string{}
}
pod.Annotations[api.SeccompPodAnnotationKey] = seccompProfile
}
pod.Spec.SecurityContext = sc.PodSecurityContext()
return nil
}
// DefaultContainerSecurityContext sets the default values of the required but not filled fields.
// It modifies the SecurityContext of the container and annotations of the pod. Validation should
// be used after the context is defaulted to ensure it complies with the required restrictions.
func (s *simpleProvider) DefaultContainerSecurityContext(pod *api.Pod, container *api.Container) error {
sc := securitycontext.NewEffectiveContainerSecurityContextMutator(
securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext),
securitycontext.NewContainerSecurityContextMutator(container.SecurityContext),
)
if sc.RunAsUser() == nil {
uid, err := s.strategies.RunAsUserStrategy.Generate(pod, container)
if err != nil {
return err
}
sc.SetRunAsUser(uid)
}
if sc.SELinuxOptions() == nil {
seLinux, err := s.strategies.SELinuxStrategy.Generate(pod, container)
if err != nil {
return err
}
sc.SetSELinuxOptions(seLinux)
}
annotations, err := s.strategies.AppArmorStrategy.Generate(pod.Annotations, container)
if err != nil {
return err
}
// if we're using the non-root strategy set the marker that this container should not be
// run as root which will signal to the kubelet to do a final check either on the runAsUser
// or, if runAsUser is not set, the image UID will be checked.
if sc.RunAsNonRoot() == nil && sc.RunAsUser() == nil && s.psp.Spec.RunAsUser.Rule == policy.RunAsUserStrategyMustRunAsNonRoot {
nonRoot := true
sc.SetRunAsNonRoot(&nonRoot)
}
caps, err := s.strategies.CapabilitiesStrategy.Generate(pod, container)
if err != nil {
return err
}
sc.SetCapabilities(caps)
// if the PSP requires a read only root filesystem and the container has not made a specific
// request then default ReadOnlyRootFilesystem to true.
if s.psp.Spec.ReadOnlyRootFilesystem && sc.ReadOnlyRootFilesystem() == nil {
readOnlyRootFS := true
sc.SetReadOnlyRootFilesystem(&readOnlyRootFS)
}
// if the PSP sets DefaultAllowPrivilegeEscalation and the container security context
// allowPrivilegeEscalation is not set, then default to that set by the PSP.
if s.psp.Spec.DefaultAllowPrivilegeEscalation != nil && sc.AllowPrivilegeEscalation() == nil {
sc.SetAllowPrivilegeEscalation(s.psp.Spec.DefaultAllowPrivilegeEscalation)
}
// if the PSP sets psp.AllowPrivilegeEscalation to false set that as the default
if !s.psp.Spec.AllowPrivilegeEscalation && sc.AllowPrivilegeEscalation() == nil {
sc.SetAllowPrivilegeEscalation(&s.psp.Spec.AllowPrivilegeEscalation)
}
pod.Annotations = annotations
container.SecurityContext = sc.ContainerSecurityContext()
return nil
}
// ValidatePod ensure a pod is in compliance with the given constraints.
func (s *simpleProvider) ValidatePod(pod *api.Pod, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
sc := securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext)
fsGroups := []int64{}
if fsGroup := sc.FSGroup(); fsGroup != nil {
fsGroups = append(fsGroups, *fsGroup)
}
allErrs = append(allErrs, s.strategies.FSGroupStrategy.Validate(pod, fsGroups)...)
allErrs = append(allErrs, s.strategies.SupplementalGroupStrategy.Validate(pod, sc.SupplementalGroups())...)
allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidatePod(pod)...)
allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(fldPath.Child("seLinuxOptions"), pod, nil, sc.SELinuxOptions())...)
if !s.psp.Spec.HostNetwork && sc.HostNetwork() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostNetwork"), sc.HostNetwork(), "Host network is not allowed to be used"))
}
if !s.psp.Spec.HostPID && sc.HostPID() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPID"), sc.HostPID(), "Host PID is not allowed to be used"))
}
if !s.psp.Spec.HostIPC && sc.HostIPC() {
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostIPC"), sc.HostIPC(), "Host IPC is not allowed to be used"))
}
allErrs = append(allErrs, s.strategies.SysctlsStrategy.Validate(pod)...)
if len(pod.Spec.Volumes) > 0 {
allowsAllVolumeTypes := psputil.PSPAllowsAllVolumes(s.psp)
allowedVolumes := psputil.FSTypeToStringSet(s.psp.Spec.Volumes)
for i, v := range pod.Spec.Volumes {
fsType, err := psputil.GetVolumeFSType(v)
if err != nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "volumes").Index(i), string(fsType), err.Error()))
continue
}
if !allowsAllVolumeTypes && !allowedVolumes.Has(string(fsType)) {
allErrs = append(allErrs, field.Invalid(
field.NewPath("spec", "volumes").Index(i), string(fsType),
fmt.Sprintf("%s volumes are not allowed to be used", string(fsType))))
continue
}
if fsType == policy.HostPath {
allows, mustBeReadOnly := psputil.AllowsHostVolumePath(s.psp, v.HostPath.Path)
if !allows {
allErrs = append(allErrs, field.Invalid(
field.NewPath("spec", "volumes").Index(i).Child("hostPath", "pathPrefix"), v.HostPath.Path,
fmt.Sprintf("is not allowed to be used")))
} else if mustBeReadOnly {
// Ensure all the VolumeMounts that use this volume are read-only
for i, c := range pod.Spec.InitContainers {
for j, cv := range c.VolumeMounts {
if cv.Name == v.Name && !cv.ReadOnly {
allErrs = append(allErrs, field.Invalid(
field.NewPath("spec", "initContainers").Index(i).Child("volumeMounts").Index(j).Child("readOnly"),
cv.ReadOnly, "must be read-only"),
)
}
}
}
for i, c := range pod.Spec.Containers {
for j, cv := range c.VolumeMounts {
if cv.Name == v.Name && !cv.ReadOnly {
allErrs = append(allErrs, field.Invalid(
field.NewPath("spec", "containers").Index(i).Child("volumeMounts").Index(j).Child("readOnly"),
cv.ReadOnly, "must be read-only"),
)
}
}
}
}
}
if fsType == policy.FlexVolume && len(s.psp.Spec.AllowedFlexVolumes) > 0 {
found := false
driver := v.FlexVolume.Driver
for _, allowedFlexVolume := range s.psp.Spec.AllowedFlexVolumes {
if driver == allowedFlexVolume.Driver {
found = true
break
}
}
if !found {
allErrs = append(allErrs,
field.Invalid(fldPath.Child("volumes").Index(i).Child("driver"), driver,
"Flexvolume driver is not allowed to be used"))
}
}
}
}
return allErrs
}
// Ensure a container's SecurityContext is in compliance with the given constraints
func (s *simpleProvider) ValidateContainerSecurityContext(pod *api.Pod, container *api.Container, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
podSC := securitycontext.NewPodSecurityContextAccessor(pod.Spec.SecurityContext)
sc := securitycontext.NewEffectiveContainerSecurityContextAccessor(podSC, securitycontext.NewContainerSecurityContextMutator(container.SecurityContext))
allErrs = append(allErrs, s.strategies.RunAsUserStrategy.Validate(fldPath.Child("securityContext"), pod, container, sc.RunAsNonRoot(), sc.RunAsUser())...)
allErrs = append(allErrs, s.strategies.SELinuxStrategy.Validate(fldPath.Child("seLinuxOptions"), pod, container, sc.SELinuxOptions())...)
allErrs = append(allErrs, s.strategies.AppArmorStrategy.Validate(pod, container)...)
allErrs = append(allErrs, s.strategies.SeccompStrategy.ValidateContainer(pod, container)...)
privileged := sc.Privileged()
if !s.psp.Spec.Privileged && privileged != nil && *privileged {
allErrs = append(allErrs, field.Invalid(fldPath.Child("privileged"), *privileged, "Privileged containers are not allowed"))
}
allErrs = append(allErrs, s.strategies.CapabilitiesStrategy.Validate(pod, container, sc.Capabilities())...)
containersPath := fldPath.Child("containers")
for idx, c := range pod.Spec.Containers {
idxPath := containersPath.Index(idx)
allErrs = append(allErrs, s.hasInvalidHostPort(&c, idxPath)...)
}
containersPath = fldPath.Child("initContainers")
for idx, c := range pod.Spec.InitContainers {
idxPath := containersPath.Index(idx)
allErrs = append(allErrs, s.hasInvalidHostPort(&c, idxPath)...)
}
if s.psp.Spec.ReadOnlyRootFilesystem {
readOnly := sc.ReadOnlyRootFilesystem()
if readOnly == nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("readOnlyRootFilesystem"), readOnly, "ReadOnlyRootFilesystem may not be nil and must be set to true"))
} else if !*readOnly {
allErrs = append(allErrs, field.Invalid(fldPath.Child("readOnlyRootFilesystem"), *readOnly, "ReadOnlyRootFilesystem must be set to true"))
}
}
allowEscalation := sc.AllowPrivilegeEscalation()
if !s.psp.Spec.AllowPrivilegeEscalation && allowEscalation == nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("allowPrivilegeEscalation"), allowEscalation, "Allowing privilege escalation for containers is not allowed"))
}
if !s.psp.Spec.AllowPrivilegeEscalation && allowEscalation != nil && *allowEscalation {
allErrs = append(allErrs, field.Invalid(fldPath.Child("allowPrivilegeEscalation"), *allowEscalation, "Allowing privilege escalation for containers is not allowed"))
}
return allErrs
}
// hasInvalidHostPort checks whether the port definitions on the container fall outside of the ranges allowed by the PSP.
func (s *simpleProvider) hasInvalidHostPort(container *api.Container, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
for _, cp := range container.Ports {
if cp.HostPort > 0 && !s.isValidHostPort(cp.HostPort) {
detail := fmt.Sprintf("Host port %d is not allowed to be used. Allowed ports: [%s]", cp.HostPort, hostPortRangesToString(s.psp.Spec.HostPorts))
allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPort"), cp.HostPort, detail))
}
}
return allErrs
}
// isValidHostPort returns true if the port falls in any range allowed by the PSP.
func (s *simpleProvider) isValidHostPort(port int32) bool {
for _, hostPortRange := range s.psp.Spec.HostPorts {
if port >= hostPortRange.Min && port <= hostPortRange.Max {
return true
}
}
return false
}
// Get the name of the PSP that this provider was initialized with.
func (s *simpleProvider) GetPSPName() string {
return s.psp.Name
}
func hostPortRangesToString(ranges []policy.HostPortRange) string {
formattedString := ""
if ranges != nil {
strRanges := []string{}
for _, r := range ranges {
if r.Min == r.Max {
strRanges = append(strRanges, fmt.Sprintf("%d", r.Min))
} else {
strRanges = append(strRanges, fmt.Sprintf("%d-%d", r.Min, r.Max))
}
}
formattedString = strings.Join(strRanges, ",")
}
return formattedString
}

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["strategy.go"],
importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp",
deps = [
"//pkg/apis/core:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["strategy_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,149 +0,0 @@
/*
Copyright 2016 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 seccomp
import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
)
const (
// AllowAny is the wildcard used to allow any profile.
AllowAny = "*"
// The annotation key specifying the default seccomp profile.
DefaultProfileAnnotationKey = "seccomp.security.alpha.kubernetes.io/defaultProfileName"
// The annotation key specifying the allowed seccomp profiles.
AllowedProfilesAnnotationKey = "seccomp.security.alpha.kubernetes.io/allowedProfileNames"
)
// Strategy defines the interface for all seccomp constraint strategies.
type Strategy interface {
// Generate returns a profile based on constraint rules.
Generate(annotations map[string]string, pod *api.Pod) (string, error)
// Validate ensures that the specified values fall within the range of the strategy.
ValidatePod(pod *api.Pod) field.ErrorList
// Validate ensures that the specified values fall within the range of the strategy.
ValidateContainer(pod *api.Pod, container *api.Container) field.ErrorList
}
type strategy struct {
defaultProfile string
allowedProfiles map[string]bool
// For printing error messages (preserves order).
allowedProfilesString string
// does the strategy allow any profile (wildcard)
allowAnyProfile bool
}
var _ Strategy = &strategy{}
// NewStrategy creates a new strategy that enforces seccomp profile constraints.
func NewStrategy(pspAnnotations map[string]string) Strategy {
var allowedProfiles map[string]bool
allowAnyProfile := false
if allowed, ok := pspAnnotations[AllowedProfilesAnnotationKey]; ok {
profiles := strings.Split(allowed, ",")
allowedProfiles = make(map[string]bool, len(profiles))
for _, p := range profiles {
if p == AllowAny {
allowAnyProfile = true
continue
}
allowedProfiles[p] = true
}
}
return &strategy{
defaultProfile: pspAnnotations[DefaultProfileAnnotationKey],
allowedProfiles: allowedProfiles,
allowedProfilesString: pspAnnotations[AllowedProfilesAnnotationKey],
allowAnyProfile: allowAnyProfile,
}
}
// Generate returns a profile based on constraint rules.
func (s *strategy) Generate(annotations map[string]string, pod *api.Pod) (string, error) {
if annotations[api.SeccompPodAnnotationKey] != "" {
// Profile already set, nothing to do.
return annotations[api.SeccompPodAnnotationKey], nil
}
return s.defaultProfile, nil
}
// ValidatePod ensures that the specified values on the pod fall within the range
// of the strategy.
func (s *strategy) ValidatePod(pod *api.Pod) field.ErrorList {
allErrs := field.ErrorList{}
podSpecFieldPath := field.NewPath("pod", "metadata", "annotations").Key(api.SeccompPodAnnotationKey)
podProfile := pod.Annotations[api.SeccompPodAnnotationKey]
if !s.allowAnyProfile && len(s.allowedProfiles) == 0 && podProfile != "" {
allErrs = append(allErrs, field.Forbidden(podSpecFieldPath, "seccomp may not be set"))
return allErrs
}
if !s.profileAllowed(podProfile) {
msg := fmt.Sprintf("%s is not an allowed seccomp profile. Valid values are %v", podProfile, s.allowedProfilesString)
allErrs = append(allErrs, field.Forbidden(podSpecFieldPath, msg))
}
return allErrs
}
// ValidateContainer ensures that the specified values on the container fall within
// the range of the strategy.
func (s *strategy) ValidateContainer(pod *api.Pod, container *api.Container) field.ErrorList {
allErrs := field.ErrorList{}
fieldPath := field.NewPath("pod", "metadata", "annotations").Key(api.SeccompContainerAnnotationKeyPrefix + container.Name)
containerProfile := profileForContainer(pod, container)
if !s.allowAnyProfile && len(s.allowedProfiles) == 0 && containerProfile != "" {
allErrs = append(allErrs, field.Forbidden(fieldPath, "seccomp may not be set"))
return allErrs
}
if !s.profileAllowed(containerProfile) {
msg := fmt.Sprintf("%s is not an allowed seccomp profile. Valid values are %v", containerProfile, s.allowedProfilesString)
allErrs = append(allErrs, field.Forbidden(fieldPath, msg))
}
return allErrs
}
// profileAllowed checks if profile is in allowedProfiles or if allowedProfiles
// contains the wildcard.
func (s *strategy) profileAllowed(profile string) bool {
// for backwards compatibility and PSPs without a defined list of allowed profiles.
// If a PSP does not have allowedProfiles set then we should allow an empty profile.
// This will mean that the runtime default is used.
if len(s.allowedProfiles) == 0 && profile == "" {
return true
}
return s.allowAnyProfile || s.allowedProfiles[profile]
}
// profileForContainer returns the container profile if set, otherwise the pod profile.
func profileForContainer(pod *api.Pod, container *api.Container) string {
containerProfile, ok := pod.Annotations[api.SeccompContainerAnnotationKeyPrefix+container.Name]
if ok {
return containerProfile
}
return pod.Annotations[api.SeccompPodAnnotationKey]
}

View File

@ -1,322 +0,0 @@
/*
Copyright 2016 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 seccomp
import (
"reflect"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
api "k8s.io/kubernetes/pkg/apis/core"
)
var (
withoutSeccomp = map[string]string{"foo": "bar"}
allowAnyNoDefault = map[string]string{
AllowedProfilesAnnotationKey: "*",
}
allowAnyDefault = map[string]string{
AllowedProfilesAnnotationKey: "*",
DefaultProfileAnnotationKey: "foo",
}
allowAnyAndSpecificDefault = map[string]string{
AllowedProfilesAnnotationKey: "*,bar",
DefaultProfileAnnotationKey: "foo",
}
allowSpecific = map[string]string{
AllowedProfilesAnnotationKey: "foo",
}
)
func TestNewStrategy(t *testing.T) {
tests := map[string]struct {
annotations map[string]string
expectedAllowedProfilesString string
expectedAllowAny bool
expectedAllowedProfiles map[string]bool
expectedDefaultProfile string
}{
"no seccomp": {
annotations: withoutSeccomp,
expectedAllowAny: false,
expectedAllowedProfilesString: "",
expectedAllowedProfiles: nil,
expectedDefaultProfile: "",
},
"allow any, no default": {
annotations: allowAnyNoDefault,
expectedAllowAny: true,
expectedAllowedProfilesString: "*",
expectedAllowedProfiles: map[string]bool{},
expectedDefaultProfile: "",
},
"allow any, default": {
annotations: allowAnyDefault,
expectedAllowAny: true,
expectedAllowedProfilesString: "*",
expectedAllowedProfiles: map[string]bool{},
expectedDefaultProfile: "foo",
},
"allow any and specific, default": {
annotations: allowAnyAndSpecificDefault,
expectedAllowAny: true,
expectedAllowedProfilesString: "*,bar",
expectedAllowedProfiles: map[string]bool{
"bar": true,
},
expectedDefaultProfile: "foo",
},
}
for k, v := range tests {
s := NewStrategy(v.annotations)
internalStrat, _ := s.(*strategy)
if internalStrat.allowAnyProfile != v.expectedAllowAny {
t.Errorf("%s expected allowAnyProfile to be %t but found %t", k, v.expectedAllowAny, internalStrat.allowAnyProfile)
}
if internalStrat.allowedProfilesString != v.expectedAllowedProfilesString {
t.Errorf("%s expected allowedProfilesString to be %s but found %s", k, v.expectedAllowedProfilesString, internalStrat.allowedProfilesString)
}
if internalStrat.defaultProfile != v.expectedDefaultProfile {
t.Errorf("%s expected defaultProfile to be %s but found %s", k, v.expectedDefaultProfile, internalStrat.defaultProfile)
}
if !reflect.DeepEqual(v.expectedAllowedProfiles, internalStrat.allowedProfiles) {
t.Errorf("%s expected expectedAllowedProfiles to be %#v but found %#v", k, v.expectedAllowedProfiles, internalStrat.allowedProfiles)
}
}
}
func TestGenerate(t *testing.T) {
tests := map[string]struct {
pspAnnotations map[string]string
podAnnotations map[string]string
expectedProfile string
}{
"no seccomp, no pod annotations": {
pspAnnotations: withoutSeccomp,
podAnnotations: nil,
expectedProfile: "",
},
"no seccomp, pod annotations": {
pspAnnotations: withoutSeccomp,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "foo",
},
expectedProfile: "foo",
},
"seccomp with no default, no pod annotations": {
pspAnnotations: allowAnyNoDefault,
podAnnotations: nil,
expectedProfile: "",
},
"seccomp with no default, pod annotations": {
pspAnnotations: allowAnyNoDefault,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "foo",
},
expectedProfile: "foo",
},
"seccomp with default, no pod annotations": {
pspAnnotations: allowAnyDefault,
podAnnotations: nil,
expectedProfile: "foo",
},
"seccomp with default, pod annotations": {
pspAnnotations: allowAnyDefault,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "bar",
},
expectedProfile: "bar",
},
}
for k, v := range tests {
s := NewStrategy(v.pspAnnotations)
actual, err := s.Generate(v.podAnnotations, nil)
if err != nil {
t.Errorf("%s received error during generation %#v", k, err)
continue
}
if actual != v.expectedProfile {
t.Errorf("%s expected profile %s but received %s", k, v.expectedProfile, actual)
}
}
}
func TestValidatePod(t *testing.T) {
tests := map[string]struct {
pspAnnotations map[string]string
podAnnotations map[string]string
expectedError string
}{
"no pod annotations, required profiles": {
pspAnnotations: allowSpecific,
podAnnotations: nil,
expectedError: "Forbidden: is not an allowed seccomp profile. Valid values are foo",
},
"no pod annotations, no required profiles": {
pspAnnotations: withoutSeccomp,
podAnnotations: nil,
expectedError: "",
},
"valid pod annotations, required profiles": {
pspAnnotations: allowSpecific,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "foo",
},
expectedError: "",
},
"invalid pod annotations, required profiles": {
pspAnnotations: allowSpecific,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "bar",
},
expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo",
},
"pod annotations, no required profiles": {
pspAnnotations: withoutSeccomp,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "foo",
},
expectedError: "Forbidden: seccomp may not be set",
},
"pod annotations, allow any": {
pspAnnotations: allowAnyNoDefault,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "foo",
},
expectedError: "",
},
"no pod annotations, allow any": {
pspAnnotations: allowAnyNoDefault,
podAnnotations: nil,
expectedError: "",
},
}
for k, v := range tests {
pod := &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: v.podAnnotations,
},
}
s := NewStrategy(v.pspAnnotations)
errs := s.ValidatePod(pod)
if v.expectedError == "" && len(errs) != 0 {
t.Errorf("%s expected no errors but received %#v", k, errs.ToAggregate().Error())
}
if v.expectedError != "" && len(errs) == 0 {
t.Errorf("%s expected error %s but received none", k, v.expectedError)
}
if v.expectedError != "" && len(errs) > 1 {
t.Errorf("%s received multiple errors: %s", k, errs.ToAggregate().Error())
}
if v.expectedError != "" && len(errs) == 1 && !strings.Contains(errs.ToAggregate().Error(), v.expectedError) {
t.Errorf("%s expected error %s but received %s", k, v.expectedError, errs.ToAggregate().Error())
}
}
}
func TestValidateContainer(t *testing.T) {
tests := map[string]struct {
pspAnnotations map[string]string
podAnnotations map[string]string
expectedError string
}{
"no pod annotations, required profiles": {
pspAnnotations: allowSpecific,
podAnnotations: nil,
expectedError: "Forbidden: is not an allowed seccomp profile. Valid values are foo",
},
"no pod annotations, no required profiles": {
pspAnnotations: withoutSeccomp,
podAnnotations: nil,
expectedError: "",
},
"valid pod annotations, required profiles": {
pspAnnotations: allowSpecific,
podAnnotations: map[string]string{
api.SeccompContainerAnnotationKeyPrefix + "container": "foo",
},
expectedError: "",
},
"invalid pod annotations, required profiles": {
pspAnnotations: allowSpecific,
podAnnotations: map[string]string{
api.SeccompContainerAnnotationKeyPrefix + "container": "bar",
},
expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo",
},
"pod annotations, no required profiles": {
pspAnnotations: withoutSeccomp,
podAnnotations: map[string]string{
api.SeccompContainerAnnotationKeyPrefix + "container": "foo",
},
expectedError: "Forbidden: seccomp may not be set",
},
"pod annotations, allow any": {
pspAnnotations: allowAnyNoDefault,
podAnnotations: map[string]string{
api.SeccompContainerAnnotationKeyPrefix + "container": "foo",
},
expectedError: "",
},
"no pod annotations, allow any": {
pspAnnotations: allowAnyNoDefault,
podAnnotations: nil,
expectedError: "",
},
"container inherits valid pod annotation": {
pspAnnotations: allowSpecific,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "foo",
},
expectedError: "",
},
"container inherits invalid pod annotation": {
pspAnnotations: allowSpecific,
podAnnotations: map[string]string{
api.SeccompPodAnnotationKey: "bar",
},
expectedError: "Forbidden: bar is not an allowed seccomp profile. Valid values are foo",
},
}
for k, v := range tests {
pod := &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: v.podAnnotations,
},
}
container := &api.Container{
Name: "container",
}
s := NewStrategy(v.pspAnnotations)
errs := s.ValidateContainer(pod, container)
if v.expectedError == "" && len(errs) != 0 {
t.Errorf("%s expected no errors but received %#v", k, errs.ToAggregate().Error())
}
if v.expectedError != "" && len(errs) == 0 {
t.Errorf("%s expected error %s but received none", k, v.expectedError)
}
if v.expectedError != "" && len(errs) > 1 {
t.Errorf("%s received multiple errors: %s", k, errs.ToAggregate().Error())
}
if v.expectedError != "" && len(errs) == 1 && !strings.Contains(errs.ToAggregate().Error(), v.expectedError) {
t.Errorf("%s expected error %s but received %s", k, v.expectedError, errs.ToAggregate().Error())
}
}
}

View File

@ -1,50 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"mustrunas.go",
"runasany.go",
"types.go",
],
importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"mustrunas_test.go",
"runasany_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/policy:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,19 +0,0 @@
/*
Copyright 2016 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 selinux contains code for validating and defaulting the SELinux
// context of a pod according to a security policy.
package selinux

View File

@ -1,120 +0,0 @@
/*
Copyright 2016 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 selinux
import (
"fmt"
"sort"
"strings"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
)
type mustRunAs struct {
opts *policy.SELinuxStrategyOptions
}
var _ SELinuxStrategy = &mustRunAs{}
func NewMustRunAs(options *policy.SELinuxStrategyOptions) (SELinuxStrategy, error) {
if options == nil {
return nil, fmt.Errorf("MustRunAs requires SELinuxContextStrategyOptions")
}
if options.SELinuxOptions == nil {
return nil, fmt.Errorf("MustRunAs requires SELinuxOptions")
}
return &mustRunAs{
opts: options,
}, nil
}
// Generate creates the SELinuxOptions based on constraint rules.
func (s *mustRunAs) Generate(_ *api.Pod, _ *api.Container) (*api.SELinuxOptions, error) {
return s.opts.SELinuxOptions, nil
}
// Validate ensures that the specified values fall within the range of the strategy.
func (s *mustRunAs) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, seLinux *api.SELinuxOptions) field.ErrorList {
allErrs := field.ErrorList{}
if seLinux == nil {
allErrs = append(allErrs, field.Required(fldPath, ""))
return allErrs
}
if !equalLevels(s.opts.SELinuxOptions.Level, seLinux.Level) {
detail := fmt.Sprintf("must be %s", s.opts.SELinuxOptions.Level)
allErrs = append(allErrs, field.Invalid(fldPath.Child("level"), seLinux.Level, detail))
}
if seLinux.Role != s.opts.SELinuxOptions.Role {
detail := fmt.Sprintf("must be %s", s.opts.SELinuxOptions.Role)
allErrs = append(allErrs, field.Invalid(fldPath.Child("role"), seLinux.Role, detail))
}
if seLinux.Type != s.opts.SELinuxOptions.Type {
detail := fmt.Sprintf("must be %s", s.opts.SELinuxOptions.Type)
allErrs = append(allErrs, field.Invalid(fldPath.Child("type"), seLinux.Type, detail))
}
if seLinux.User != s.opts.SELinuxOptions.User {
detail := fmt.Sprintf("must be %s", s.opts.SELinuxOptions.User)
allErrs = append(allErrs, field.Invalid(fldPath.Child("user"), seLinux.User, detail))
}
return allErrs
}
// equalLevels compares SELinux levels for equality.
func equalLevels(expected, actual string) bool {
if expected == actual {
return true
}
// "s0:c6,c0" => [ "s0", "c6,c0" ]
expectedParts := strings.SplitN(expected, ":", 2)
actualParts := strings.SplitN(actual, ":", 2)
// both SELinux levels must be in a format "sX:cY"
if len(expectedParts) != 2 || len(actualParts) != 2 {
return false
}
if !equalSensitivity(expectedParts[0], actualParts[0]) {
return false
}
if !equalCategories(expectedParts[1], actualParts[1]) {
return false
}
return true
}
// equalSensitivity compares sensitivities of the SELinux levels for equality.
func equalSensitivity(expected, actual string) bool {
return expected == actual
}
// equalCategories compares categories of the SELinux levels for equality.
func equalCategories(expected, actual string) bool {
expectedCategories := strings.Split(expected, ",")
actualCategories := strings.Split(actual, ",")
sort.Strings(expectedCategories)
sort.Strings(actualCategories)
return util.EqualStringSlices(expectedCategories, actualCategories)
}

View File

@ -1,177 +0,0 @@
/*
Copyright 2016 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 selinux
import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
"reflect"
"strings"
"testing"
)
func TestMustRunAsOptions(t *testing.T) {
tests := map[string]struct {
opts *policy.SELinuxStrategyOptions
pass bool
}{
"nil opts": {
opts: nil,
pass: false,
},
"invalid opts": {
opts: &policy.SELinuxStrategyOptions{},
pass: false,
},
"valid opts": {
opts: &policy.SELinuxStrategyOptions{SELinuxOptions: &api.SELinuxOptions{}},
pass: true,
},
}
for name, tc := range tests {
_, err := NewMustRunAs(tc.opts)
if err != nil && tc.pass {
t.Errorf("%s expected to pass but received error %#v", name, err)
}
if err == nil && !tc.pass {
t.Errorf("%s expected to fail but did not receive an error", name)
}
}
}
func TestMustRunAsGenerate(t *testing.T) {
opts := &policy.SELinuxStrategyOptions{
SELinuxOptions: &api.SELinuxOptions{
User: "user",
Role: "role",
Type: "type",
Level: "level",
},
}
mustRunAs, err := NewMustRunAs(opts)
if err != nil {
t.Fatalf("unexpected error initializing NewMustRunAs %v", err)
}
generated, err := mustRunAs.Generate(nil, nil)
if err != nil {
t.Fatalf("unexpected error generating selinux %v", err)
}
if !reflect.DeepEqual(generated, opts.SELinuxOptions) {
t.Errorf("generated selinux does not equal configured selinux")
}
}
func TestMustRunAsValidate(t *testing.T) {
newValidOpts := func() *api.SELinuxOptions {
return &api.SELinuxOptions{
User: "user",
Role: "role",
Level: "s0:c0,c6",
Type: "type",
}
}
newValidOptsWithLevel := func(level string) *api.SELinuxOptions {
opts := newValidOpts()
opts.Level = level
return opts
}
role := newValidOpts()
role.Role = "invalid"
user := newValidOpts()
user.User = "invalid"
seType := newValidOpts()
seType.Type = "invalid"
validOpts := newValidOpts()
tests := map[string]struct {
podSeLinux *api.SELinuxOptions
pspSeLinux *api.SELinuxOptions
expectedMsg string
}{
"invalid role": {
podSeLinux: role,
pspSeLinux: validOpts,
expectedMsg: "role: Invalid value",
},
"invalid user": {
podSeLinux: user,
pspSeLinux: validOpts,
expectedMsg: "user: Invalid value",
},
"levels are not equal": {
podSeLinux: newValidOptsWithLevel("s0"),
pspSeLinux: newValidOptsWithLevel("s0:c1,c2"),
expectedMsg: "level: Invalid value",
},
"levels differ by sensitivity": {
podSeLinux: newValidOptsWithLevel("s0:c6"),
pspSeLinux: newValidOptsWithLevel("s1:c6"),
expectedMsg: "level: Invalid value",
},
"levels differ by categories": {
podSeLinux: newValidOptsWithLevel("s0:c0,c8"),
pspSeLinux: newValidOptsWithLevel("s0:c1,c7"),
expectedMsg: "level: Invalid value",
},
"valid": {
podSeLinux: validOpts,
pspSeLinux: validOpts,
expectedMsg: "",
},
"valid with different order of categories": {
podSeLinux: newValidOptsWithLevel("s0:c6,c0"),
pspSeLinux: validOpts,
expectedMsg: "",
},
}
for name, tc := range tests {
opts := &policy.SELinuxStrategyOptions{
SELinuxOptions: tc.pspSeLinux,
}
mustRunAs, err := NewMustRunAs(opts)
if err != nil {
t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err)
continue
}
errs := mustRunAs.Validate(nil, nil, nil, tc.podSeLinux)
//should've passed but didn't
if len(tc.expectedMsg) == 0 && len(errs) > 0 {
t.Errorf("%s expected no errors but received %v", name, errs)
}
//should've failed but didn't
if len(tc.expectedMsg) != 0 && len(errs) == 0 {
t.Errorf("%s expected error %s but received no errors", name, tc.expectedMsg)
}
//failed with additional messages
if len(tc.expectedMsg) != 0 && len(errs) > 1 {
t.Errorf("%s expected error %s but received multiple errors: %v", name, tc.expectedMsg, errs)
}
//check that we got the right message
if len(tc.expectedMsg) != 0 && len(errs) == 1 {
if !strings.Contains(errs[0].Error(), tc.expectedMsg) {
t.Errorf("%s expected error to contain %s but it did not: %v", name, tc.expectedMsg, errs)
}
}
}
}

View File

@ -1,43 +0,0 @@
/*
Copyright 2016 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 selinux
import (
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
)
// runAsAny implements the SELinuxStrategy interface.
type runAsAny struct{}
var _ SELinuxStrategy = &runAsAny{}
// NewRunAsAny provides a strategy that will return the configured se linux context or nil.
func NewRunAsAny(options *policy.SELinuxStrategyOptions) (SELinuxStrategy, error) {
return &runAsAny{}, nil
}
// Generate creates the SELinuxOptions based on constraint rules.
func (s *runAsAny) Generate(pod *api.Pod, container *api.Container) (*api.SELinuxOptions, error) {
return nil, nil
}
// Validate ensures that the specified values fall within the range of the strategy.
func (s *runAsAny) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, options *api.SELinuxOptions) field.ErrorList {
return field.ErrorList{}
}

View File

@ -1,72 +0,0 @@
/*
Copyright 2016 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 selinux
import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
"testing"
)
func TestRunAsAnyOptions(t *testing.T) {
_, err := NewRunAsAny(nil)
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
}
_, err = NewRunAsAny(&policy.SELinuxStrategyOptions{})
if err != nil {
t.Errorf("unexpected error initializing NewRunAsAny %v", err)
}
}
func TestRunAsAnyGenerate(t *testing.T) {
s, err := NewRunAsAny(&policy.SELinuxStrategyOptions{})
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
}
uid, err := s.Generate(nil, nil)
if uid != nil {
t.Errorf("expected nil uid but got %v", *uid)
}
if err != nil {
t.Errorf("unexpected error generating uid %v", err)
}
}
func TestRunAsAnyValidate(t *testing.T) {
s, err := NewRunAsAny(&policy.SELinuxStrategyOptions{
SELinuxOptions: &api.SELinuxOptions{
Level: "foo",
},
},
)
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
}
errs := s.Validate(nil, nil, nil, nil)
if len(errs) != 0 {
t.Errorf("unexpected errors validating with ")
}
s, err = NewRunAsAny(&policy.SELinuxStrategyOptions{})
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
}
errs = s.Validate(nil, nil, nil, nil)
if len(errs) != 0 {
t.Errorf("unexpected errors validating %v", errs)
}
}

View File

@ -1,30 +0,0 @@
/*
Copyright 2016 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 selinux
import (
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
)
// SELinuxStrategy defines the interface for all SELinux constraint strategies.
type SELinuxStrategy interface {
// Generate creates the SELinuxOptions based on constraint rules.
Generate(pod *api.Pod, container *api.Container) (*api.SELinuxOptions, error)
// Validate ensures that the specified values fall within the range of the strategy.
Validate(fldPath *field.Path, pod *api.Pod, container *api.Container, options *api.SELinuxOptions) field.ErrorList
}

View File

@ -1,40 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"mustmatchpatterns.go",
"types.go",
],
importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl",
deps = [
"//pkg/apis/core:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["mustmatchpatterns_test.go"],
embed = [":go_default_library"],
deps = ["//pkg/apis/core:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,126 +0,0 @@
/*
Copyright 2016 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 sysctl
import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
)
// SafeSysctlWhitelist returns the whitelist of safe sysctls and safe sysctl patterns (ending in *).
//
// A sysctl is called safe iff
// - it is namespaced in the container or the pod
// - it is isolated, i.e. has no influence on any other pod on the same node.
func SafeSysctlWhitelist() []string {
return []string{
"kernel.shm_rmid_forced",
"net.ipv4.ip_local_port_range",
"net.ipv4.tcp_syncookies",
}
}
// mustMatchPatterns implements the SysctlsStrategy interface
type mustMatchPatterns struct {
safeWhitelist []string
allowedUnsafeSysctls []string
forbiddenSysctls []string
}
var (
_ SysctlsStrategy = &mustMatchPatterns{}
defaultSysctlsPatterns = []string{"*"}
)
// NewMustMatchPatterns creates a new mustMatchPatterns strategy that will provide validation.
// Passing nil means the default pattern, passing an empty list means to disallow all sysctls.
func NewMustMatchPatterns(safeWhitelist, allowedUnsafeSysctls, forbiddenSysctls []string) SysctlsStrategy {
return &mustMatchPatterns{
safeWhitelist: safeWhitelist,
allowedUnsafeSysctls: allowedUnsafeSysctls,
forbiddenSysctls: forbiddenSysctls,
}
}
func (s *mustMatchPatterns) isForbidden(sysctlName string) bool {
// Is the sysctl forbidden?
for _, s := range s.forbiddenSysctls {
if strings.HasSuffix(s, "*") {
prefix := strings.TrimSuffix(s, "*")
if strings.HasPrefix(sysctlName, prefix) {
return true
}
} else if sysctlName == s {
return true
}
}
return false
}
func (s *mustMatchPatterns) isSafe(sysctlName string) bool {
for _, ws := range s.safeWhitelist {
if sysctlName == ws {
return true
}
}
return false
}
func (s *mustMatchPatterns) isAllowedUnsafe(sysctlName string) bool {
for _, s := range s.allowedUnsafeSysctls {
if strings.HasSuffix(s, "*") {
prefix := strings.TrimSuffix(s, "*")
if strings.HasPrefix(sysctlName, prefix) {
return true
}
} else if sysctlName == s {
return true
}
}
return false
}
// Validate ensures that the specified values fall within the range of the strategy.
func (s *mustMatchPatterns) Validate(pod *api.Pod) field.ErrorList {
allErrs := field.ErrorList{}
var sysctls []api.Sysctl
if pod.Spec.SecurityContext != nil {
sysctls = pod.Spec.SecurityContext.Sysctls
}
fieldPath := field.NewPath("pod", "spec", "securityContext").Child("sysctls")
for i, sysctl := range sysctls {
switch {
case s.isForbidden(sysctl.Name):
allErrs = append(allErrs, field.ErrorList{field.Forbidden(fieldPath.Index(i), fmt.Sprintf("sysctl %q is not allowed", sysctl.Name))}...)
case s.isSafe(sysctl.Name):
continue
case s.isAllowedUnsafe(sysctl.Name):
continue
default:
allErrs = append(allErrs, field.ErrorList{field.Forbidden(fieldPath.Index(i), fmt.Sprintf("unsafe sysctl %q is not allowed", sysctl.Name))}...)
}
}
return allErrs
}

View File

@ -1,104 +0,0 @@
/*
Copyright 2016 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 sysctl
import (
"testing"
api "k8s.io/kubernetes/pkg/apis/core"
)
func TestValidate(t *testing.T) {
tests := map[string]struct {
whitelist []string
forbiddenSafe []string
allowedUnsafe []string
allowed []string
disallowed []string
}{
// no container requests
"with allow all": {
whitelist: []string{"foo"},
allowed: []string{"foo"},
},
"empty": {
whitelist: []string{"foo"},
forbiddenSafe: []string{"*"},
disallowed: []string{"foo"},
},
"without wildcard": {
whitelist: []string{"a", "a.b"},
allowed: []string{"a", "a.b"},
disallowed: []string{"b"},
},
"with catch-all wildcard and non-wildcard": {
allowedUnsafe: []string{"a.b.c", "*"},
allowed: []string{"a", "a.b", "a.b.c", "b"},
},
"without catch-all wildcard": {
allowedUnsafe: []string{"a.*", "b.*", "c.d.e", "d.e.f.*"},
allowed: []string{"a.b", "b.c", "c.d.e", "d.e.f.g.h"},
disallowed: []string{"a", "b", "c", "c.d", "d.e", "d.e.f"},
},
}
for k, v := range tests {
strategy := NewMustMatchPatterns(v.whitelist, v.allowedUnsafe, v.forbiddenSafe)
pod := &api.Pod{}
errs := strategy.Validate(pod)
if len(errs) != 0 {
t.Errorf("%s: unexpected validaton errors for empty sysctls: %v", k, errs)
}
testAllowed := func() {
sysctls := []api.Sysctl{}
for _, s := range v.allowed {
sysctls = append(sysctls, api.Sysctl{
Name: s,
Value: "dummy",
})
}
pod.Spec.SecurityContext = &api.PodSecurityContext{
Sysctls: sysctls,
}
errs = strategy.Validate(pod)
if len(errs) != 0 {
t.Errorf("%s: unexpected validaton errors for sysctls: %v", k, errs)
}
}
testDisallowed := func() {
for _, s := range v.disallowed {
pod.Spec.SecurityContext = &api.PodSecurityContext{
Sysctls: []api.Sysctl{
{
Name: s,
Value: "dummy",
},
},
}
errs = strategy.Validate(pod)
if len(errs) == 0 {
t.Errorf("%s: expected error for sysctl %q", k, s)
}
}
}
testAllowed()
testDisallowed()
}
}

View File

@ -1,28 +0,0 @@
/*
Copyright 2016 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 sysctl
import (
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
)
// SysctlsStrategy defines the interface for all sysctl strategies.
type SysctlsStrategy interface {
// Validate ensures that the specified values fall within the range of the strategy.
Validate(pod *api.Pod) field.ErrorList
}

View File

@ -1,70 +0,0 @@
/*
Copyright 2016 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 podsecuritypolicy
import (
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/apparmor"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/capabilities"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/group"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/seccomp"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/selinux"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/sysctl"
"k8s.io/kubernetes/pkg/security/podsecuritypolicy/user"
)
// Provider provides the implementation to generate a new security
// context based on constraints or validate an existing security context against constraints.
type Provider interface {
// DefaultPodSecurityContext sets the default values of the required but not filled fields.
// It modifies the SecurityContext and annotations of the provided pod.
DefaultPodSecurityContext(pod *api.Pod) error
// DefaultContainerSecurityContext sets the default values of the required but not filled fields.
// It modifies the SecurityContext of the container and annotations of the pod.
DefaultContainerSecurityContext(pod *api.Pod, container *api.Container) error
// Ensure a pod is in compliance with the given constraints.
ValidatePod(pod *api.Pod, fldPath *field.Path) field.ErrorList
// Ensure a container's SecurityContext is in compliance with the given constraints
ValidateContainerSecurityContext(pod *api.Pod, container *api.Container, fldPath *field.Path) field.ErrorList
// Get the name of the PSP that this provider was initialized with.
GetPSPName() string
}
// StrategyFactory abstracts how the strategies are created from the provider so that you may
// implement your own custom strategies that may pull information from other resources as necessary.
// For example, if you would like to populate the strategies with values from namespace annotations
// you may create a factory with a client that can pull the namespace and populate the appropriate
// values.
type StrategyFactory interface {
// CreateStrategies creates the strategies that a provider will use. The namespace argument
// should be the namespace of the object being checked (the pod's namespace).
CreateStrategies(psp *policy.PodSecurityPolicy, namespace string) (*ProviderStrategies, error)
}
// ProviderStrategies is a holder for all strategies that the provider requires to be populated.
type ProviderStrategies struct {
RunAsUserStrategy user.RunAsUserStrategy
SELinuxStrategy selinux.SELinuxStrategy
AppArmorStrategy apparmor.Strategy
FSGroupStrategy group.GroupStrategy
SupplementalGroupStrategy group.GroupStrategy
CapabilitiesStrategy capabilities.Strategy
SysctlsStrategy sysctl.SysctlsStrategy
SeccompStrategy seccomp.Strategy
}

View File

@ -1,52 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"mustrunas.go",
"nonroot.go",
"runasany.go",
"types.go",
],
importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/user",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/policy:go_default_library",
"//pkg/security/podsecuritypolicy/util:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"mustrunas_test.go",
"nonroot_test.go",
"runasany_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/policy:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,19 +0,0 @@
/*
Copyright 2016 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 user contains code for validating and defaulting the UID of a pod
// or container according to a security policy.
package user

View File

@ -1,74 +0,0 @@
/*
Copyright 2016 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 user
import (
"fmt"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
)
// mustRunAs implements the RunAsUserStrategy interface
type mustRunAs struct {
opts *policy.RunAsUserStrategyOptions
}
// NewMustRunAs provides a strategy that requires the container to run as a specific UID in a range.
func NewMustRunAs(options *policy.RunAsUserStrategyOptions) (RunAsUserStrategy, error) {
if options == nil {
return nil, fmt.Errorf("MustRunAs requires run as user options")
}
if len(options.Ranges) == 0 {
return nil, fmt.Errorf("MustRunAs requires at least one range")
}
return &mustRunAs{
opts: options,
}, nil
}
// Generate creates the uid based on policy rules. MustRunAs returns the first range's Min.
func (s *mustRunAs) Generate(pod *api.Pod, container *api.Container) (*int64, error) {
return &s.opts.Ranges[0].Min, nil
}
// Validate ensures that the specified values fall within the range of the strategy.
func (s *mustRunAs) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList {
allErrs := field.ErrorList{}
if runAsUser == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("runAsUser"), ""))
return allErrs
}
if !s.isValidUID(*runAsUser) {
detail := fmt.Sprintf("must be in the ranges: %v", s.opts.Ranges)
allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *runAsUser, detail))
}
return allErrs
}
func (s *mustRunAs) isValidUID(id int64) bool {
for _, rng := range s.opts.Ranges {
if psputil.UserFallsInRange(id, rng) {
return true
}
}
return false
}

View File

@ -1,144 +0,0 @@
/*
Copyright 2016 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 user
import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
"strings"
"testing"
)
func TestNewMustRunAs(t *testing.T) {
tests := map[string]struct {
opts *policy.RunAsUserStrategyOptions
pass bool
}{
"nil opts": {
opts: nil,
pass: false,
},
"invalid opts": {
opts: &policy.RunAsUserStrategyOptions{},
pass: false,
},
"valid opts": {
opts: &policy.RunAsUserStrategyOptions{
Ranges: []policy.IDRange{
{Min: 1, Max: 1},
},
},
pass: true,
},
}
for name, tc := range tests {
_, err := NewMustRunAs(tc.opts)
if err != nil && tc.pass {
t.Errorf("%s expected to pass but received error %#v", name, err)
}
if err == nil && !tc.pass {
t.Errorf("%s expected to fail but did not receive an error", name)
}
}
}
func TestGenerate(t *testing.T) {
opts := &policy.RunAsUserStrategyOptions{
Ranges: []policy.IDRange{
{Min: 1, Max: 1},
},
}
mustRunAs, err := NewMustRunAs(opts)
if err != nil {
t.Fatalf("unexpected error initializing NewMustRunAs %v", err)
}
generated, err := mustRunAs.Generate(nil, nil)
if err != nil {
t.Fatalf("unexpected error generating runAsUser %v", err)
}
if *generated != opts.Ranges[0].Min {
t.Errorf("generated runAsUser does not equal configured runAsUser")
}
}
func TestValidate(t *testing.T) {
opts := &policy.RunAsUserStrategyOptions{
Ranges: []policy.IDRange{
{Min: 1, Max: 1},
{Min: 10, Max: 20},
},
}
validID := int64(15)
invalidID := int64(21)
tests := map[string]struct {
container *api.Container
expectedMsg string
}{
"good container": {
container: &api.Container{
SecurityContext: &api.SecurityContext{
RunAsUser: &validID,
},
},
},
"nil run as user": {
container: &api.Container{
SecurityContext: &api.SecurityContext{
RunAsUser: nil,
},
},
expectedMsg: "runAsUser: Required",
},
"invalid id": {
container: &api.Container{
SecurityContext: &api.SecurityContext{
RunAsUser: &invalidID,
},
},
expectedMsg: "runAsUser: Invalid",
},
}
for name, tc := range tests {
mustRunAs, err := NewMustRunAs(opts)
if err != nil {
t.Errorf("unexpected error initializing NewMustRunAs for testcase %s: %#v", name, err)
continue
}
errs := mustRunAs.Validate(nil, nil, nil, tc.container.SecurityContext.RunAsNonRoot, tc.container.SecurityContext.RunAsUser)
//should've passed but didn't
if len(tc.expectedMsg) == 0 && len(errs) > 0 {
t.Errorf("%s expected no errors but received %v", name, errs)
}
//should've failed but didn't
if len(tc.expectedMsg) != 0 && len(errs) == 0 {
t.Errorf("%s expected error %s but received no errors", name, tc.expectedMsg)
}
//failed with additional messages
if len(tc.expectedMsg) != 0 && len(errs) > 1 {
t.Errorf("%s expected error %s but received multiple errors: %v", name, tc.expectedMsg, errs)
}
//check that we got the right message
if len(tc.expectedMsg) != 0 && len(errs) == 1 {
if !strings.Contains(errs[0].Error(), tc.expectedMsg) {
t.Errorf("%s expected error to contain %s but it did not: %v", name, tc.expectedMsg, errs)
}
}
}
}

View File

@ -1,59 +0,0 @@
/*
Copyright 2016 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 user
import (
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
)
type nonRoot struct{}
var _ RunAsUserStrategy = &nonRoot{}
func NewRunAsNonRoot(options *policy.RunAsUserStrategyOptions) (RunAsUserStrategy, error) {
return &nonRoot{}, nil
}
// Generate creates the uid based on policy rules. This strategy does return a UID. It assumes
// that the user will specify a UID or the container image specifies a UID.
func (s *nonRoot) Generate(pod *api.Pod, container *api.Container) (*int64, error) {
return nil, nil
}
// Validate ensures that the specified values fall within the range of the strategy. Validation
// of this will pass if either the UID is not set, assuming that the image will provided the UID
// or if the UID is set it is not root. Validation will fail if RunAsNonRoot is set to false.
// In order to work properly this assumes that the kubelet performs a final check on runAsUser
// or the image UID when runAsUser is nil.
func (s *nonRoot) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList {
allErrs := field.ErrorList{}
if runAsNonRoot == nil && runAsUser == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("runAsNonRoot"), "must be true"))
return allErrs
}
if runAsNonRoot != nil && *runAsNonRoot == false {
allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsNonRoot"), *runAsNonRoot, "must be true"))
return allErrs
}
if runAsUser != nil && *runAsUser == 0 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("runAsUser"), *runAsUser, "running with the root UID is forbidden"))
return allErrs
}
return allErrs
}

View File

@ -1,119 +0,0 @@
/*
Copyright 2016 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 user
import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
"testing"
)
func TestNonRootOptions(t *testing.T) {
_, err := NewRunAsNonRoot(nil)
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsNonRoot %v", err)
}
_, err = NewRunAsNonRoot(&policy.RunAsUserStrategyOptions{})
if err != nil {
t.Errorf("unexpected error initializing NewRunAsNonRoot %v", err)
}
}
func TestNonRootGenerate(t *testing.T) {
s, err := NewRunAsNonRoot(&policy.RunAsUserStrategyOptions{})
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsNonRoot %v", err)
}
uid, err := s.Generate(nil, nil)
if uid != nil {
t.Errorf("expected nil uid but got %d", *uid)
}
if err != nil {
t.Errorf("unexpected error generating uid %v", err)
}
}
func TestNonRootValidate(t *testing.T) {
goodUID := int64(1)
badUID := int64(0)
untrue := false
unfalse := true
s, err := NewRunAsNonRoot(&policy.RunAsUserStrategyOptions{})
if err != nil {
t.Fatalf("unexpected error initializing NewMustRunAs %v", err)
}
tests := []struct {
container *api.Container
expectedErr bool
msg string
}{
{
container: &api.Container{
SecurityContext: &api.SecurityContext{
RunAsUser: &badUID,
},
},
expectedErr: true,
msg: "in test case %d, expected errors from root uid but got none: %v",
},
{
container: &api.Container{
SecurityContext: &api.SecurityContext{
RunAsUser: &goodUID,
},
},
expectedErr: false,
msg: "in test case %d, expected no errors from non-root uid but got %v",
},
{
container: &api.Container{
SecurityContext: &api.SecurityContext{
RunAsNonRoot: &untrue,
},
},
expectedErr: true,
msg: "in test case %d, expected errors from RunAsNonRoot but got none: %v",
},
{
container: &api.Container{
SecurityContext: &api.SecurityContext{
RunAsNonRoot: &unfalse,
RunAsUser: &goodUID,
},
},
expectedErr: false,
msg: "in test case %d, expected no errors from non-root uid but got %v",
},
{
container: &api.Container{
SecurityContext: &api.SecurityContext{
RunAsNonRoot: nil,
RunAsUser: nil,
},
},
expectedErr: true,
msg: "in test case %d, expected errors from nil runAsNonRoot and nil runAsUser but got %v",
},
}
for i, tc := range tests {
errs := s.Validate(nil, nil, nil, tc.container.SecurityContext.RunAsNonRoot, tc.container.SecurityContext.RunAsUser)
if (len(errs) == 0) == tc.expectedErr {
t.Errorf(tc.msg, i, errs)
}
}
}

View File

@ -1,43 +0,0 @@
/*
Copyright 2016 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 user
import (
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
)
// runAsAny implements the interface RunAsUserStrategy.
type runAsAny struct{}
var _ RunAsUserStrategy = &runAsAny{}
// NewRunAsAny provides a strategy that will return nil.
func NewRunAsAny(options *policy.RunAsUserStrategyOptions) (RunAsUserStrategy, error) {
return &runAsAny{}, nil
}
// Generate creates the uid based on policy rules.
func (s *runAsAny) Generate(pod *api.Pod, container *api.Container) (*int64, error) {
return nil, nil
}
// Validate ensures that the specified values fall within the range of the strategy.
func (s *runAsAny) Validate(fldPath *field.Path, _ *api.Pod, _ *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList {
return field.ErrorList{}
}

View File

@ -1,59 +0,0 @@
/*
Copyright 2016 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 user
import (
"testing"
"k8s.io/kubernetes/pkg/apis/policy"
)
func TestRunAsAnyOptions(t *testing.T) {
_, err := NewRunAsAny(nil)
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
}
_, err = NewRunAsAny(&policy.RunAsUserStrategyOptions{})
if err != nil {
t.Errorf("unexpected error initializing NewRunAsAny %v", err)
}
}
func TestRunAsAnyGenerate(t *testing.T) {
s, err := NewRunAsAny(&policy.RunAsUserStrategyOptions{})
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
}
uid, err := s.Generate(nil, nil)
if uid != nil {
t.Errorf("expected nil uid but got %d", *uid)
}
if err != nil {
t.Errorf("unexpected error generating uid %v", err)
}
}
func TestRunAsAnyValidate(t *testing.T) {
s, err := NewRunAsAny(&policy.RunAsUserStrategyOptions{})
if err != nil {
t.Fatalf("unexpected error initializing NewRunAsAny %v", err)
}
errs := s.Validate(nil, nil, nil, nil, nil)
if len(errs) != 0 {
t.Errorf("unexpected errors validating with ")
}
}

View File

@ -1,30 +0,0 @@
/*
Copyright 2016 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 user
import (
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
)
// RunAsUserStrategy defines the interface for all uid constraint strategies.
type RunAsUserStrategy interface {
// Generate creates the uid based on policy rules.
Generate(pod *api.Pod, container *api.Container) (*int64, error)
// Validate ensures that the specified values fall within the range of the strategy.
Validate(fldPath *field.Path, pod *api.Pod, container *api.Container, runAsNonRoot *bool, runAsUser *int64) field.ErrorList
}

View File

@ -1,44 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"util.go",
],
importpath = "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/policy:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["util_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/apis/policy:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,19 +0,0 @@
/*
Copyright 2016 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 util contains utility code shared amongst different parts of the
// pod security policy apparatus.
package util

View File

@ -1,242 +0,0 @@
/*
Copyright 2016 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 util
import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/util/sets"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
)
const (
ValidatedPSPAnnotation = "kubernetes.io/psp"
)
func GetAllFSTypesExcept(exceptions ...string) sets.String {
fstypes := GetAllFSTypesAsSet()
for _, e := range exceptions {
fstypes.Delete(e)
}
return fstypes
}
func GetAllFSTypesAsSet() sets.String {
fstypes := sets.NewString()
fstypes.Insert(
string(policy.HostPath),
string(policy.AzureFile),
string(policy.Flocker),
string(policy.FlexVolume),
string(policy.EmptyDir),
string(policy.GCEPersistentDisk),
string(policy.AWSElasticBlockStore),
string(policy.GitRepo),
string(policy.Secret),
string(policy.NFS),
string(policy.ISCSI),
string(policy.Glusterfs),
string(policy.PersistentVolumeClaim),
string(policy.RBD),
string(policy.Cinder),
string(policy.CephFS),
string(policy.DownwardAPI),
string(policy.FC),
string(policy.ConfigMap),
string(policy.VsphereVolume),
string(policy.Quobyte),
string(policy.AzureDisk),
string(policy.PhotonPersistentDisk),
string(policy.StorageOS),
string(policy.Projected),
string(policy.PortworxVolume),
string(policy.ScaleIO),
string(policy.CSI),
)
return fstypes
}
// getVolumeFSType gets the FSType for a volume.
func GetVolumeFSType(v api.Volume) (policy.FSType, error) {
switch {
case v.HostPath != nil:
return policy.HostPath, nil
case v.EmptyDir != nil:
return policy.EmptyDir, nil
case v.GCEPersistentDisk != nil:
return policy.GCEPersistentDisk, nil
case v.AWSElasticBlockStore != nil:
return policy.AWSElasticBlockStore, nil
case v.GitRepo != nil:
return policy.GitRepo, nil
case v.Secret != nil:
return policy.Secret, nil
case v.NFS != nil:
return policy.NFS, nil
case v.ISCSI != nil:
return policy.ISCSI, nil
case v.Glusterfs != nil:
return policy.Glusterfs, nil
case v.PersistentVolumeClaim != nil:
return policy.PersistentVolumeClaim, nil
case v.RBD != nil:
return policy.RBD, nil
case v.FlexVolume != nil:
return policy.FlexVolume, nil
case v.Cinder != nil:
return policy.Cinder, nil
case v.CephFS != nil:
return policy.CephFS, nil
case v.Flocker != nil:
return policy.Flocker, nil
case v.DownwardAPI != nil:
return policy.DownwardAPI, nil
case v.FC != nil:
return policy.FC, nil
case v.AzureFile != nil:
return policy.AzureFile, nil
case v.ConfigMap != nil:
return policy.ConfigMap, nil
case v.VsphereVolume != nil:
return policy.VsphereVolume, nil
case v.Quobyte != nil:
return policy.Quobyte, nil
case v.AzureDisk != nil:
return policy.AzureDisk, nil
case v.PhotonPersistentDisk != nil:
return policy.PhotonPersistentDisk, nil
case v.StorageOS != nil:
return policy.StorageOS, nil
case v.Projected != nil:
return policy.Projected, nil
case v.PortworxVolume != nil:
return policy.PortworxVolume, nil
case v.ScaleIO != nil:
return policy.ScaleIO, nil
}
return "", fmt.Errorf("unknown volume type for volume: %#v", v)
}
// FSTypeToStringSet converts an FSType slice to a string set.
func FSTypeToStringSet(fsTypes []policy.FSType) sets.String {
set := sets.NewString()
for _, v := range fsTypes {
set.Insert(string(v))
}
return set
}
// PSPAllowsAllVolumes checks for FSTypeAll in the psp's allowed volumes.
func PSPAllowsAllVolumes(psp *policy.PodSecurityPolicy) bool {
return PSPAllowsFSType(psp, policy.All)
}
// PSPAllowsFSType is a utility for checking if a PSP allows a particular FSType.
// If all volumes are allowed then this will return true for any FSType passed.
func PSPAllowsFSType(psp *policy.PodSecurityPolicy, fsType policy.FSType) bool {
if psp == nil {
return false
}
for _, v := range psp.Spec.Volumes {
if v == fsType || v == policy.All {
return true
}
}
return false
}
// UserFallsInRange is a utility to determine it the id falls in the valid range.
func UserFallsInRange(id int64, rng policy.IDRange) bool {
return id >= rng.Min && id <= rng.Max
}
// GroupFallsInRange is a utility to determine it the id falls in the valid range.
func GroupFallsInRange(id int64, rng policy.IDRange) bool {
return id >= rng.Min && id <= rng.Max
}
// AllowsHostVolumePath is a utility for checking if a PSP allows the host volume path.
// This only checks the path. You should still check to make sure the host volume fs type is allowed.
func AllowsHostVolumePath(psp *policy.PodSecurityPolicy, hostPath string) (pathIsAllowed, mustBeReadOnly bool) {
if psp == nil {
return false, false
}
// If no allowed paths are specified then allow any path
if len(psp.Spec.AllowedHostPaths) == 0 {
return true, false
}
for _, allowedPath := range psp.Spec.AllowedHostPaths {
if hasPathPrefix(hostPath, allowedPath.PathPrefix) {
if !allowedPath.ReadOnly {
return true, allowedPath.ReadOnly
}
pathIsAllowed = true
mustBeReadOnly = true
}
}
return pathIsAllowed, mustBeReadOnly
}
// hasPathPrefix returns true if the string matches pathPrefix exactly, or if is prefixed with pathPrefix at a path segment boundary
// the string and pathPrefix are both normalized to remove trailing slashes prior to checking.
func hasPathPrefix(s, pathPrefix string) bool {
s = strings.TrimSuffix(s, "/")
pathPrefix = strings.TrimSuffix(pathPrefix, "/")
// Short circuit if s doesn't contain the prefix at all
if !strings.HasPrefix(s, pathPrefix) {
return false
}
pathPrefixLength := len(pathPrefix)
if len(s) == pathPrefixLength {
// Exact match
return true
}
if s[pathPrefixLength:pathPrefixLength+1] == "/" {
// The next character in s is a path segment boundary
// Check this instead of normalizing pathPrefix to avoid allocating on every call
// Example where this check applies: s=/foo/bar and pathPrefix=/foo
return true
}
return false
}
// EqualStringSlices compares string slices for equality. Slices are equal when
// their sizes and elements on similar positions are equal.
func EqualStringSlices(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}

View File

@ -1,253 +0,0 @@
/*
Copyright 2016 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 util
import (
"reflect"
"testing"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
)
// TestVolumeSourceFSTypeDrift ensures that for every known type of volume source (by the fields on
// a VolumeSource object that GetVolumeFSType is returning a good value. This ensures both that we're
// returning an FSType for the VolumeSource field (protect the GetVolumeFSType method) and that we
// haven't drifted (ensure new fields in VolumeSource are covered).
func TestVolumeSourceFSTypeDrift(t *testing.T) {
allFSTypes := GetAllFSTypesAsSet()
val := reflect.ValueOf(api.VolumeSource{})
for i := 0; i < val.NumField(); i++ {
fieldVal := val.Type().Field(i)
volumeSource := api.VolumeSource{}
volumeSourceVolume := reflect.New(fieldVal.Type.Elem())
reflect.ValueOf(&volumeSource).Elem().FieldByName(fieldVal.Name).Set(volumeSourceVolume)
fsType, err := GetVolumeFSType(api.Volume{VolumeSource: volumeSource})
if err != nil {
t.Errorf("error getting fstype for field %s. This likely means that drift has occurred between FSType and VolumeSource. Please update the api and getVolumeFSType", fieldVal.Name)
}
if !allFSTypes.Has(string(fsType)) {
t.Errorf("%s was missing from GetFSTypesAsSet", fsType)
}
}
}
func TestPSPAllowsFSType(t *testing.T) {
tests := map[string]struct {
psp *policy.PodSecurityPolicy
fsType policy.FSType
allows bool
}{
"nil psp": {
psp: nil,
fsType: policy.HostPath,
allows: false,
},
"empty volumes": {
psp: &policy.PodSecurityPolicy{},
fsType: policy.HostPath,
allows: false,
},
"non-matching": {
psp: &policy.PodSecurityPolicy{
Spec: policy.PodSecurityPolicySpec{
Volumes: []policy.FSType{policy.AWSElasticBlockStore},
},
},
fsType: policy.HostPath,
allows: false,
},
"match on FSTypeAll": {
psp: &policy.PodSecurityPolicy{
Spec: policy.PodSecurityPolicySpec{
Volumes: []policy.FSType{policy.All},
},
},
fsType: policy.HostPath,
allows: true,
},
"match on direct match": {
psp: &policy.PodSecurityPolicy{
Spec: policy.PodSecurityPolicySpec{
Volumes: []policy.FSType{policy.HostPath},
},
},
fsType: policy.HostPath,
allows: true,
},
}
for k, v := range tests {
allows := PSPAllowsFSType(v.psp, v.fsType)
if v.allows != allows {
t.Errorf("%s expected PSPAllowsFSType to return %t but got %t", k, v.allows, allows)
}
}
}
func TestAllowsHostVolumePath(t *testing.T) {
tests := map[string]struct {
psp *policy.PodSecurityPolicy
path string
allows bool
mustBeReadOnly bool
}{
"nil psp": {
psp: nil,
path: "/test",
allows: false,
mustBeReadOnly: false,
},
"empty allowed paths": {
psp: &policy.PodSecurityPolicy{},
path: "/test",
allows: true,
mustBeReadOnly: false,
},
"non-matching": {
psp: &policy.PodSecurityPolicy{
Spec: policy.PodSecurityPolicySpec{
AllowedHostPaths: []policy.AllowedHostPath{
{
PathPrefix: "/foo",
ReadOnly: true,
},
},
},
},
path: "/foobar",
allows: false,
mustBeReadOnly: false,
},
"match on direct match": {
psp: &policy.PodSecurityPolicy{
Spec: policy.PodSecurityPolicySpec{
AllowedHostPaths: []policy.AllowedHostPath{
{
PathPrefix: "/foo",
ReadOnly: true,
},
},
},
},
path: "/foo",
allows: true,
mustBeReadOnly: true,
},
"match with trailing slash on host path": {
psp: &policy.PodSecurityPolicy{
Spec: policy.PodSecurityPolicySpec{
AllowedHostPaths: []policy.AllowedHostPath{
{PathPrefix: "/foo"},
},
},
},
path: "/foo/",
allows: true,
mustBeReadOnly: false,
},
"match with trailing slash on allowed path": {
psp: &policy.PodSecurityPolicy{
Spec: policy.PodSecurityPolicySpec{
AllowedHostPaths: []policy.AllowedHostPath{
{PathPrefix: "/foo/"},
},
},
},
path: "/foo",
allows: true,
mustBeReadOnly: false,
},
"match child directory": {
psp: &policy.PodSecurityPolicy{
Spec: policy.PodSecurityPolicySpec{
AllowedHostPaths: []policy.AllowedHostPath{
{
PathPrefix: "/foo/",
ReadOnly: true,
},
},
},
},
path: "/foo/bar",
allows: true,
mustBeReadOnly: true,
},
"non-matching parent directory": {
psp: &policy.PodSecurityPolicy{
Spec: policy.PodSecurityPolicySpec{
AllowedHostPaths: []policy.AllowedHostPath{
{PathPrefix: "/foo/bar"},
},
},
},
path: "/foo",
allows: false,
mustBeReadOnly: false,
},
}
for k, v := range tests {
allows, mustBeReadOnly := AllowsHostVolumePath(v.psp, v.path)
if v.allows != allows {
t.Errorf("allows: %s expected %t but got %t", k, v.allows, allows)
}
if v.mustBeReadOnly != mustBeReadOnly {
t.Errorf("mustBeReadOnly: %s expected %t but got %t", k, v.mustBeReadOnly, mustBeReadOnly)
}
}
}
func TestEqualStringSlices(t *testing.T) {
tests := map[string]struct {
arg1 []string
arg2 []string
expectedResult bool
}{
"nil equals to nil": {
arg1: nil,
arg2: nil,
expectedResult: true,
},
"equal by size": {
arg1: []string{"1", "1"},
arg2: []string{"1", "1"},
expectedResult: true,
},
"not equal by size": {
arg1: []string{"1"},
arg2: []string{"1", "1"},
expectedResult: false,
},
"not equal by elements": {
arg1: []string{"1", "1"},
arg2: []string{"1", "2"},
expectedResult: false,
},
}
for k, v := range tests {
if result := EqualStringSlices(v.arg1, v.arg2); result != v.expectedResult {
t.Errorf("%s expected to return %t but got %t", k, v.expectedResult, result)
}
}
}