Merge pull request #123789 from tallclair/apparmor-warnings
Warn on deprecated AppArmor annotation use
This commit is contained in:
@@ -24,10 +24,12 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
nodeapi "k8s.io/kubernetes/pkg/api/node"
|
||||
pvcutil "k8s.io/kubernetes/pkg/api/persistentvolumeclaim"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/pods"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
func GetWarningsForPod(ctx context.Context, pod, oldPod *api.Pod) []string {
|
||||
@@ -212,6 +214,7 @@ func warningsForPodSpecAndMeta(fieldPath *field.Path, podSpec *api.PodSpec, meta
|
||||
warnings = append(warnings, fmt.Sprintf(`%s: non-functional in v1.27+; use the "seccompProfile" field instead`, fieldPath.Child("metadata", "annotations").Key(api.SeccompPodAnnotationKey)))
|
||||
}
|
||||
}
|
||||
hasPodAppArmorProfile := podSpec.SecurityContext != nil && podSpec.SecurityContext.AppArmorProfile != nil
|
||||
|
||||
pods.VisitContainersWithPath(podSpec, fieldPath.Child("spec"), func(c *api.Container, p *field.Path) bool {
|
||||
// use of container seccomp annotation without accompanying field
|
||||
@@ -221,6 +224,18 @@ func warningsForPodSpecAndMeta(fieldPath *field.Path, podSpec *api.PodSpec, meta
|
||||
}
|
||||
}
|
||||
|
||||
// use of container AppArmor annotation without accompanying field
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.AppArmorFields) {
|
||||
isPodTemplate := fieldPath != nil // Pod warnings are emitted through applyAppArmorVersionSkew instead.
|
||||
hasAppArmorField := hasPodAppArmorProfile || (c.SecurityContext != nil && c.SecurityContext.AppArmorProfile != nil)
|
||||
if isPodTemplate && !hasAppArmorField {
|
||||
key := api.DeprecatedAppArmorAnnotationKeyPrefix + c.Name
|
||||
if _, exists := meta.Annotations[key]; exists {
|
||||
warnings = append(warnings, fmt.Sprintf(`%s: deprecated since v1.30; use the "appArmorProfile" field instead`, fieldPath.Child("metadata", "annotations").Key(key)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fractional memory/ephemeral-storage requests/limits (#79950, #49442, #18538)
|
||||
if value, ok := c.Resources.Limits[api.ResourceMemory]; ok && value.MilliValue()%int64(1000) != int64(0) {
|
||||
warnings = append(warnings, fmt.Sprintf("%s: fractional byte value %q is invalid, must be an integer", p.Child("resources", "limits").Key(string(api.ResourceMemory)), value.String()))
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"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/validation/field"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
)
|
||||
@@ -1095,3 +1096,90 @@ func TestWarnings(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateOnlyWarnings(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
template *api.PodTemplateSpec
|
||||
oldTemplate *api.PodTemplateSpec
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "annotations",
|
||||
template: &api.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
|
||||
`container.apparmor.security.beta.kubernetes.io/foo`: `unconfined`,
|
||||
}},
|
||||
Spec: api.PodSpec{Containers: []api.Container{{Name: "foo"}}},
|
||||
},
|
||||
expected: []string{
|
||||
`template.metadata.annotations[container.apparmor.security.beta.kubernetes.io/foo]: deprecated since v1.30; use the "appArmorProfile" field instead`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "AppArmor pod field",
|
||||
template: &api.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
|
||||
`container.apparmor.security.beta.kubernetes.io/foo`: `unconfined`,
|
||||
}},
|
||||
Spec: api.PodSpec{
|
||||
SecurityContext: &api.PodSecurityContext{
|
||||
AppArmorProfile: &api.AppArmorProfile{Type: api.AppArmorProfileTypeUnconfined},
|
||||
},
|
||||
Containers: []api.Container{{
|
||||
Name: "foo",
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "AppArmor container field",
|
||||
template: &api.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{
|
||||
`container.apparmor.security.beta.kubernetes.io/foo`: `unconfined`,
|
||||
}},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{{
|
||||
Name: "foo",
|
||||
SecurityContext: &api.SecurityContext{
|
||||
AppArmorProfile: &api.AppArmorProfile{Type: api.AppArmorProfileTypeUnconfined},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expected: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run("podspec_"+tc.name, func(t *testing.T) {
|
||||
var oldTemplate *api.PodTemplateSpec
|
||||
if tc.oldTemplate != nil {
|
||||
oldTemplate = tc.oldTemplate
|
||||
}
|
||||
actual := sets.New[string](GetWarningsForPodTemplate(context.TODO(), field.NewPath("template"), tc.template, oldTemplate)...)
|
||||
expected := sets.New[string](tc.expected...)
|
||||
for _, missing := range sets.List[string](expected.Difference(actual)) {
|
||||
t.Errorf("missing: %s", missing)
|
||||
}
|
||||
for _, extra := range sets.List[string](actual.Difference(expected)) {
|
||||
t.Errorf("extra: %s", extra)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("pod_"+tc.name, func(t *testing.T) {
|
||||
var pod *api.Pod
|
||||
if tc.template != nil {
|
||||
pod = &api.Pod{
|
||||
ObjectMeta: tc.template.ObjectMeta,
|
||||
Spec: tc.template.Spec,
|
||||
}
|
||||
}
|
||||
actual := GetWarningsForPod(context.TODO(), pod, &api.Pod{})
|
||||
if len(actual) > 0 {
|
||||
t.Errorf("unexpected template-only warnings on pod: %v", actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user