mirror of
https://github.com/labring/sealos.git
synced 2026-08-28 17:22:42 +08:00
fix(account): enforce suspended namespace webhook (#7274)
fix(account): enforce suspended namespace webhook (#7266) * fix(account): enforce suspended namespace webhook * style(account): format webhook tests Co-authored-by: zijiren <84728412+zijiren233@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
711fa8ae05
commit
35804bfc0e
@@ -193,6 +193,13 @@ func (d *DebtValidate) checkOption(
|
||||
fmt.Sprintf("this namespace is not user namespace %s, or have not created", ns.Name),
|
||||
)
|
||||
}
|
||||
if suspendedStatus, suspended := getSuspendedNamespaceStatus(ns); suspended {
|
||||
logger.V(1).
|
||||
Info("deny request for suspended namespace", "ns", ns.Name, "status", suspendedStatus)
|
||||
return admission.Denied(
|
||||
fmt.Sprintf("namespace %s is suspended with status %s", ns.Name, suspendedStatus),
|
||||
)
|
||||
}
|
||||
logger.V(1).Info("check user namespace", "ns", ns.Name, "user", user)
|
||||
|
||||
// Check cache first
|
||||
@@ -272,6 +279,27 @@ func (d *DebtValidate) checkOption(
|
||||
return admission.Allowed(fmt.Sprintf("pass user %s, namespace %s", user, ns.Name))
|
||||
}
|
||||
|
||||
func getSuspendedNamespaceStatus(ns *corev1.Namespace) (string, bool) {
|
||||
debtStatus := ns.Annotations[pkgtype.DebtNamespaceAnnoStatusKey]
|
||||
switch debtStatus {
|
||||
case pkgtype.SuspendDebtNamespaceAnnoStatus,
|
||||
pkgtype.SuspendCompletedDebtNamespaceAnnoStatus,
|
||||
pkgtype.TerminateSuspendDebtNamespaceAnnoStatus,
|
||||
pkgtype.TerminateSuspendCompletedDebtNamespaceAnnoStatus,
|
||||
pkgtype.FinalDeletionDebtNamespaceAnnoStatus,
|
||||
pkgtype.FinalDeletionCompletedDebtNamespaceAnnoStatus:
|
||||
return debtStatus, true
|
||||
}
|
||||
|
||||
networkStatus := ns.Annotations[pkgtype.NetworkStatusAnnoKey]
|
||||
switch networkStatus {
|
||||
case pkgtype.NetworkSuspend, pkgtype.NetworkSuspendCompleted:
|
||||
return networkStatus, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
func isDefaultQuotaName(name string) bool {
|
||||
return strings.HasPrefix(name, "quota-") || name == debtLimit0QuotaName
|
||||
}
|
||||
|
||||
@@ -194,6 +194,37 @@ func TestHandle_DeleteQuotaNotBypassed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandle_SuspendedNamespace_NonDeleteDenied(t *testing.T) {
|
||||
d := newDebtWithNS(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ns-test",
|
||||
Labels: map[string]string{"user.sealos.io/owner": "user-1"},
|
||||
Annotations: map[string]string{
|
||||
pkgtype.DebtNamespaceAnnoStatusKey: pkgtype.SuspendCompletedDebtNamespaceAnnoStatus,
|
||||
},
|
||||
}})
|
||||
req := makeReq(admissionv1.Update, "Pod", "", "v1", "pods")
|
||||
if resp := d.Handle(context.Background(), req); resp.Allowed {
|
||||
t.Fatal("non-DELETE request in a suspended namespace should be denied")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandle_SuspendedNamespace_DeleteAllowed(t *testing.T) {
|
||||
d := newDebtWithNS(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ns-test",
|
||||
Labels: map[string]string{"user.sealos.io/owner": "user-1"},
|
||||
Annotations: map[string]string{
|
||||
pkgtype.DebtNamespaceAnnoStatusKey: pkgtype.SuspendCompletedDebtNamespaceAnnoStatus,
|
||||
},
|
||||
}})
|
||||
req := makeReq(admissionv1.Delete, "Pod", "", "v1", "pods")
|
||||
if resp := d.Handle(context.Background(), req); !resp.Allowed {
|
||||
t.Fatalf(
|
||||
"DELETE request in a suspended namespace should be allowed, got: %s",
|
||||
resp.Result.Message,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Resource-type denial tests (no client/DB needed) ---
|
||||
|
||||
func TestHandle_NamespaceDenied(t *testing.T) {
|
||||
@@ -324,8 +355,157 @@ func TestCheckOption_CacheHit_SufficientBalance(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckOption_SuspendedDebtStatus_Denied(t *testing.T) {
|
||||
statuses := []string{
|
||||
pkgtype.SuspendDebtNamespaceAnnoStatus,
|
||||
pkgtype.SuspendCompletedDebtNamespaceAnnoStatus,
|
||||
pkgtype.TerminateSuspendDebtNamespaceAnnoStatus,
|
||||
pkgtype.TerminateSuspendCompletedDebtNamespaceAnnoStatus,
|
||||
pkgtype.FinalDeletionDebtNamespaceAnnoStatus,
|
||||
pkgtype.FinalDeletionCompletedDebtNamespaceAnnoStatus,
|
||||
}
|
||||
|
||||
for _, status := range statuses {
|
||||
t.Run(status, func(t *testing.T) {
|
||||
d := newDebtWithNS(&corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ns-test",
|
||||
Labels: map[string]string{"user.sealos.io/owner": "user-1"},
|
||||
Annotations: map[string]string{pkgtype.DebtNamespaceAnnoStatusKey: status},
|
||||
},
|
||||
})
|
||||
if resp := d.checkOption(
|
||||
context.Background(),
|
||||
logger,
|
||||
d.Client,
|
||||
"ns-test",
|
||||
); resp.Allowed {
|
||||
t.Fatalf("suspended namespace status %q should be denied", status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckOption_SuspendedNetworkStatus_Denied(t *testing.T) {
|
||||
for _, status := range []string{pkgtype.NetworkSuspend, pkgtype.NetworkSuspendCompleted} {
|
||||
t.Run(status, func(t *testing.T) {
|
||||
d := newDebtWithNS(&corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ns-test",
|
||||
Labels: map[string]string{"user.sealos.io/owner": "user-1"},
|
||||
Annotations: map[string]string{pkgtype.NetworkStatusAnnoKey: status},
|
||||
},
|
||||
})
|
||||
if resp := d.checkOption(
|
||||
context.Background(),
|
||||
logger,
|
||||
d.Client,
|
||||
"ns-test",
|
||||
); resp.Allowed {
|
||||
t.Fatalf("suspended network status %q should be denied", status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckOption_ActiveDebtStatus_AllowsWithSufficientBalance(t *testing.T) {
|
||||
for _, status := range []string{
|
||||
pkgtype.NormalDebtNamespaceAnnoStatus,
|
||||
pkgtype.ResumeDebtNamespaceAnnoStatus,
|
||||
pkgtype.ResumeCompletedDebtNamespaceAnnoStatus,
|
||||
} {
|
||||
t.Run(status, func(t *testing.T) {
|
||||
d := newDebtWithNS(&corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ns-test",
|
||||
Labels: map[string]string{"user.sealos.io/owner": "user-1"},
|
||||
Annotations: map[string]string{pkgtype.DebtNamespaceAnnoStatusKey: status},
|
||||
},
|
||||
})
|
||||
d.TTLUserMap.Put("account:user-1", &pkgtype.UsableBalanceWithCredits{
|
||||
Balance: 100,
|
||||
})
|
||||
if resp := d.checkOption(
|
||||
context.Background(),
|
||||
logger,
|
||||
d.Client,
|
||||
"ns-test",
|
||||
); !resp.Allowed {
|
||||
t.Fatalf(
|
||||
"active namespace status %q should be allowed, got: %s",
|
||||
status,
|
||||
resp.Result.Message,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
func TestGetSuspendedNamespaceStatus(t *testing.T) {
|
||||
for _, status := range []string{
|
||||
pkgtype.SuspendDebtNamespaceAnnoStatus,
|
||||
pkgtype.SuspendCompletedDebtNamespaceAnnoStatus,
|
||||
pkgtype.TerminateSuspendDebtNamespaceAnnoStatus,
|
||||
pkgtype.TerminateSuspendCompletedDebtNamespaceAnnoStatus,
|
||||
pkgtype.FinalDeletionDebtNamespaceAnnoStatus,
|
||||
pkgtype.FinalDeletionCompletedDebtNamespaceAnnoStatus,
|
||||
} {
|
||||
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{pkgtype.DebtNamespaceAnnoStatusKey: status},
|
||||
}}
|
||||
if got, ok := getSuspendedNamespaceStatus(ns); !ok || got != status {
|
||||
t.Errorf(
|
||||
"getSuspendedNamespaceStatus(%q) = (%q, %v), want (%q, true)",
|
||||
status,
|
||||
got,
|
||||
ok,
|
||||
status,
|
||||
)
|
||||
}
|
||||
}
|
||||
for _, status := range []string{pkgtype.NetworkSuspend, pkgtype.NetworkSuspendCompleted} {
|
||||
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{pkgtype.NetworkStatusAnnoKey: status},
|
||||
}}
|
||||
if got, ok := getSuspendedNamespaceStatus(ns); !ok || got != status {
|
||||
t.Errorf(
|
||||
"getSuspendedNamespaceStatus(%q) = (%q, %v), want (%q, true)",
|
||||
status,
|
||||
got,
|
||||
ok,
|
||||
status,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
activeStatuses := []struct {
|
||||
key string
|
||||
status string
|
||||
}{
|
||||
{pkgtype.DebtNamespaceAnnoStatusKey, ""},
|
||||
{pkgtype.DebtNamespaceAnnoStatusKey, pkgtype.NormalDebtNamespaceAnnoStatus},
|
||||
{pkgtype.DebtNamespaceAnnoStatusKey, pkgtype.ResumeDebtNamespaceAnnoStatus},
|
||||
{pkgtype.DebtNamespaceAnnoStatusKey, pkgtype.ResumeCompletedDebtNamespaceAnnoStatus},
|
||||
{pkgtype.NetworkStatusAnnoKey, pkgtype.NetworkResume},
|
||||
{pkgtype.NetworkStatusAnnoKey, pkgtype.NetworkResumeCompleted},
|
||||
}
|
||||
for _, test := range activeStatuses {
|
||||
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{test.key: test.status},
|
||||
}}
|
||||
if got, ok := getSuspendedNamespaceStatus(ns); ok {
|
||||
t.Errorf(
|
||||
"getSuspendedNamespaceStatus(%s=%q) = (%q, true), want no suspension",
|
||||
test.key,
|
||||
test.status,
|
||||
got,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDefaultQuotaName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user