Skip to content

Commit f07a5ff

Browse files
authored
Add BMCUser type and controller (#368)
* My actual changes only * Add BMC user account management methods and update mock data * Add YAML test data files to REUSE.toml annotations * adds missing rbac for bmcsecrets; minor refactoring * removes unused ctx param in redfish funcs
1 parent f345f9c commit f07a5ff

32 files changed

+2012
-5
lines changed

PROJECT

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ resources:
2929
kind: BMCSecret
3030
path: github.com/ironcore-dev/metal-operator/api/v1alpha1
3131
version: v1alpha1
32+
webhooks:
33+
validation: true
34+
webhookVersion: v1
3235
- api:
3336
crdVersion: v1
3437
controller: true
@@ -142,6 +145,14 @@ resources:
142145
kind: BMCVersionSet
143146
path: github.com/ironcore-dev/metal-operator/api/v1alpha1
144147
version: v1alpha1
148+
- api:
149+
crdVersion: v1
150+
controller: true
151+
domain: ironcore.dev
152+
group: metal
153+
kind: BMCUser
154+
path: github.com/ironcore-dev/metal-operator/api/v1alpha1
155+
version: v1alpha1
145156
- api:
146157
crdVersion: v1
147158
controller: true

api/v1alpha1/bmcuser_types.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package v1alpha1
5+
6+
import (
7+
v1 "k8s.io/api/core/v1"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
)
10+
11+
// BMCUserSpec defines the desired state of BMCUser.
12+
type BMCUserSpec struct {
13+
// Username of the BMC user.
14+
UserName string `json:"userName"`
15+
// RoleID is the ID of the role to assign to the user.
16+
// The available roles depend on the BMC implementation.
17+
// For Redfish, common role IDs are "Administrator", "Operator", "ReadOnly".
18+
RoleID string `json:"roleID"`
19+
// Description is an optional description for the BMC user.
20+
Description string `json:"description,omitempty"`
21+
// RotationPeriod defines how often the password should be rotated.
22+
// if not set, the password will not be rotated.
23+
RotationPeriod *metav1.Duration `json:"rotationPeriod,omitempty"`
24+
// BMCSecretRef references the BMCSecret containing the credentials for this user.
25+
// If not set, the operator will generate a secure password based on BMC manufacturer requirements.
26+
BMCSecretRef *v1.LocalObjectReference `json:"bmcSecretRef,omitempty"`
27+
// BMCRef references the BMC this user should be created on.
28+
BMCRef *v1.LocalObjectReference `json:"bmcRef,omitempty"`
29+
}
30+
31+
// BMCUserStatus defines the observed state of BMCUser.
32+
type BMCUserStatus struct {
33+
// EffectiveBMCSecretRef references the BMCSecret currently used for this user.
34+
// This may differ from Spec.BMCSecretRef if the operator generated a password.
35+
EffectiveBMCSecretRef *v1.LocalObjectReference `json:"effectiveBMCSecretRef,omitempty"`
36+
// LastRotation is the timestamp of the last password rotation.
37+
LastRotation *metav1.Time `json:"lastRotation,omitempty"`
38+
// PasswordExpiration is the timestamp when the password will expire.
39+
PasswordExpiration *metav1.Time `json:"passwordExpiration,omitempty"`
40+
// ID of the user in the BMC system
41+
ID string `json:"id,omitempty"`
42+
}
43+
44+
// +kubebuilder:object:root=true
45+
// +kubebuilder:subresource:status
46+
// +kubebuilder:resource:scope=Cluster
47+
48+
// BMCUser is the Schema for the bmcusers API.
49+
type BMCUser struct {
50+
metav1.TypeMeta `json:",inline"`
51+
metav1.ObjectMeta `json:"metadata,omitempty"`
52+
53+
Spec BMCUserSpec `json:"spec,omitempty"`
54+
Status BMCUserStatus `json:"status,omitempty"`
55+
}
56+
57+
// +kubebuilder:object:root=true
58+
59+
// BMCUserList contains a list of BMCUser.
60+
type BMCUserList struct {
61+
metav1.TypeMeta `json:",inline"`
62+
metav1.ListMeta `json:"metadata,omitempty"`
63+
Items []BMCUser `json:"items"`
64+
}
65+
66+
func init() {
67+
SchemeBuilder.Register(&BMCUser{}, &BMCUserList{})
68+
}

api/v1alpha1/constants.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ const (
4848

4949
// OperationAnnotationRetryFailedPropagated restarts the reconciliation of a resource's child from failed state -> initial state.
5050
OperationAnnotationRetryFailedPropagated = "retry-failed-state-resource-propagated"
51+
52+
// OperationAnnotationRotateCredentials is used to indicate that credentials should be rotated.
53+
OperationAnnotationRotateCredentials = "rotate-credentials"
5154
)
5255

5356
const (

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 117 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bmc/bmc.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@ type BMC interface {
113113

114114
// GetBMCUpgradeTask retrieves the task for the BMC upgrade.
115115
GetBMCUpgradeTask(ctx context.Context, manufacturer string, taskURI string) (*redfish.Task, error)
116+
117+
// CreateOrUpdateAccount creates or updates a BMC user account.
118+
CreateOrUpdateAccount(ctx context.Context, userName, role, password string, enabled bool) error
119+
120+
// DeleteAccount deletes a BMC user account.
121+
DeleteAccount(ctx context.Context, userName, id string) error
122+
123+
// GetAccounts retrieves all BMC user accounts.
124+
GetAccounts() ([]*redfish.ManagerAccount, error)
125+
126+
// GetAccountService retrieves the account service.
127+
GetAccountService() (*redfish.AccountService, error)
116128
}
117129

118130
type Entity struct {

bmc/mock/server/data/AccountService/Accounts/1/index.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"Enabled": true,
77
"Password": null,
88
"PasswordChangeRequired": false,
9+
"PasswordExpiration": "2026-12-31T23:59:59Z",
910
"AccountTypes": [
1011
"Redfish"
1112
],
@@ -30,4 +31,4 @@
3031
},
3132
"@odata.id": "/redfish/v1/AccountService/Accounts/1",
3233
"@Redfish.Copyright": "Copyright 2014-2023 DMTF. For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
33-
}
34+
}

