diff --git a/modules/api/cmd/kubermatic-api/swagger.json b/modules/api/cmd/kubermatic-api/swagger.json index 1bd8e74331..b6d49f931f 100644 --- a/modules/api/cmd/kubermatic-api/swagger.json +++ b/modules/api/cmd/kubermatic-api/swagger.json @@ -28169,6 +28169,11 @@ "type": "string", "x-go-name": "Email" }, + "grantedByGroup": { + "description": "GrantedByGroup is the OIDC group that granted this user admin via the\nadmin-group controller. Empty for manually-managed admins. Read-only.", + "type": "string", + "x-go-name": "GrantedByGroup" + }, "isAdmin": { "description": "IsAdmin indicates admin role", "type": "boolean", @@ -35080,6 +35085,14 @@ "description": "GlobalSettings defines global settings", "type": "object", "properties": { + "adminGroups": { + "description": "AdminGroups is the list of OIDC group names whose members are automatically\ngranted KKP administrator privileges.", + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "AdminGroups" + }, "allowedOperatingSystems": { "description": "AllowedOperatingSystems shows the available operating systems to use in the machine deployment.", "type": "object", diff --git a/modules/api/go.mod b/modules/api/go.mod index 87648f3e04..59009e4777 100644 --- a/modules/api/go.mod +++ b/modules/api/go.mod @@ -69,8 +69,8 @@ require ( google.golang.org/api v0.280.0 gopkg.in/yaml.v3 v3.0.1 k8c.io/kubeone v1.12.3 - k8c.io/kubermatic/sdk/v2 v2.30.1-0.20260729132423-bf21cc1ec788 - k8c.io/kubermatic/v2 v2.30.1-0.20260729132423-bf21cc1ec788 + k8c.io/kubermatic/sdk/v2 v2.30.1-0.20260804131818-a52d397b275f + k8c.io/kubermatic/v2 v2.30.1-0.20260804131818-a52d397b275f k8c.io/machine-controller/sdk v1.66.1 k8c.io/operating-system-manager v1.10.7 k8c.io/reconciler v0.5.0 @@ -352,7 +352,7 @@ require ( github.com/sigstore/rekor v1.5.2 // indirect github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 // indirect github.com/sigstore/sigstore v1.10.8 // indirect - github.com/sigstore/sigstore-go v1.2.1 // indirect + github.com/sigstore/sigstore-go v1.2.0 // indirect github.com/sigstore/timestamp-authority/v2 v2.1.2 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect @@ -402,7 +402,7 @@ require ( gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/grpc v1.82.1 // indirect + google.golang.org/grpc v1.81.1 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect diff --git a/modules/api/go.sum b/modules/api/go.sum index b4cabbb48e..943666e870 100644 --- a/modules/api/go.sum +++ b/modules/api/go.sum @@ -1027,8 +1027,8 @@ github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443 h1:/CO8F github.com/sigstore/rekor-tiles/v2 v2.2.2-0.20260601073857-5d098a2b6443/go.mod h1:w1h8wF8vq9lHjmtRdwJiEaoVxhP+WHIMpj4M39pkzp0= github.com/sigstore/sigstore v1.10.8 h1:1Mgkxvkw4AXMfIP1DOjc6kw0GkUgA8pGVpveN/EfOq4= github.com/sigstore/sigstore v1.10.8/go.mod h1:f9+B/4iaYimvUkySyb2mvc73n3RLqNn24grHZM/ET8M= -github.com/sigstore/sigstore-go v1.2.1 h1:YWP/rDbBaEBvtbkj6xtwsSj38ZCFEhTVVadNOXjVe3A= -github.com/sigstore/sigstore-go v1.2.1/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= +github.com/sigstore/sigstore-go v1.2.0 h1:8k8sGMVUUWwZ/KA+s4Q66yEPEzcC1xZ8UsTgI46J9Fc= +github.com/sigstore/sigstore-go v1.2.0/go.mod h1:I8BqVwAb/SaQJ5pBu5IDFY+ksq8O/1/kCag8XUgrsko= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8 h1:tofVQ+UWJgad/69I5zbqxdFCN5gpIn9tRQP7iBzIpBw= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.10.8/go.mod h1:73AfJE8H6w5KGCFPBu4x/OG+i1Yxgmh0L/FtV7prd88= github.com/sigstore/sigstore/pkg/signature/kms/azure v1.10.8 h1:8Mt7J36GcUEmbiJaiFhz2tud5ZIgkfVVCe2H/WJCHmw= @@ -1560,8 +1560,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= -google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1619,10 +1619,10 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8c.io/kubeone v1.12.3 h1:b+dmLJDsofUiDHzyUmR512wXnF5LDDI1KpHsS/BTbrY= k8c.io/kubeone v1.12.3/go.mod h1:saB47KgCNP2028b/ZHZlNal1p7xHqmVfc3LSj+WOW/A= -k8c.io/kubermatic/sdk/v2 v2.30.1-0.20260729132423-bf21cc1ec788 h1:wWVDI7llxgzYT5cNfPQkiEup8tPLuD1WnMJF+qxLd8g= -k8c.io/kubermatic/sdk/v2 v2.30.1-0.20260729132423-bf21cc1ec788/go.mod h1:Vgzb8v63NhUk3Qt4aHgQ2yqI6aWHzwfLHXuMnOlc/yM= -k8c.io/kubermatic/v2 v2.30.1-0.20260729132423-bf21cc1ec788 h1:J4hjDR1ncerWrJH5JYqTxYrfDClFF9rfQ4RFs/rD/rE= -k8c.io/kubermatic/v2 v2.30.1-0.20260729132423-bf21cc1ec788/go.mod h1:8ngxVDYbzRLDgQucCoqeZ2DZmgLsFAA5Icu4L0on3GU= +k8c.io/kubermatic/sdk/v2 v2.30.1-0.20260804131818-a52d397b275f h1:WA92EI8Im0wF/5xL7006tHXuPeBSB2e94doZ8n2A/sQ= +k8c.io/kubermatic/sdk/v2 v2.30.1-0.20260804131818-a52d397b275f/go.mod h1:Vgzb8v63NhUk3Qt4aHgQ2yqI6aWHzwfLHXuMnOlc/yM= +k8c.io/kubermatic/v2 v2.30.1-0.20260804131818-a52d397b275f h1:wSdNgTxevIHciCcxOCZuZhnBDHOMhLYUqxOi3uCyyuo= +k8c.io/kubermatic/v2 v2.30.1-0.20260804131818-a52d397b275f/go.mod h1:8ngxVDYbzRLDgQucCoqeZ2DZmgLsFAA5Icu4L0on3GU= k8c.io/machine-controller/sdk v1.66.1 h1:dUrNbXUfj/xrmgajCTDFuZj4zsaPyIzBkB0ZxsIkSh4= k8c.io/machine-controller/sdk v1.66.1/go.mod h1:/5eWTMcfa7mTQUQoqziaalViiHSVzqzy3fXjejT1qLg= k8c.io/operating-system-manager v1.10.7 h1:XS8uQVnMZ2jgrKcFkFE8lgKcTTVZf6JVuRduntkqi6s= diff --git a/modules/api/pkg/api/v1/types.go b/modules/api/pkg/api/v1/types.go index 9265d73af4..0144762097 100644 --- a/modules/api/pkg/api/v1/types.go +++ b/modules/api/pkg/api/v1/types.go @@ -675,6 +675,9 @@ type Admin struct { IsAdmin *bool `json:"isAdmin,omitempty"` // IsGlobalViewer indicates GlobalViewer role IsGlobalViewer *bool `json:"isGlobalViewer,omitempty"` + // GrantedByGroup is the OIDC group that granted this user admin via the + // admin-group controller. Empty for manually-managed admins. Read-only. + GrantedByGroup string `json:"grantedByGroup,omitempty"` } // ProjectGroup is a helper data structure that diff --git a/modules/api/pkg/api/v2/types.go b/modules/api/pkg/api/v2/types.go index 6b693eb37c..ede730a6a8 100644 --- a/modules/api/pkg/api/v2/types.go +++ b/modules/api/pkg/api/v2/types.go @@ -2249,6 +2249,10 @@ type GlobalSettings struct { // option is disabled in the dashboard. DisabledAuditWebhookBackendDCs []string `json:"disabledAuditWebhookBackendDCs,omitempty"` + // AdminGroups is the list of OIDC group names whose members are automatically + // granted KKP administrator privileges. + AdminGroups []string `json:"adminGroups,omitempty"` + // UserProjectsLimit is the maximum number of projects a user can create. UserProjectsLimit int64 `json:"userProjectsLimit"` RestrictProjectCreation bool `json:"restrictProjectCreation"` diff --git a/modules/api/pkg/handler/v1/admin/admin.go b/modules/api/pkg/handler/v1/admin/admin.go index 88b65a26b4..1f681b7322 100644 --- a/modules/api/pkg/handler/v1/admin/admin.go +++ b/modules/api/pkg/handler/v1/admin/admin.go @@ -26,6 +26,7 @@ import ( apiv1 "k8c.io/dashboard/v2/pkg/api/v1" "k8c.io/dashboard/v2/pkg/handler/v1/common" "k8c.io/dashboard/v2/pkg/provider" + kubermaticv1 "k8c.io/kubermatic/sdk/v2/apis/kubermatic/v1" utilerrors "k8c.io/kubermatic/v2/pkg/util/errors" ) @@ -44,7 +45,12 @@ func GetAdminEndpoint(userInfoGetter provider.UserInfoGetter, adminProvider prov var resultList []apiv1.Admin for _, admin := range admins { - resultList = append(resultList, apiv1.Admin{Email: admin.Spec.Email, IsAdmin: &admin.Spec.IsAdmin, Name: admin.Spec.Name}) + resultList = append(resultList, apiv1.Admin{ + Email: admin.Spec.Email, + IsAdmin: &admin.Spec.IsAdmin, + Name: admin.Spec.Name, + GrantedByGroup: admin.Annotations[kubermaticv1.AdminGrantedByGroupAnnotation], + }) } return resultList, nil diff --git a/modules/api/pkg/handler/v1/admin/admin_test.go b/modules/api/pkg/handler/v1/admin/admin_test.go index 784ed85546..44d9462d31 100644 --- a/modules/api/pkg/handler/v1/admin/admin_test.go +++ b/modules/api/pkg/handler/v1/admin/admin_test.go @@ -62,6 +62,14 @@ func TestGetAdmins(t *testing.T) { existingKubermaticObjs: []ctrlruntimeclient.Object{genUser("Bob", "bob@acme.com", true)}, existingAPIUser: test.GenDefaultAPIUser(), }, + // scenario 3 + { + name: "scenario 3: group-granted admin surfaces grantedByGroup", + expectedResponse: `[{"email":"bob@acme.com","name":"Bob","isAdmin":true,"grantedByGroup":"kkp-admins"}]`, + httpStatus: http.StatusOK, + existingKubermaticObjs: []ctrlruntimeclient.Object{genAdminGrantedByGroup("Bob", "bob@acme.com", "kkp-admins")}, + existingAPIUser: test.GenDefaultAPIUser(), + }, } for _, tc := range testcases { @@ -162,3 +170,11 @@ func TestSetAdmin(t *testing.T) { }) } } + +// genAdminGrantedByGroup returns an admin User carrying the provenance annotation +// that the admin-group controller stamps when it grants admin via an OIDC group. +func genAdminGrantedByGroup(name, email, group string) *kubermaticv1.User { + user := genUser(name, email, true) + user.Annotations = map[string]string{kubermaticv1.AdminGrantedByGroupAnnotation: group} + return user +} diff --git a/modules/api/pkg/handler/v1/admin/settings.go b/modules/api/pkg/handler/v1/admin/settings.go index 7b76b7d54c..bb53741171 100644 --- a/modules/api/pkg/handler/v1/admin/settings.go +++ b/modules/api/pkg/handler/v1/admin/settings.go @@ -147,6 +147,7 @@ func convertAPISettingsToSettingsSpec(settings *apiv2.GlobalSettings) (kubermati EnableEtcdBackup: settings.EnableEtcdBackup, DisableAdminKubeconfig: settings.DisableAdminKubeconfig, DisabledAuditWebhookBackendDCs: settings.DisabledAuditWebhookBackendDCs, + AdminGroups: settings.AdminGroups, UserProjectsLimit: settings.UserProjectsLimit, RestrictProjectCreation: settings.RestrictProjectCreation, RestrictProjectDeletion: settings.RestrictProjectDeletion, @@ -209,6 +210,7 @@ func ConvertCRDSettingsToAPISettingsSpec(settings *kubermaticv1.SettingSpec) api EnableEtcdBackup: settings.EnableEtcdBackup, DisableAdminKubeconfig: settings.DisableAdminKubeconfig, DisabledAuditWebhookBackendDCs: settings.DisabledAuditWebhookBackendDCs, + AdminGroups: settings.AdminGroups, UserProjectsLimit: settings.UserProjectsLimit, RestrictProjectCreation: settings.RestrictProjectCreation, RestrictProjectDeletion: settings.RestrictProjectDeletion, diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/admin.go b/modules/api/pkg/test/e2e/utils/apiclient/models/admin.go index 42b34e9e1f..42cc9cda17 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/models/admin.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/models/admin.go @@ -20,6 +20,10 @@ type Admin struct { // Email address of the admin user Email string `json:"email,omitempty"` + // GrantedByGroup is the OIDC group that granted this user admin via the + // admin-group controller. Empty for manually-managed admins. Read-only. + GrantedByGroup string `json:"grantedByGroup,omitempty"` + // IsAdmin indicates admin role IsAdmin bool `json:"isAdmin,omitempty"` diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/global_settings.go b/modules/api/pkg/test/e2e/utils/apiclient/models/global_settings.go index 1baaf06bad..277accabef 100644 --- a/modules/api/pkg/test/e2e/utils/apiclient/models/global_settings.go +++ b/modules/api/pkg/test/e2e/utils/apiclient/models/global_settings.go @@ -20,6 +20,10 @@ import ( // swagger:model GlobalSettings type GlobalSettings struct { + // AdminGroups is the list of OIDC group names whose members are automatically + // granted KKP administrator privileges. + AdminGroups []string `json:"adminGroups"` + // AllowedOperatingSystems shows the available operating systems to use in the machine deployment. AllowedOperatingSystems map[string]bool `json:"allowedOperatingSystems,omitempty"` diff --git a/modules/web/src/app/settings/admin/admins/component.ts b/modules/web/src/app/settings/admin/admins/component.ts index 390274c55d..b751257e98 100644 --- a/modules/web/src/app/settings/admin/admins/component.ts +++ b/modules/web/src/app/settings/admin/admins/component.ts @@ -79,7 +79,8 @@ export class AdminsComponent implements OnInit, OnChanges { } isDeleteEnabled(admin: Admin): boolean { - return !!this.user && admin.email !== this.user.email; + // Group-granted admins cannot be removed here; remove the group from admin settings instead. + return !!this.user && admin.email !== this.user.email && !admin.grantedByGroup; } delete(admin: Admin): void { diff --git a/modules/web/src/app/settings/admin/admins/style.scss b/modules/web/src/app/settings/admin/admins/style.scss index 5c3846e5ee..2cfa8f95cb 100644 --- a/modules/web/src/app/settings/admin/admins/style.scss +++ b/modules/web/src/app/settings/admin/admins/style.scss @@ -26,3 +26,14 @@ #km-add-admin-btn { margin: 2px 0; } + +.km-admin-via-group { + border: 1px solid currentcolor; + border-radius: 10px; + font-size: 11px; + line-height: 16px; + margin-left: 8px; + opacity: 70%; + padding: 1px 8px; + white-space: nowrap; +} diff --git a/modules/web/src/app/settings/admin/admins/template.html b/modules/web/src/app/settings/admin/admins/template.html index 389417478a..03a249d083 100644 --- a/modules/web/src/app/settings/admin/admins/template.html +++ b/modules/web/src/app/settings/admin/admins/template.html @@ -42,7 +42,15 @@ mat-sort-header>Name