mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-18 13:57:38 +00:00
KEP-5284: add constrained impersonation metrics
See https://kep.k8s.io/5284 for details. apiserver_impersonation_attempts_total{mode, decision} apiserver_impersonation_attempts_duration_seconds{mode, decision} apiserver_impersonation_authorization_attempts_total{mode, decision} apiserver_impersonation_authorization_attempts_duration_seconds{mode, decision} Signed-off-by: Monis Khan <mok@microsoft.com>
This commit is contained in:
@@ -36,6 +36,8 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -1110,6 +1112,8 @@ func TestConstrainedImpersonation(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("bob impersonating alice", func(t *testing.T) {
|
||||
resetAllMetrics(t, ctx, superuserClient)
|
||||
|
||||
impersonatorClientConfig := rest.CopyConfig(kubeConfig)
|
||||
impersonatorClientConfig.BearerToken = "bob"
|
||||
impersonatorClientConfig.Impersonate = rest.ImpersonationConfig{
|
||||
@@ -1164,9 +1168,29 @@ func TestConstrainedImpersonation(t *testing.T) {
|
||||
if !errors.IsForbidden(err) {
|
||||
t.Fatalf("expected forbidden error, got %T %v", err, err)
|
||||
}
|
||||
|
||||
assertImpersonationMetrics(t, ctx, superuserClient, []string{
|
||||
`apiserver_impersonation_attempts_duration_seconds_count{decision="allowed",mode="user-info"} 1`,
|
||||
`apiserver_impersonation_attempts_duration_seconds_count{decision="denied",mode=""} 3`,
|
||||
`apiserver_impersonation_attempts_duration_seconds_sum{decision="allowed",mode="user-info"} FP`,
|
||||
`apiserver_impersonation_attempts_duration_seconds_sum{decision="denied",mode=""} FP`,
|
||||
`apiserver_impersonation_attempts_total{decision="allowed",mode="user-info"} 1`,
|
||||
`apiserver_impersonation_attempts_total{decision="denied",mode=""} 3`,
|
||||
`apiserver_impersonation_authorization_attempts_total{decision="allowed",mode="user-info"} 2`,
|
||||
`apiserver_impersonation_authorization_attempts_total{decision="denied",mode="legacy"} 3`,
|
||||
`apiserver_impersonation_authorization_attempts_total{decision="denied",mode="user-info"} 3`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="allowed",mode="user-info"} 2`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="denied",mode="legacy"} 3`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="denied",mode="user-info"} 3`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="allowed",mode="user-info"} FP`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="denied",mode="legacy"} FP`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="denied",mode="user-info"} FP`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("bob impersonating a node", func(t *testing.T) {
|
||||
resetAllMetrics(t, ctx, superuserClient)
|
||||
|
||||
impersonatorClientConfig := rest.CopyConfig(kubeConfig)
|
||||
impersonatorClientConfig.BearerToken = "bob"
|
||||
impersonatorClientConfig.Impersonate = rest.ImpersonationConfig{
|
||||
@@ -1221,9 +1245,29 @@ func TestConstrainedImpersonation(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %T %v", err, err)
|
||||
}
|
||||
|
||||
assertImpersonationMetrics(t, ctx, superuserClient, []string{
|
||||
`apiserver_impersonation_attempts_duration_seconds_count{decision="allowed",mode="arbitrary-node"} 1`,
|
||||
`apiserver_impersonation_attempts_duration_seconds_count{decision="denied",mode=""} 2`,
|
||||
`apiserver_impersonation_attempts_duration_seconds_sum{decision="allowed",mode="arbitrary-node"} FP`,
|
||||
`apiserver_impersonation_attempts_duration_seconds_sum{decision="denied",mode=""} FP`,
|
||||
`apiserver_impersonation_attempts_total{decision="allowed",mode="arbitrary-node"} 1`,
|
||||
`apiserver_impersonation_attempts_total{decision="denied",mode=""} 2`,
|
||||
`apiserver_impersonation_authorization_attempts_total{decision="allowed",mode="arbitrary-node"} 3`,
|
||||
`apiserver_impersonation_authorization_attempts_total{decision="denied",mode="arbitrary-node"} 2`,
|
||||
`apiserver_impersonation_authorization_attempts_total{decision="denied",mode="legacy"} 2`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="allowed",mode="arbitrary-node"} 3`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="denied",mode="arbitrary-node"} 2`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="denied",mode="legacy"} 2`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="allowed",mode="arbitrary-node"} FP`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="denied",mode="arbitrary-node"} FP`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="denied",mode="legacy"} FP`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("impersonating scheduled node", func(t *testing.T) {
|
||||
resetAllMetrics(t, ctx, superuserClient)
|
||||
|
||||
impersonatorClientConfig := rest.CopyConfig(kubeConfig)
|
||||
impersonatorClientConfig.BearerToken = "serviceaccount2"
|
||||
impersonatorClientConfig.Impersonate = rest.ImpersonationConfig{
|
||||
@@ -1275,9 +1319,29 @@ func TestConstrainedImpersonation(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %T %v", err, err)
|
||||
}
|
||||
|
||||
assertImpersonationMetrics(t, ctx, superuserClient, []string{
|
||||
`apiserver_impersonation_attempts_duration_seconds_count{decision="allowed",mode="associated-node"} 1`,
|
||||
`apiserver_impersonation_attempts_duration_seconds_count{decision="denied",mode=""} 1`,
|
||||
`apiserver_impersonation_attempts_duration_seconds_sum{decision="allowed",mode="associated-node"} FP`,
|
||||
`apiserver_impersonation_attempts_duration_seconds_sum{decision="denied",mode=""} FP`,
|
||||
`apiserver_impersonation_attempts_total{decision="allowed",mode="associated-node"} 1`,
|
||||
`apiserver_impersonation_attempts_total{decision="denied",mode=""} 1`,
|
||||
`apiserver_impersonation_authorization_attempts_total{decision="allowed",mode="associated-node"} 2`,
|
||||
`apiserver_impersonation_authorization_attempts_total{decision="denied",mode="arbitrary-node"} 1`,
|
||||
`apiserver_impersonation_authorization_attempts_total{decision="denied",mode="legacy"} 1`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="allowed",mode="associated-node"} 2`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="denied",mode="arbitrary-node"} 1`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="denied",mode="legacy"} 1`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="allowed",mode="associated-node"} FP`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="denied",mode="arbitrary-node"} FP`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="denied",mode="legacy"} FP`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("fallback to legacy impersonation", func(t *testing.T) {
|
||||
resetAllMetrics(t, ctx, superuserClient)
|
||||
|
||||
impersonatorClientConfig := rest.CopyConfig(kubeConfig)
|
||||
impersonatorClientConfig.BearerToken = "bob"
|
||||
impersonatorClientConfig.Impersonate = rest.ImpersonationConfig{
|
||||
@@ -1293,13 +1357,68 @@ func TestConstrainedImpersonation(t *testing.T) {
|
||||
Resources: []string{"users"},
|
||||
})
|
||||
|
||||
_, err := client.CoreV1().Pods(metav1.NamespaceAll).List(ctx, metav1.ListOptions{})
|
||||
_, err := client.CoreV1().Pods(metav1.NamespaceAll).List(ctx, metav1.ListOptions{
|
||||
LabelSelector: "app=panda", // force this request to have a different cache key than the earlier test
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %T %v", err, err)
|
||||
}
|
||||
|
||||
assertImpersonationMetrics(t, ctx, superuserClient, []string{
|
||||
`apiserver_impersonation_attempts_duration_seconds_count{decision="allowed",mode="legacy"} 1`,
|
||||
`apiserver_impersonation_attempts_duration_seconds_sum{decision="allowed",mode="legacy"} FP`,
|
||||
`apiserver_impersonation_attempts_total{decision="allowed",mode="legacy"} 1`,
|
||||
`apiserver_impersonation_authorization_attempts_total{decision="allowed",mode="legacy"} 1`,
|
||||
`apiserver_impersonation_authorization_attempts_total{decision="denied",mode="user-info"} 1`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="allowed",mode="legacy"} 1`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_count{decision="denied",mode="user-info"} 1`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="allowed",mode="legacy"} FP`,
|
||||
`apiserver_impersonation_authorization_attempts_duration_seconds_sum{decision="denied",mode="user-info"} FP`,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func resetAllMetrics(t *testing.T, ctx context.Context, client clientset.Interface) {
|
||||
t.Helper()
|
||||
|
||||
if err := client.CoreV1().RESTClient().Delete().AbsPath("/metrics").Do(ctx).Error(); err != nil {
|
||||
t.Fatalf("failed to reset metrics: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertImpersonationMetrics(t *testing.T, ctx context.Context, client clientset.Interface, wantMetricStrings []string) {
|
||||
t.Helper()
|
||||
|
||||
rc := client.CoreV1().RESTClient()
|
||||
|
||||
body, err := rc.Get().AbsPath("/metrics").DoRaw(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to fetch metrics: %v", err)
|
||||
}
|
||||
|
||||
var gotMetricStrings []string
|
||||
trimFP := regexp.MustCompile(`(.*)(} \d+\.\d+.*)`)
|
||||
for line := range strings.SplitSeq(string(body), "\n") {
|
||||
if !strings.HasPrefix(line, "apiserver_impersonation_") {
|
||||
continue
|
||||
}
|
||||
// skip histogram bucket lines to keep assertions manageable
|
||||
if strings.Contains(line, "_bucket{") {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(line, "_seconds_sum") {
|
||||
line = trimFP.ReplaceAllString(line, `$1`) + "} FP"
|
||||
}
|
||||
gotMetricStrings = append(gotMetricStrings, line)
|
||||
}
|
||||
slices.Sort(gotMetricStrings)
|
||||
slices.Sort(wantMetricStrings)
|
||||
|
||||
if diff := cmp.Diff(wantMetricStrings, gotMetricStrings); diff != "" {
|
||||
t.Errorf("unexpected impersonation metrics diff (-want +got): %s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConstrainedImpersonationDisabled tests the impersonation behavior when the
|
||||
// ConstrainedImpersonation feature gate is disabled. In this mode, the legacy
|
||||
// impersonation behavior is expected, where a user only needs the "impersonate"
|
||||
|
||||
Reference in New Issue
Block a user