mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 05:57:25 +00:00
Merge pull request #89193 from zshihang/master
set proper file permission for projected service account volume
This commit is contained in:
commit
3365ed5625
@ -32,6 +32,7 @@ go_test(
|
|||||||
"//pkg/apis/core:go_default_library",
|
"//pkg/apis/core:go_default_library",
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||||
|
"//vendor/k8s.io/utils/pointer:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
package securitycontext
|
package securitycontext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HasPrivilegedRequest returns the value of SecurityContext.Privileged, taking into account
|
// HasPrivilegedRequest returns the value of SecurityContext.Privileged, taking into account
|
||||||
@ -124,6 +124,25 @@ func DetermineEffectiveSecurityContext(pod *v1.Pod, container *v1.Container) *v1
|
|||||||
return effectiveSc
|
return effectiveSc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DetermineEffectiveRunAsUser returns a pointer of UID from the provided pod's
|
||||||
|
// and container's security context and a bool value to indicate if it is absent.
|
||||||
|
// Container's runAsUser take precedence in cases where both are set.
|
||||||
|
func DetermineEffectiveRunAsUser(pod *v1.Pod, container *v1.Container) (*int64, bool) {
|
||||||
|
var runAsUser *int64
|
||||||
|
if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.RunAsUser != nil {
|
||||||
|
runAsUser = new(int64)
|
||||||
|
*runAsUser = *pod.Spec.SecurityContext.RunAsUser
|
||||||
|
}
|
||||||
|
if container.SecurityContext != nil && container.SecurityContext.RunAsUser != nil {
|
||||||
|
runAsUser = new(int64)
|
||||||
|
*runAsUser = *container.SecurityContext.RunAsUser
|
||||||
|
}
|
||||||
|
if runAsUser == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return runAsUser, true
|
||||||
|
}
|
||||||
|
|
||||||
func securityContextFromPodSecurityContext(pod *v1.Pod) *v1.SecurityContext {
|
func securityContextFromPodSecurityContext(pod *v1.Pod) *v1.SecurityContext {
|
||||||
if pod.Spec.SecurityContext == nil {
|
if pod.Spec.SecurityContext == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -20,7 +20,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
utilptr "k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAddNoNewPrivileges(t *testing.T) {
|
func TestAddNoNewPrivileges(t *testing.T) {
|
||||||
@ -120,3 +121,92 @@ func TestConvertToRuntimeReadonlyPaths(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDetermineEffectiveRunAsUser(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
pod *v1.Pod
|
||||||
|
container *v1.Container
|
||||||
|
wantRunAsUser *int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "no securityContext in pod, no securityContext in container",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{},
|
||||||
|
},
|
||||||
|
container: &v1.Container{},
|
||||||
|
wantRunAsUser: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no runAsUser in pod, no runAsUser in container",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
SecurityContext: &v1.PodSecurityContext{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
container: &v1.Container{
|
||||||
|
SecurityContext: &v1.SecurityContext{},
|
||||||
|
},
|
||||||
|
wantRunAsUser: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "runAsUser in pod, no runAsUser in container",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
SecurityContext: &v1.PodSecurityContext{
|
||||||
|
RunAsUser: new(int64),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
container: &v1.Container{
|
||||||
|
SecurityContext: &v1.SecurityContext{},
|
||||||
|
},
|
||||||
|
wantRunAsUser: new(int64),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no runAsUser in pod, runAsUser in container",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
SecurityContext: &v1.PodSecurityContext{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
container: &v1.Container{
|
||||||
|
SecurityContext: &v1.SecurityContext{
|
||||||
|
RunAsUser: new(int64),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantRunAsUser: new(int64),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "no runAsUser in pod, runAsUser in container",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
SecurityContext: &v1.PodSecurityContext{
|
||||||
|
RunAsUser: new(int64),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
container: &v1.Container{
|
||||||
|
SecurityContext: &v1.SecurityContext{
|
||||||
|
RunAsUser: utilptr.Int64Ptr(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantRunAsUser: utilptr.Int64Ptr(1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
runAsUser, ok := DetermineEffectiveRunAsUser(test.pod, test.container)
|
||||||
|
if !ok && test.wantRunAsUser != nil {
|
||||||
|
t.Errorf("DetermineEffectiveRunAsUser(%v, %v) = %v, want %d", test.pod, test.container, runAsUser, *test.wantRunAsUser)
|
||||||
|
}
|
||||||
|
if ok && test.wantRunAsUser == nil {
|
||||||
|
t.Errorf("DetermineEffectiveRunAsUser(%v, %v) = %d, want %v", test.pod, test.container, *runAsUser, test.wantRunAsUser)
|
||||||
|
}
|
||||||
|
if ok && test.wantRunAsUser != nil && *runAsUser != *test.wantRunAsUser {
|
||||||
|
t.Errorf("DetermineEffectiveRunAsUser(%v, %v) = %d, want %d", test.pod, test.container, *runAsUser, *test.wantRunAsUser)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -22,11 +22,12 @@ go_test(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/testing:go_default_library",
|
"//staging/src/k8s.io/client-go/testing:go_default_library",
|
||||||
|
"//vendor/github.com/google/go-cmp/cmp:go_default_library",
|
||||||
|
"//vendor/k8s.io/utils/pointer:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ func (s *projectedVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterA
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := s.collectData()
|
data, err := s.collectData(mounterArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Errorf("Error preparing data for projected volume %v for pod %v/%v: %s", s.volName, s.pod.Namespace, s.pod.Name, err.Error())
|
klog.Errorf("Error preparing data for projected volume %v for pod %v/%v: %s", s.volName, s.pod.Namespace, s.pod.Name, err.Error())
|
||||||
return err
|
return err
|
||||||
@ -248,7 +248,7 @@ func (s *projectedVolumeMounter) SetUpAt(dir string, mounterArgs volume.MounterA
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *projectedVolumeMounter) collectData() (map[string]volumeutil.FileProjection, error) {
|
func (s *projectedVolumeMounter) collectData(mounterArgs volume.MounterArgs) (map[string]volumeutil.FileProjection, error) {
|
||||||
if s.source.DefaultMode == nil {
|
if s.source.DefaultMode == nil {
|
||||||
return nil, fmt.Errorf("No defaultMode used, not even the default value for it")
|
return nil, fmt.Errorf("No defaultMode used, not even the default value for it")
|
||||||
}
|
}
|
||||||
@ -328,6 +328,13 @@ func (s *projectedVolumeMounter) collectData() (map[string]volumeutil.FileProjec
|
|||||||
}
|
}
|
||||||
tp := source.ServiceAccountToken
|
tp := source.ServiceAccountToken
|
||||||
|
|
||||||
|
// When FsGroup is set, we depend on SetVolumeOwnership to
|
||||||
|
// change from 0600 to 0640.
|
||||||
|
mode := *s.source.DefaultMode
|
||||||
|
if mounterArgs.FsUser != nil || mounterArgs.FsGroup != nil {
|
||||||
|
mode = 0600
|
||||||
|
}
|
||||||
|
|
||||||
var auds []string
|
var auds []string
|
||||||
if len(tp.Audience) != 0 {
|
if len(tp.Audience) != 0 {
|
||||||
auds = []string{tp.Audience}
|
auds = []string{tp.Audience}
|
||||||
@ -350,7 +357,8 @@ func (s *projectedVolumeMounter) collectData() (map[string]volumeutil.FileProjec
|
|||||||
}
|
}
|
||||||
payload[tp.Path] = volumeutil.FileProjection{
|
payload[tp.Path] = volumeutil.FileProjection{
|
||||||
Data: []byte(tr.Status.Token),
|
Data: []byte(tr.Status.Token),
|
||||||
Mode: 0600,
|
Mode: mode,
|
||||||
|
FsUser: mounterArgs.FsUser,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
authenticationv1 "k8s.io/api/authentication/v1"
|
authenticationv1 "k8s.io/api/authentication/v1"
|
||||||
"k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/diff"
|
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/kubernetes/fake"
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
@ -41,6 +41,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/volume/emptydir"
|
"k8s.io/kubernetes/pkg/volume/emptydir"
|
||||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||||
"k8s.io/kubernetes/pkg/volume/util"
|
"k8s.io/kubernetes/pkg/volume/util"
|
||||||
|
utilptr "k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCollectDataWithSecret(t *testing.T) {
|
func TestCollectDataWithSecret(t *testing.T) {
|
||||||
@ -253,7 +254,7 @@ func TestCollectDataWithSecret(t *testing.T) {
|
|||||||
Name: tc.name,
|
Name: tc.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
source := makeProjection(tc.name, tc.mode, "secret")
|
source := makeProjection(tc.name, utilptr.Int32Ptr(tc.mode), "secret")
|
||||||
source.Sources[0].Secret.Items = tc.mappings
|
source.Sources[0].Secret.Items = tc.mappings
|
||||||
source.Sources[0].Secret.Optional = &tc.optional
|
source.Sources[0].Secret.Optional = &tc.optional
|
||||||
|
|
||||||
@ -275,7 +276,7 @@ func TestCollectDataWithSecret(t *testing.T) {
|
|||||||
pod: pod,
|
pod: pod,
|
||||||
}
|
}
|
||||||
|
|
||||||
actualPayload, err := myVolumeMounter.collectData()
|
actualPayload, err := myVolumeMounter.collectData(volume.MounterArgs{})
|
||||||
if err != nil && tc.success {
|
if err != nil && tc.success {
|
||||||
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
|
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
|
||||||
return
|
return
|
||||||
@ -502,7 +503,7 @@ func TestCollectDataWithConfigMap(t *testing.T) {
|
|||||||
Name: tc.name,
|
Name: tc.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
source := makeProjection(tc.name, tc.mode, "configMap")
|
source := makeProjection(tc.name, utilptr.Int32Ptr(tc.mode), "configMap")
|
||||||
source.Sources[0].ConfigMap.Items = tc.mappings
|
source.Sources[0].ConfigMap.Items = tc.mappings
|
||||||
source.Sources[0].ConfigMap.Optional = &tc.optional
|
source.Sources[0].ConfigMap.Optional = &tc.optional
|
||||||
|
|
||||||
@ -524,7 +525,7 @@ func TestCollectDataWithConfigMap(t *testing.T) {
|
|||||||
pod: pod,
|
pod: pod,
|
||||||
}
|
}
|
||||||
|
|
||||||
actualPayload, err := myVolumeMounter.collectData()
|
actualPayload, err := myVolumeMounter.collectData(volume.MounterArgs{})
|
||||||
if err != nil && tc.success {
|
if err != nil && tc.success {
|
||||||
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
|
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
|
||||||
return
|
return
|
||||||
@ -676,7 +677,7 @@ func TestCollectDataWithDownwardAPI(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
source := makeProjection("", tc.mode, "downwardAPI")
|
source := makeProjection("", utilptr.Int32Ptr(tc.mode), "downwardAPI")
|
||||||
source.Sources[0].DownwardAPI.Items = tc.volumeFile
|
source.Sources[0].DownwardAPI.Items = tc.volumeFile
|
||||||
|
|
||||||
client := fake.NewSimpleClientset(tc.pod)
|
client := fake.NewSimpleClientset(tc.pod)
|
||||||
@ -694,7 +695,7 @@ func TestCollectDataWithDownwardAPI(t *testing.T) {
|
|||||||
pod: tc.pod,
|
pod: tc.pod,
|
||||||
}
|
}
|
||||||
|
|
||||||
actualPayload, err := myVolumeMounter.collectData()
|
actualPayload, err := myVolumeMounter.collectData(volume.MounterArgs{})
|
||||||
if err != nil && tc.success {
|
if err != nil && tc.success {
|
||||||
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
|
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
|
||||||
return
|
return
|
||||||
@ -724,47 +725,97 @@ func TestCollectDataWithServiceAccountToken(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
svcacct string
|
svcacct string
|
||||||
audience string
|
audience string
|
||||||
|
defaultMode *int32
|
||||||
|
fsUser *int64
|
||||||
|
fsGroup *int64
|
||||||
expiration *int64
|
expiration *int64
|
||||||
path string
|
path string
|
||||||
|
|
||||||
payload map[string]util.FileProjection
|
wantPayload map[string]util.FileProjection
|
||||||
|
wantErr error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "test good service account",
|
name: "good service account",
|
||||||
audience: "https://example.com",
|
audience: "https://example.com",
|
||||||
|
defaultMode: utilptr.Int32Ptr(0644),
|
||||||
path: "token",
|
path: "token",
|
||||||
expiration: &minute,
|
expiration: &minute,
|
||||||
|
|
||||||
payload: map[string]util.FileProjection{
|
wantPayload: map[string]util.FileProjection{
|
||||||
"token": {Data: []byte("test_projected_namespace:foo:60:[https://example.com]"), Mode: 0600},
|
"token": {Data: []byte("test_projected_namespace:foo:60:[https://example.com]"), Mode: 0644},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test good service account other path",
|
name: "good service account other path",
|
||||||
audience: "https://example.com",
|
audience: "https://example.com",
|
||||||
|
defaultMode: utilptr.Int32Ptr(0644),
|
||||||
path: "other-token",
|
path: "other-token",
|
||||||
expiration: &minute,
|
expiration: &minute,
|
||||||
|
wantPayload: map[string]util.FileProjection{
|
||||||
payload: map[string]util.FileProjection{
|
"other-token": {Data: []byte("test_projected_namespace:foo:60:[https://example.com]"), Mode: 0644},
|
||||||
"other-token": {Data: []byte("test_projected_namespace:foo:60:[https://example.com]"), Mode: 0600},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test good service account defaults audience",
|
name: "good service account defaults audience",
|
||||||
|
defaultMode: utilptr.Int32Ptr(0644),
|
||||||
path: "token",
|
path: "token",
|
||||||
expiration: &minute,
|
expiration: &minute,
|
||||||
|
|
||||||
payload: map[string]util.FileProjection{
|
wantPayload: map[string]util.FileProjection{
|
||||||
"token": {Data: []byte("test_projected_namespace:foo:60:[https://api]"), Mode: 0600},
|
"token": {Data: []byte("test_projected_namespace:foo:60:[https://api]"), Mode: 0644},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test good service account defaults expiration",
|
name: "good service account defaults expiration",
|
||||||
audience: "https://example.com",
|
defaultMode: utilptr.Int32Ptr(0644),
|
||||||
path: "token",
|
path: "token",
|
||||||
|
|
||||||
payload: map[string]util.FileProjection{
|
wantPayload: map[string]util.FileProjection{
|
||||||
"token": {Data: []byte("test_projected_namespace:foo:3600:[https://example.com]"), Mode: 0600},
|
"token": {Data: []byte("test_projected_namespace:foo:3600:[https://api]"), Mode: 0644},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no default mode",
|
||||||
|
path: "token",
|
||||||
|
wantErr: fmt.Errorf("No defaultMode used, not even the default value for it"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fsUser != nil",
|
||||||
|
defaultMode: utilptr.Int32Ptr(0644),
|
||||||
|
fsUser: utilptr.Int64Ptr(1000),
|
||||||
|
path: "token",
|
||||||
|
wantPayload: map[string]util.FileProjection{
|
||||||
|
"token": {
|
||||||
|
Data: []byte("test_projected_namespace:foo:3600:[https://api]"),
|
||||||
|
Mode: 0600,
|
||||||
|
FsUser: utilptr.Int64Ptr(1000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fsGroup != nil",
|
||||||
|
defaultMode: utilptr.Int32Ptr(0644),
|
||||||
|
fsGroup: utilptr.Int64Ptr(1000),
|
||||||
|
path: "token",
|
||||||
|
wantPayload: map[string]util.FileProjection{
|
||||||
|
"token": {
|
||||||
|
Data: []byte("test_projected_namespace:foo:3600:[https://api]"),
|
||||||
|
Mode: 0600,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fsUser != nil && fsGroup != nil",
|
||||||
|
defaultMode: utilptr.Int32Ptr(0644),
|
||||||
|
fsGroup: utilptr.Int64Ptr(1000),
|
||||||
|
fsUser: utilptr.Int64Ptr(1000),
|
||||||
|
path: "token",
|
||||||
|
wantPayload: map[string]util.FileProjection{
|
||||||
|
"token": {
|
||||||
|
Data: []byte("test_projected_namespace:foo:3600:[https://api]"),
|
||||||
|
Mode: 0600,
|
||||||
|
FsUser: utilptr.Int64Ptr(1000),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -772,7 +823,7 @@ func TestCollectDataWithServiceAccountToken(t *testing.T) {
|
|||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
testNamespace := "test_projected_namespace"
|
testNamespace := "test_projected_namespace"
|
||||||
source := makeProjection(tc.name, 0600, "serviceAccountToken")
|
source := makeProjection(tc.name, tc.defaultMode, "serviceAccountToken")
|
||||||
source.Sources[0].ServiceAccountToken.Audience = tc.audience
|
source.Sources[0].ServiceAccountToken.Audience = tc.audience
|
||||||
source.Sources[0].ServiceAccountToken.ExpirationSeconds = tc.expiration
|
source.Sources[0].ServiceAccountToken.ExpirationSeconds = tc.expiration
|
||||||
source.Sources[0].ServiceAccountToken.Path = tc.path
|
source.Sources[0].ServiceAccountToken.Path = tc.path
|
||||||
@ -811,12 +862,12 @@ func TestCollectDataWithServiceAccountToken(t *testing.T) {
|
|||||||
pod: pod,
|
pod: pod,
|
||||||
}
|
}
|
||||||
|
|
||||||
actualPayload, err := myVolumeMounter.collectData()
|
gotPayload, err := myVolumeMounter.collectData(volume.MounterArgs{FsUser: tc.fsUser, FsGroup: tc.fsGroup})
|
||||||
if err != nil {
|
if err != nil && (tc.wantErr == nil || tc.wantErr.Error() != err.Error()) {
|
||||||
t.Fatalf("unexpected failure making payload: %v", err)
|
t.Fatalf("collectData() = unexpected err: %v", err)
|
||||||
}
|
}
|
||||||
if e, a := tc.payload, actualPayload; !reflect.DeepEqual(e, a) {
|
if diff := cmp.Diff(tc.wantPayload, gotPayload); diff != "" {
|
||||||
t.Errorf("expected and actual payload do not match:\n%s", diff.ObjectReflectDiff(e, a))
|
t.Errorf("collectData() = unexpected diff (-want +got):\n%s", diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1184,7 +1235,7 @@ func makeVolumeSpec(volumeName, name string, defaultMode int32) *v1.Volume {
|
|||||||
return &v1.Volume{
|
return &v1.Volume{
|
||||||
Name: volumeName,
|
Name: volumeName,
|
||||||
VolumeSource: v1.VolumeSource{
|
VolumeSource: v1.VolumeSource{
|
||||||
Projected: makeProjection(name, defaultMode, "secret"),
|
Projected: makeProjection(name, utilptr.Int32Ptr(defaultMode), "secret"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1203,7 +1254,7 @@ func makeSecret(namespace, name string) v1.Secret {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeProjection(name string, defaultMode int32, kind string) *v1.ProjectedVolumeSource {
|
func makeProjection(name string, defaultMode *int32, kind string) *v1.ProjectedVolumeSource {
|
||||||
var item v1.VolumeProjection
|
var item v1.VolumeProjection
|
||||||
|
|
||||||
switch kind {
|
switch kind {
|
||||||
@ -1231,7 +1282,7 @@ func makeProjection(name string, defaultMode int32, kind string) *v1.ProjectedVo
|
|||||||
|
|
||||||
return &v1.ProjectedVolumeSource{
|
return &v1.ProjectedVolumeSource{
|
||||||
Sources: []v1.VolumeProjection{item},
|
Sources: []v1.VolumeProjection{item},
|
||||||
DefaultMode: &defaultMode,
|
DefaultMode: defaultMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ go_library(
|
|||||||
"//pkg/api/legacyscheme:go_default_library",
|
"//pkg/api/legacyscheme:go_default_library",
|
||||||
"//pkg/api/v1/pod:go_default_library",
|
"//pkg/api/v1/pod:go_default_library",
|
||||||
"//pkg/apis/core/v1/helper:go_default_library",
|
"//pkg/apis/core/v1/helper:go_default_library",
|
||||||
|
"//pkg/securitycontext:go_default_library",
|
||||||
"//pkg/util/resizefs:go_default_library",
|
"//pkg/util/resizefs:go_default_library",
|
||||||
"//pkg/volume:go_default_library",
|
"//pkg/volume:go_default_library",
|
||||||
"//pkg/volume/util/types:go_default_library",
|
"//pkg/volume/util/types:go_default_library",
|
||||||
@ -71,6 +72,7 @@ go_test(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||||
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
|
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
|
||||||
|
"//vendor/k8s.io/utils/pointer:go_default_library",
|
||||||
] + select({
|
] + select({
|
||||||
"@io_bazel_rules_go//go/platform:android": [
|
"@io_bazel_rules_go//go/platform:android": [
|
||||||
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
|
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
|
||||||
|
@ -65,6 +65,7 @@ type AtomicWriter struct {
|
|||||||
type FileProjection struct {
|
type FileProjection struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
Mode int32
|
Mode int32
|
||||||
|
FsUser *int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAtomicWriter creates a new AtomicWriter configured to write to the given
|
// NewAtomicWriter creates a new AtomicWriter configured to write to the given
|
||||||
@ -378,14 +379,12 @@ func (w *AtomicWriter) writePayloadToDir(payload map[string]FileProjection, dir
|
|||||||
fullPath := filepath.Join(dir, userVisiblePath)
|
fullPath := filepath.Join(dir, userVisiblePath)
|
||||||
baseDir, _ := filepath.Split(fullPath)
|
baseDir, _ := filepath.Split(fullPath)
|
||||||
|
|
||||||
err := os.MkdirAll(baseDir, os.ModePerm)
|
if err := os.MkdirAll(baseDir, os.ModePerm); err != nil {
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("%s: unable to create directory %s: %v", w.logContext, baseDir, err)
|
klog.Errorf("%s: unable to create directory %s: %v", w.logContext, baseDir, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(fullPath, content, mode)
|
if err := ioutil.WriteFile(fullPath, content, mode); err != nil {
|
||||||
if err != nil {
|
|
||||||
klog.Errorf("%s: unable to write file %s with mode %v: %v", w.logContext, fullPath, mode, err)
|
klog.Errorf("%s: unable to write file %s with mode %v: %v", w.logContext, fullPath, mode, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -393,9 +392,17 @@ func (w *AtomicWriter) writePayloadToDir(payload map[string]FileProjection, dir
|
|||||||
// open(2) to create the file, so the final mode used is "mode &
|
// open(2) to create the file, so the final mode used is "mode &
|
||||||
// ~umask". But we want to make sure the specified mode is used
|
// ~umask". But we want to make sure the specified mode is used
|
||||||
// in the file no matter what the umask is.
|
// in the file no matter what the umask is.
|
||||||
err = os.Chmod(fullPath, mode)
|
if err := os.Chmod(fullPath, mode); err != nil {
|
||||||
if err != nil {
|
klog.Errorf("%s: unable to change file %s with mode %v: %v", w.logContext, fullPath, mode, err)
|
||||||
klog.Errorf("%s: unable to write file %s with mode %v: %v", w.logContext, fullPath, mode, err)
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileProjection.FsUser == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := os.Chown(fullPath, int(*fileProjection.FsUser), -1); err != nil {
|
||||||
|
klog.Errorf("%s: unable to change file %s with owner %v: %v", w.logContext, fullPath, int(*fileProjection.FsUser), err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,6 +627,7 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
|
|||||||
|
|
||||||
// Execute mount
|
// Execute mount
|
||||||
mountErr := volumeMounter.SetUp(volume.MounterArgs{
|
mountErr := volumeMounter.SetUp(volume.MounterArgs{
|
||||||
|
FsUser: ioutil.FsUserFrom(volumeToMount.Pod),
|
||||||
FsGroup: fsGroup,
|
FsGroup: fsGroup,
|
||||||
DesiredSize: volumeToMount.DesiredSizeLimit,
|
DesiredSize: volumeToMount.DesiredSizeLimit,
|
||||||
FSGroupChangePolicy: fsGroupChangePolicy,
|
FSGroupChangePolicy: fsGroupChangePolicy,
|
||||||
|
@ -43,6 +43,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||||
|
"k8s.io/kubernetes/pkg/securitycontext"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
"k8s.io/kubernetes/pkg/volume/util/types"
|
"k8s.io/kubernetes/pkg/volume/util/types"
|
||||||
"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
|
"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
|
||||||
@ -608,6 +609,27 @@ func GetPodVolumeNames(pod *v1.Pod) (mounts sets.String, devices sets.String) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FsUserFrom returns FsUser of pod, which is determined by the runAsUser
|
||||||
|
// attributes.
|
||||||
|
func FsUserFrom(pod *v1.Pod) *int64 {
|
||||||
|
var fsUser *int64
|
||||||
|
// Exclude ephemeral containers because SecurityContext is not allowed.
|
||||||
|
podutil.VisitContainers(&pod.Spec, podutil.InitContainers|podutil.Containers, func(container *v1.Container, containerType podutil.ContainerType) bool {
|
||||||
|
runAsUser, ok := securitycontext.DetermineEffectiveRunAsUser(pod, container)
|
||||||
|
// One container doesn't specify user or there are more than one
|
||||||
|
// non-root UIDs.
|
||||||
|
if !ok || (fsUser != nil && *fsUser != *runAsUser) {
|
||||||
|
fsUser = nil
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if fsUser == nil {
|
||||||
|
fsUser = runAsUser
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return fsUser
|
||||||
|
}
|
||||||
|
|
||||||
// HasMountRefs checks if the given mountPath has mountRefs.
|
// HasMountRefs checks if the given mountPath has mountRefs.
|
||||||
// TODO: this is a workaround for the unmount device issue caused by gci mounter.
|
// TODO: this is a workaround for the unmount device issue caused by gci mounter.
|
||||||
// In GCI cluster, if gci mounter is used for mounting, the container started by mounter
|
// In GCI cluster, if gci mounter is used for mounting, the container started by mounter
|
||||||
|
@ -19,25 +19,22 @@ package util
|
|||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/util/slice"
|
"k8s.io/kubernetes/pkg/util/slice"
|
||||||
"k8s.io/kubernetes/pkg/volume"
|
"k8s.io/kubernetes/pkg/volume"
|
||||||
|
utilptr "k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nodeLabels = map[string]string{
|
var nodeLabels = map[string]string{
|
||||||
@ -344,6 +341,119 @@ func TestCalculateTimeoutForVolume(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFsUserFrom(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
pod *v1.Pod
|
||||||
|
wantFsUser *int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "no runAsUser specified",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{},
|
||||||
|
},
|
||||||
|
wantFsUser: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "some have runAsUser specified",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
SecurityContext: &v1.PodSecurityContext{},
|
||||||
|
InitContainers: []v1.Container{
|
||||||
|
{
|
||||||
|
SecurityContext: &v1.SecurityContext{
|
||||||
|
RunAsUser: utilptr.Int64Ptr(1000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
SecurityContext: &v1.SecurityContext{
|
||||||
|
RunAsUser: utilptr.Int64Ptr(1000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SecurityContext: &v1.SecurityContext{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantFsUser: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all have runAsUser specified but not the same",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
SecurityContext: &v1.PodSecurityContext{},
|
||||||
|
InitContainers: []v1.Container{
|
||||||
|
{
|
||||||
|
SecurityContext: &v1.SecurityContext{
|
||||||
|
RunAsUser: utilptr.Int64Ptr(999),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
SecurityContext: &v1.SecurityContext{
|
||||||
|
RunAsUser: utilptr.Int64Ptr(1000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SecurityContext: &v1.SecurityContext{
|
||||||
|
RunAsUser: utilptr.Int64Ptr(1000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantFsUser: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "all have runAsUser specified and the same",
|
||||||
|
pod: &v1.Pod{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
SecurityContext: &v1.PodSecurityContext{},
|
||||||
|
InitContainers: []v1.Container{
|
||||||
|
{
|
||||||
|
SecurityContext: &v1.SecurityContext{
|
||||||
|
RunAsUser: utilptr.Int64Ptr(1000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
SecurityContext: &v1.SecurityContext{
|
||||||
|
RunAsUser: utilptr.Int64Ptr(1000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SecurityContext: &v1.SecurityContext{
|
||||||
|
RunAsUser: utilptr.Int64Ptr(1000),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantFsUser: utilptr.Int64Ptr(1000),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
fsUser := FsUserFrom(test.pod)
|
||||||
|
if fsUser == nil && test.wantFsUser != nil {
|
||||||
|
t.Errorf("FsUserFrom(%v) = %v, want %d", test.pod, fsUser, *test.wantFsUser)
|
||||||
|
}
|
||||||
|
if fsUser != nil && test.wantFsUser == nil {
|
||||||
|
t.Errorf("FsUserFrom(%v) = %d, want %v", test.pod, *fsUser, test.wantFsUser)
|
||||||
|
}
|
||||||
|
if fsUser != nil && test.wantFsUser != nil && *fsUser != *test.wantFsUser {
|
||||||
|
t.Errorf("FsUserFrom(%v) = %d, want %d", test.pod, *fsUser, *test.wantFsUser)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerateVolumeName(t *testing.T) {
|
func TestGenerateVolumeName(t *testing.T) {
|
||||||
|
|
||||||
// Normal operation, no truncate
|
// Normal operation, no truncate
|
||||||
|
@ -103,6 +103,10 @@ type Attributes struct {
|
|||||||
|
|
||||||
// MounterArgs provides more easily extensible arguments to Mounter
|
// MounterArgs provides more easily extensible arguments to Mounter
|
||||||
type MounterArgs struct {
|
type MounterArgs struct {
|
||||||
|
// When FsUser is set, the ownership of the volume will be modified to be
|
||||||
|
// owned and writable by FsUser. Otherwise, there is no side effects.
|
||||||
|
// Currently only supported with projected service account tokens.
|
||||||
|
FsUser *int64
|
||||||
FsGroup *int64
|
FsGroup *int64
|
||||||
FSGroupChangePolicy *v1.PodFSGroupChangePolicy
|
FSGroupChangePolicy *v1.PodFSGroupChangePolicy
|
||||||
DesiredSize *resource.Quantity
|
DesiredSize *resource.Quantity
|
||||||
@ -126,7 +130,7 @@ type Mounter interface {
|
|||||||
|
|
||||||
// SetUp prepares and mounts/unpacks the volume to a
|
// SetUp prepares and mounts/unpacks the volume to a
|
||||||
// self-determined directory path. The mount point and its
|
// self-determined directory path. The mount point and its
|
||||||
// content should be owned by 'fsGroup' so that it can be
|
// content should be owned by `fsUser` or 'fsGroup' so that it can be
|
||||||
// accessed by the pod. This may be called more than once, so
|
// accessed by the pod. This may be called more than once, so
|
||||||
// implementations must be idempotent.
|
// implementations must be idempotent.
|
||||||
// It could return following types of errors:
|
// It could return following types of errors:
|
||||||
@ -137,7 +141,7 @@ type Mounter interface {
|
|||||||
|
|
||||||
// SetUpAt prepares and mounts/unpacks the volume to the
|
// SetUpAt prepares and mounts/unpacks the volume to the
|
||||||
// specified directory path, which may or may not exist yet.
|
// specified directory path, which may or may not exist yet.
|
||||||
// The mount point and its content should be owned by
|
// The mount point and its content should be owned by `fsUser`
|
||||||
// 'fsGroup' so that it can be accessed by the pod. This may
|
// 'fsGroup' so that it can be accessed by the pod. This may
|
||||||
// be called more than once, so implementations must be
|
// be called more than once, so implementations must be
|
||||||
// idempotent.
|
// idempotent.
|
||||||
|
@ -39,6 +39,7 @@ import (
|
|||||||
"k8s.io/kubernetes/test/e2e/framework"
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
|
e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
|
||||||
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
|
||||||
|
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
|
||||||
imageutils "k8s.io/kubernetes/test/utils/image"
|
imageutils "k8s.io/kubernetes/test/utils/image"
|
||||||
|
|
||||||
"github.com/onsi/ginkgo"
|
"github.com/onsi/ginkgo"
|
||||||
@ -417,6 +418,127 @@ var _ = SIGDescribe("ServiceAccounts", func() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
Testname: Projected service account token file ownership and permission.
|
||||||
|
Description: Ensure that Projected Service Account Token is mounted with
|
||||||
|
correct file ownership and permissino mounted. We test the
|
||||||
|
following scenarios here.
|
||||||
|
1. RunAsUser is set,
|
||||||
|
2. FsGroup is set,
|
||||||
|
3. RunAsUser and FsGroup are set,
|
||||||
|
4. Default, neither RunAsUser nor FsGroup is set,
|
||||||
|
|
||||||
|
Containers MUST verify that the projected service account token can be
|
||||||
|
read and has correct file mode set including ownership and permission.
|
||||||
|
*/
|
||||||
|
ginkgo.It("should set ownership and permission when RunAsUser or FsGroup is present [LinuxOnly] [NodeFeature:FSGroup] [Feature:TokenRequestProjection]", func() {
|
||||||
|
e2eskipper.SkipIfNodeOSDistroIs("windows")
|
||||||
|
|
||||||
|
var (
|
||||||
|
podName = "test-pod-" + string(uuid.NewUUID())
|
||||||
|
containerName = "test-container"
|
||||||
|
volumeName = "test-volume"
|
||||||
|
volumeMountPath = "/test-volume"
|
||||||
|
tokenVolumePath = "/test-volume/token"
|
||||||
|
int64p = func(i int64) *int64 { return &i }
|
||||||
|
)
|
||||||
|
|
||||||
|
pod := &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: podName,
|
||||||
|
},
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Volumes: []v1.Volume{
|
||||||
|
{
|
||||||
|
Name: volumeName,
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
Projected: &v1.ProjectedVolumeSource{
|
||||||
|
Sources: []v1.VolumeProjection{
|
||||||
|
{
|
||||||
|
ServiceAccountToken: &v1.ServiceAccountTokenProjection{
|
||||||
|
Path: "token",
|
||||||
|
ExpirationSeconds: int64p(60 * 60),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Name: containerName,
|
||||||
|
Image: imageutils.GetE2EImage(imageutils.Agnhost),
|
||||||
|
Args: []string{
|
||||||
|
"mounttest",
|
||||||
|
fmt.Sprintf("--file_perm=%v", tokenVolumePath),
|
||||||
|
fmt.Sprintf("--file_owner=%v", tokenVolumePath),
|
||||||
|
fmt.Sprintf("--file_content=%v", tokenVolumePath),
|
||||||
|
},
|
||||||
|
VolumeMounts: []v1.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: volumeName,
|
||||||
|
MountPath: volumeMountPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RestartPolicy: v1.RestartPolicyNever,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testcases := []struct {
|
||||||
|
runAsUser bool
|
||||||
|
fsGroup bool
|
||||||
|
wantPerm string
|
||||||
|
wantUID int64
|
||||||
|
wantGID int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
runAsUser: true,
|
||||||
|
wantPerm: "-rw-------",
|
||||||
|
wantUID: 1000,
|
||||||
|
wantGID: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fsGroup: true,
|
||||||
|
wantPerm: "-rw-r-----",
|
||||||
|
wantUID: 0,
|
||||||
|
wantGID: 10000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
runAsUser: true,
|
||||||
|
fsGroup: true,
|
||||||
|
wantPerm: "-rw-r-----",
|
||||||
|
wantUID: 1000,
|
||||||
|
wantGID: 10000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
wantPerm: "-rw-r--r--",
|
||||||
|
wantUID: 0,
|
||||||
|
wantGID: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
pod.Spec.SecurityContext = &v1.PodSecurityContext{}
|
||||||
|
if tc.runAsUser {
|
||||||
|
pod.Spec.SecurityContext.RunAsUser = &tc.wantUID
|
||||||
|
}
|
||||||
|
if tc.fsGroup {
|
||||||
|
pod.Spec.SecurityContext.FSGroup = &tc.wantGID
|
||||||
|
}
|
||||||
|
|
||||||
|
output := []string{
|
||||||
|
fmt.Sprintf("perms of file \"%v\": %s", tokenVolumePath, tc.wantPerm),
|
||||||
|
fmt.Sprintf("content of file \"%v\": %s", tokenVolumePath, ".+"),
|
||||||
|
fmt.Sprintf("owner UID of \"%v\": %d", tokenVolumePath, tc.wantUID),
|
||||||
|
fmt.Sprintf("owner GID of \"%v\": %d", tokenVolumePath, tc.wantGID),
|
||||||
|
}
|
||||||
|
f.TestContainerOutputRegexp("service account token: ", pod, 0, output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ginkgo.It("should support InClusterConfig with token rotation [Slow] [Feature:TokenRequestProjection]", func() {
|
ginkgo.It("should support InClusterConfig with token rotation [Slow] [Feature:TokenRequestProjection]", func() {
|
||||||
cfg, err := framework.LoadConfig()
|
cfg, err := framework.LoadConfig()
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
Loading…
Reference in New Issue
Block a user