mirror of
https://github.com/rancher/k3k.git
synced 2026-08-18 03:46:31 +00:00
Add support for projected service account tokens in shared mode Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com>
328 lines
8.6 KiB
Go
328 lines
8.6 KiB
Go
package provider
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"k8s.io/utils/ptr"
|
|
|
|
authv1 "k8s.io/api/authentication/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
k3kcontroller "github.com/rancher/k3k/pkg/controller"
|
|
)
|
|
|
|
func Test_hasKubeAccessVolume(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pod *corev1.Pod
|
|
want bool
|
|
}{
|
|
{
|
|
name: "no volumes",
|
|
pod: &corev1.Pod{},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "volume with kube-api-access prefix",
|
|
pod: &corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{Name: "kube-api-access-abc123"},
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "exact kube-api-access name",
|
|
pod: &corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{Name: "kube-api-access"},
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "volume without kube-api-access prefix",
|
|
pod: &corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{Name: "my-volume"},
|
|
},
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "multiple volumes with one kube-api-access",
|
|
pod: &corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{Name: "config-volume"},
|
|
{Name: "kube-api-access-xyz"},
|
|
{Name: "data-volume"},
|
|
},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.want, hasKubeAccessVolume(tt.pod))
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_removeKubeAccessVolume(t *testing.T) {
|
|
t.Run("removes volume and all volume mounts from containers", func(t *testing.T) {
|
|
pod := &corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{Name: "config-volume"},
|
|
{Name: "kube-api-access-abc"},
|
|
{Name: "data-volume"},
|
|
},
|
|
InitContainers: []corev1.Container{
|
|
{
|
|
Name: "init",
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{Name: "config-volume", MountPath: "/config"},
|
|
{Name: "kube-api-access-abc", MountPath: serviceAccountTokenMountPath},
|
|
},
|
|
},
|
|
},
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "main",
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{Name: "kube-api-access-abc", MountPath: serviceAccountTokenMountPath},
|
|
{Name: "data-volume", MountPath: "/data"},
|
|
},
|
|
},
|
|
},
|
|
EphemeralContainers: []corev1.EphemeralContainer{
|
|
{
|
|
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
|
Name: "debug",
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{Name: "kube-api-access-abc", MountPath: serviceAccountTokenMountPath},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
removeKubeAccessVolume(pod)
|
|
|
|
// Verify volume was removed
|
|
assert.Equal(t, 2, len(pod.Spec.Volumes))
|
|
assert.Equal(t, "config-volume", pod.Spec.Volumes[0].Name)
|
|
assert.Equal(t, "data-volume", pod.Spec.Volumes[1].Name)
|
|
|
|
// Verify init container mount was removed
|
|
assert.Equal(t, 1, len(pod.Spec.InitContainers[0].VolumeMounts))
|
|
assert.Equal(t, "config-volume", pod.Spec.InitContainers[0].VolumeMounts[0].Name)
|
|
|
|
// Verify container mount was removed
|
|
assert.Equal(t, 1, len(pod.Spec.Containers[0].VolumeMounts))
|
|
assert.Equal(t, "data-volume", pod.Spec.Containers[0].VolumeMounts[0].Name)
|
|
|
|
// Verify ephemeral container mount was removed
|
|
assert.Equal(t, 0, len(pod.Spec.EphemeralContainers[0].VolumeMounts))
|
|
})
|
|
|
|
t.Run("no kube-api-access volume present", func(t *testing.T) {
|
|
pod := &corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{Name: "config-volume"},
|
|
},
|
|
Containers: []corev1.Container{
|
|
{
|
|
Name: "main",
|
|
VolumeMounts: []corev1.VolumeMount{
|
|
{Name: "config-volume", MountPath: "/config"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
removeKubeAccessVolume(pod)
|
|
|
|
assert.Equal(t, 1, len(pod.Spec.Volumes))
|
|
assert.Equal(t, "config-volume", pod.Spec.Volumes[0].Name)
|
|
assert.Equal(t, 1, len(pod.Spec.Containers[0].VolumeMounts))
|
|
})
|
|
}
|
|
|
|
func Test_addKubeAccessVolume(t *testing.T) {
|
|
tokenVolumeName := k3kcontroller.SafeConcatNameWithPrefix(kubeAPIAccessPrefix)
|
|
hostSecretName := "host-secret-token"
|
|
|
|
pod := &corev1.Pod{
|
|
Spec: corev1.PodSpec{
|
|
Volumes: []corev1.Volume{
|
|
{Name: "existing-volume"},
|
|
},
|
|
InitContainers: []corev1.Container{
|
|
{Name: "init"},
|
|
},
|
|
Containers: []corev1.Container{
|
|
{Name: "main"},
|
|
{Name: "sidecar"},
|
|
},
|
|
EphemeralContainers: []corev1.EphemeralContainer{
|
|
{
|
|
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
|
|
Name: "debug",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
addKubeAccessVolume(pod, hostSecretName)
|
|
|
|
// Verify volume was added
|
|
assert.Equal(t, 2, len(pod.Spec.Volumes))
|
|
addedVol := pod.Spec.Volumes[1]
|
|
assert.Equal(t, tokenVolumeName, addedVol.Name)
|
|
assert.Equal(t, hostSecretName, addedVol.Secret.SecretName)
|
|
|
|
// Verify init container mount was added
|
|
assert.Equal(t, 1, len(pod.Spec.InitContainers[0].VolumeMounts))
|
|
assert.Equal(t, tokenVolumeName, pod.Spec.InitContainers[0].VolumeMounts[0].Name)
|
|
assert.Equal(t, serviceAccountTokenMountPath, pod.Spec.InitContainers[0].VolumeMounts[0].MountPath)
|
|
|
|
// Verify all container mounts were added
|
|
for _, c := range pod.Spec.Containers {
|
|
assert.Equal(t, 1, len(c.VolumeMounts), "container %s should have mount", c.Name)
|
|
assert.Equal(t, tokenVolumeName, c.VolumeMounts[0].Name)
|
|
assert.Equal(t, serviceAccountTokenMountPath, c.VolumeMounts[0].MountPath)
|
|
}
|
|
|
|
// Verify ephemeral container mounts were added
|
|
for _, c := range pod.Spec.EphemeralContainers {
|
|
assert.Equal(t, 1, len(c.VolumeMounts), "ephemeral container %s should have mount", c.Name)
|
|
assert.Equal(t, tokenVolumeName, c.VolumeMounts[0].Name)
|
|
assert.Equal(t, serviceAccountTokenMountPath, c.VolumeMounts[0].MountPath)
|
|
}
|
|
}
|
|
|
|
func Test_virtualSecret(t *testing.T) {
|
|
s := virtualSecret("my-secret", "my-ns", "my-sa")
|
|
|
|
assert.Equal(t, "my-secret", s.Name)
|
|
assert.Equal(t, "my-ns", s.Namespace)
|
|
assert.Equal(t, corev1.SecretTypeServiceAccountToken, s.Type)
|
|
assert.Equal(t, "my-sa", s.Annotations[corev1.ServiceAccountNameKey])
|
|
assert.Equal(t, "Secret", s.Kind)
|
|
assert.Equal(t, "v1", s.APIVersion)
|
|
}
|
|
|
|
func Test_generateTokenSecretName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
serviceAccountName string
|
|
tokenPath string
|
|
tokenReq *authv1.TokenRequest
|
|
want string
|
|
}{
|
|
{
|
|
name: "no audiences, no expiration, no path",
|
|
serviceAccountName: "default",
|
|
tokenReq: &authv1.TokenRequest{
|
|
Spec: authv1.TokenRequestSpec{},
|
|
},
|
|
want: "k3k-default",
|
|
},
|
|
{
|
|
name: "no audiences, with expiration",
|
|
serviceAccountName: "default",
|
|
tokenPath: "token",
|
|
tokenReq: &authv1.TokenRequest{
|
|
Spec: authv1.TokenRequestSpec{
|
|
ExpirationSeconds: ptr.To(int64(3600)),
|
|
},
|
|
},
|
|
want: "k3k-default-3600-token",
|
|
},
|
|
{
|
|
name: "with single audience and expiration",
|
|
serviceAccountName: "my-sa",
|
|
tokenPath: "token",
|
|
tokenReq: &authv1.TokenRequest{
|
|
Spec: authv1.TokenRequestSpec{
|
|
Audiences: []string{"api"},
|
|
ExpirationSeconds: ptr.To(int64(3600)),
|
|
},
|
|
},
|
|
want: "k3k-my-sa-api-3600-token",
|
|
},
|
|
{
|
|
name: "with multiple audiences and expiration",
|
|
serviceAccountName: "my-sa",
|
|
tokenPath: "token",
|
|
tokenReq: &authv1.TokenRequest{
|
|
Spec: authv1.TokenRequestSpec{
|
|
Audiences: []string{"api", "vault"},
|
|
ExpirationSeconds: ptr.To(int64(3600)),
|
|
},
|
|
},
|
|
want: "k3k-my-sa-api-vault-3600-token",
|
|
},
|
|
{
|
|
name: "with audiences, no expiration",
|
|
serviceAccountName: "my-sa",
|
|
tokenPath: "vault-token",
|
|
tokenReq: &authv1.TokenRequest{
|
|
Spec: authv1.TokenRequestSpec{
|
|
Audiences: []string{"api"},
|
|
},
|
|
},
|
|
want: "k3k-my-sa-api-vault-token",
|
|
},
|
|
{
|
|
name: "different paths produce different names",
|
|
serviceAccountName: "my-sa",
|
|
tokenPath: "other-path",
|
|
tokenReq: &authv1.TokenRequest{
|
|
Spec: authv1.TokenRequestSpec{
|
|
Audiences: []string{"api"},
|
|
ExpirationSeconds: ptr.To(int64(3600)),
|
|
},
|
|
},
|
|
want: "k3k-my-sa-api-3600-other-path",
|
|
},
|
|
{
|
|
name: "long name gets truncated with hash",
|
|
serviceAccountName: "my-very-long-service-account-name",
|
|
tokenPath: "some-very-long-token-path-value",
|
|
tokenReq: &authv1.TokenRequest{
|
|
Spec: authv1.TokenRequestSpec{
|
|
Audiences: []string{"some-very-long-audience-string"},
|
|
ExpirationSeconds: ptr.To(int64(3600)),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := generateTokenSecretName(tt.serviceAccountName, tt.tokenPath, tt.tokenReq)
|
|
if tt.want != "" {
|
|
assert.Equal(t, tt.want, got)
|
|
}
|
|
|
|
assert.Less(t, len(got), 64, "name should be under 64 characters")
|
|
})
|
|
}
|
|
}
|