Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions modules/api/cmd/kubermatic-api/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
8 changes: 4 additions & 4 deletions modules/api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions modules/api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
3 changes: 3 additions & 0 deletions modules/api/pkg/api/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions modules/api/pkg/api/v2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
8 changes: 7 additions & 1 deletion modules/api/pkg/handler/v1/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
Expand Down
16 changes: 16 additions & 0 deletions modules/api/pkg/handler/v1/admin/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
2 changes: 2 additions & 0 deletions modules/api/pkg/handler/v1/admin/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions modules/api/pkg/test/e2e/utils/apiclient/models/admin.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion modules/web/src/app/settings/admin/admins/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 11 additions & 0 deletions modules/web/src/app/settings/admin/admins/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
10 changes: 9 additions & 1 deletion modules/web/src/app/settings/admin/admins/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,15 @@
mat-sort-header>Name
</th>
<td mat-cell
*matCellDef="let element">{{element.name}}</td>
*matCellDef="let element">
{{element.name}}
@if (element.grantedByGroup) {
<span class="km-admin-via-group"
title="Admin granted via OIDC group '{{element.grantedByGroup}}'. Remove the group from Admin Groups settings to revoke.">
via {{element.grantedByGroup}}
</span>
}
</td>
</ng-container>

<ng-container matColumnDef="email">
Expand Down
14 changes: 14 additions & 0 deletions modules/web/src/app/settings/admin/defaults/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ export class DefaultsComponent implements OnInit, OnDestroy {
return this.settings.annotations?.protectedAnnotations;
}

get adminGroups(): string[] {
return this.settings?.adminGroups;
}

ngOnInit(): void {
this._userService.currentUser.pipe(take(1)).subscribe(user => (this.user = user));
this._datacenterService.datacenters
Expand Down Expand Up @@ -214,6 +218,11 @@ export class DefaultsComponent implements OnInit, OnDestroy {
this.onSettingsChange();
}

onAdminGroupsChange(val: string[]): void {
this.settings.adminGroups = val;
this.onSettingsChange();
}

isMLALoggingEqual(): boolean {
return (
this.isEqual(this.settings.mlaOptions.loggingEnabled, this.apiSettings.mlaOptions.loggingEnabled) &&
Expand Down Expand Up @@ -368,6 +377,11 @@ export class DefaultsComponent implements OnInit, OnDestroy {
patch.disabledAuditWebhookBackendDCs = this.settings.disabledAuditWebhookBackendDCs;
}

// Send full adminGroups array to avoid merge-patch issues with removals.
if (patch.adminGroups) {
patch.adminGroups = this.settings.adminGroups;
}

return patch;
}
}
19 changes: 19 additions & 0 deletions modules/web/src/app/settings/admin/defaults/template.html
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,25 @@
fxFlex />
</div>
</div>
<div fxLayout="row"
fxLayoutAlign=" center">
<div fxFlex="16%"
fxLayoutAlign=" center"
class="entry-label">
<span>Admin Groups</span>
<div class="km-icon-info km-pointer"
matTooltip="Members of these OIDC groups are automatically granted administrator privileges when they log in. Matching is exact and case-sensitive. Removing a group here demotes only the users whose admin rights were granted through it."></div>
</div>
<div fxFlex
fxLayout="row"
fxLayoutGap="10px">
<km-chip-list label="Admin Groups"
[tags]="adminGroups"
(onChange)="onAdminGroupsChange($event)"
description="OIDC group names whose members become KKP administrators. Use comma, space or enter key as the separator."
fxFlex="50%" />
</div>
</div>
<div fxLayout="row"
fxLayoutAlign=" center">
<div fxFlex="16%"
Expand Down
1 change: 1 addition & 0 deletions modules/web/src/app/shared/entity/member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ export class Admin {
email?: string;
isAdmin?: boolean;
isGlobalViewer?: boolean;
grantedByGroup?: string; // Read-only; set when admin was granted via an OIDC group.
}
2 changes: 2 additions & 0 deletions modules/web/src/app/shared/entity/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface AdminSettings {
// disabledAuditWebhookBackendDCs is the list of datacenters for which the Audit Webhook Backend
// option is hidden in the cluster wizard and edit-cluster dialog.
disabledAuditWebhookBackendDCs?: string[];
adminGroups: string[];
userProjectsLimit: number;
restrictProjectCreation: boolean;
restrictProjectDeletion: boolean;
Expand Down Expand Up @@ -240,6 +241,7 @@ export const DEFAULT_ADMIN_SETTINGS: AdminSettings = {
enableShareCluster: false,
disableAdminKubeconfig: false,
disabledAuditWebhookBackendDCs: [],
adminGroups: [],
enableOIDCKubeconfig: false,
enableClusterBackups: false,
enableEtcdBackup: false,
Expand Down