bmc/mock/server/data/AccountService/index.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
},
1010
"ServiceEnabled": true,
1111
"AuthFailureLoggingThreshold": 3,
12+
"MaxPasswordLength": 30,
1213
"MinPasswordLength": 8,
1314
"AccountLockoutThreshold": 5,
1415
"AccountLockoutDuration": 30,
@@ -123,4 +124,4 @@
123124
"RequireChangePasswordAction": false,
124125
"@odata.id": "/redfish/v1/AccountService",
125126
"@Redfish.Copyright": "Copyright 2014-2023 DMTF. For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
126-
}
127+
}

bmc/mockup.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33

44
package bmc
55

6-
import "github.com/stmcginnis/gofish/redfish"
6+
import (
7+
"github.com/stmcginnis/gofish/common"
8+
"github.com/stmcginnis/gofish/redfish"
9+
)
710

811
// RedfishMockUps is an implementation of the BMC interface for Redfish.
912
type RedfishMockUps struct {
@@ -22,6 +25,7 @@ type RedfishMockUps struct {
2225
BMCUpgradeTaskIndex int
2326
BMCUpgradeTaskStatus []redfish.Task
2427

28+
Accounts map[string]*redfish.ManagerAccount
2529
SimulateUnvailableBMC bool
2630
}
2731

@@ -107,6 +111,39 @@ func (r *RedfishMockUps) InitializeDefaults() {
107111
},
108112
}
109113

114+
r.Accounts = map[string]*redfish.ManagerAccount{
115+
"foo": {
116+
Entity: common.Entity{
117+
ID: "0",
118+
},
119+
UserName: "foo",
120+
Enabled: true,
121+
RoleID: "ReadOnly",
122+
Locked: false,
123+
Password: "bar",
124+
},
125+
"admin": {
126+
Entity: common.Entity{
127+
ID: "1",
128+
},
129+
130+
UserName: "admin",
131+
Enabled: true,
132+
RoleID: "Administrator",
133+
Locked: false,
134+
Password: "adminpass",
135+
},
136+
"user": {
137+
Entity: common.Entity{
138+
ID: "2",
139+
},
140+
UserName: "user",
141+
Enabled: true,
142+
RoleID: "ReadOnly",
143+
Locked: false,
144+
Password: "userpass",
145+
},
146+
}
110147
r.SimulateUnvailableBMC = false
111148
}
112149

0 commit comments

Comments
 (0)