Repository navigation
Add support for graphical consoles #640
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,22 +21,25 @@ import ( | |
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "github.com/go-logr/logr" | ||
| rabbitmqv1 "github.com/openstack-k8s-operators/infra-operator/apis/rabbitmq/v1beta1" | ||
| topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" | ||
| ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1" | ||
| keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" | ||
| corev1 "k8s.io/api/core/v1" | ||
| rbacv1 "k8s.io/api/rbac/v1" | ||
| k8s_errors "k8s.io/apimachinery/pkg/api/errors" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| ctrl "sigs.k8s.io/controller-runtime" | ||
| "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" | ||
|
|
||
| "github.com/openstack-k8s-operators/lib-common/modules/common/condition" | ||
| "github.com/openstack-k8s-operators/lib-common/modules/common/helper" | ||
| common_rbac "github.com/openstack-k8s-operators/lib-common/modules/common/rbac" | ||
| "github.com/openstack-k8s-operators/lib-common/modules/common/secret" | ||
| "github.com/openstack-k8s-operators/lib-common/modules/common/util" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| ) | ||
|
|
||
| // Static errors for ironic controllers | ||
|
|
@@ -96,6 +99,32 @@ func getCommonRbacRules() []rbacv1.PolicyRule { | |
| Resources: []string{"securitycontextconstraints"}, | ||
| Verbs: []string{"use"}, | ||
| }, | ||
| { | ||
| APIGroups: []string{""}, | ||
| Resources: []string{"pods"}, | ||
| Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"}, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func getGraphicalConsoleRbacRules() []rbacv1.PolicyRule { | ||
| return []rbacv1.PolicyRule{ | ||
| { | ||
| APIGroups: []string{"security.openshift.io"}, | ||
| ResourceNames: []string{"anyuid", "privileged"}, | ||
| Resources: []string{"securitycontextconstraints"}, | ||
| Verbs: []string{"use"}, | ||
| }, | ||
| { | ||
| APIGroups: []string{""}, | ||
| Resources: []string{"pods"}, | ||
| Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"}, | ||
| }, | ||
| { | ||
| APIGroups: []string{""}, | ||
| Resources: []string{"secrets"}, | ||
| Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"}, | ||
| }, | ||
|
Comment on lines
+110
to
+127
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: rg -n '\+kubebuilder:rbac' internal/controller/ | rg -i 'pods|secrets|roles|securitycontextconstraints'
rg -n -B2 -A12 'pods|escalate|bind' config/rbac/role.yamlRepository: openstack-k8s-operators/ironic-operator Length of output: 3527 🏁 Script executed: set -eu
printf '%s\n' '--- funcs.go RBAC helpers and callers ---'
rg -n -B8 -A80 'getCommonRbacRules|getGraphicalConsoleRbacRules|RoleBinding|CreateOrPatch.*Role|rbacv1\.Role' internal/controller/funcs.go internal/controller/*.go || true
printf '%s\n' '--- all controller RBAC marker blocks ---'
for f in internal/controller/*.go; do
if rg -q '\+kubebuilder:rbac' "$f"; then
printf '\n### %s\n' "$f"
rg -n -B3 -A4 '\+kubebuilder:rbac' "$f"
fi
done
printf '%s\n' '--- generated operator Role, complete relevant entries ---'
nl -ba config/rbac/role.yaml | sed -n '1,240p'
printf '%s\n' '--- funcs.go changed helper region ---'
nl -ba internal/controller/funcs.go | sed -n '70,190p'Repository: openstack-k8s-operators/ironic-operator Length of output: 42650 🏁 Script executed: set -eu
printf '%s\n' '--- helper declarations and direct callers ---'
rg -n -B12 -A35 'func getCommonRbacRules|func getGraphicalConsoleRbacRules|getCommonRbacRules\(|getGraphicalConsoleRbacRules\(' internal/controller
printf '%s\n' '--- operator Role binding ---'
rg -n -B8 -A20 'manager-role|roleRef:|serviceAccountName:' config/rbac config/manager
printf '%s\n' '--- generated Role rules for requested resources ---'
awk '
/^- apiGroups:/ { if (NR > 1) print block; block=$0; next }
{ block=block ORS $0 }
END { print block }
' config/rbac/role.yaml | awk '
/resources:/ { inres=1; res=$0; next }
inres && /verbs:/ { inres=0; if (res ~ /pods|secrets|roles|securitycontextconstraints/) print res ORS $0; next }
inres { res=res ORS $0 }
'Repository: openstack-k8s-operators/ironic-operator Length of output: 32617 Update the operator RBAC before reconciling delegated pod Roles. The controllers call Kubernetes can therefore reject the delegated Role or RoleBinding with Suggested fix--- a/internal/controller/ironicapi_controller.go
+++ b/internal/controller/ironicapi_controller.go
@@
// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=core,resources=pods,verbs=create;get;list;watch;update;patch;delete
--- a/internal/controller/ironic_controller.go
+++ b/internal/controller/ironic_controller.go
@@
// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;create;update;patch;delete;watch
+// +kubebuilder:rbac:groups=core,resources=pods,verbs=create;get;list;watch;update;patch;delete
--- a/internal/controller/ironicconductor_controller.go
+++ b/internal/controller/ironicconductor_controller.go
@@
-// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list
+// +kubebuilder:rbac:groups=core,resources=pods,verbs=create;get;list;watch;update;patch;delete
--- a/internal/controller/ironicinspector_controller.go
+++ b/internal/controller/ironicinspector_controller.go
@@
-// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list
+// +kubebuilder:rbac:groups=core,resources=pods,verbs=create;get;list;watch;update;patch;delete
--- a/internal/controller/ironicneutronagent_controller.go
+++ b/internal/controller/ironicneutronagent_controller.go
@@
-// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list
+// +kubebuilder:rbac:groups=core,resources=pods,verbs=create;get;list;watch;update;patch;delete
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@
resources:
- pods
verbs:
+ - create
+ - delete
- get
- list
+ - patch
+ - update
+ - watch🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -166,6 +195,157 @@ func getQuorumQueues( | |
| return quorumQueues, nil | ||
| } | ||
|
|
||
| // getConsoleNamespaceName returns the namespace name for console pods based on the service namespace. | ||
| // The prefix is extracted from the service namespace (e.g., "openstack" -> "openstack-ironic-consoles") | ||
| func getConsoleNamespaceName(serviceNamespace string) string { | ||
| // Extract the prefix from the service namespace (before any hyphen or use full name) | ||
| prefix := serviceNamespace | ||
| if idx := strings.Index(serviceNamespace, "-"); idx > 0 { | ||
| prefix = serviceNamespace[:idx] | ||
| } | ||
| return prefix + "-ironic-consoles" | ||
| } | ||
|
Comment on lines
+200
to
+207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Use a console namespace name that cannot collide. The prefix truncation maps 🤖 Prompt for AI Agents |
||
|
|
||
| // ensureConsoleNamespace creates the console namespace if it doesn't exist | ||
| func ensureConsoleNamespace( | ||
| ctx context.Context, | ||
| h *helper.Helper, | ||
| serviceNamespace string, | ||
| ) error { | ||
| consolesNamespace := getConsoleNamespaceName(serviceNamespace) | ||
|
|
||
| ns := &corev1.Namespace{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: consolesNamespace, | ||
| }, | ||
| } | ||
|
|
||
| op, err := controllerutil.CreateOrPatch(ctx, h.GetClient(), ns, func() error { | ||
| // Set labels | ||
| if ns.Labels == nil { | ||
| ns.Labels = make(map[string]string) | ||
| } | ||
| ns.Labels["app"] = "ironic" | ||
| return nil | ||
| }) | ||
|
|
||
| if err != nil { | ||
| return fmt.Errorf("failed to reconcile console namespace %s: %w", consolesNamespace, err) | ||
| } | ||
|
|
||
| if op != controllerutil.OperationResultNone { | ||
| h.GetLogger().Info(fmt.Sprintf("Namespace %s %s", consolesNamespace, op)) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // reconcileGraphicalConsoleRbac creates a Role and RoleBinding in the console namespace | ||
| // that grants the ServiceAccount from the service namespace permissions to create console pods. | ||
| // This enables cross-namespace RBAC where the ironic ServiceAccount in the 'openstack' namespace | ||
| // can create pods and secrets in the 'openstack-ironic-consoles' namespace. | ||
| // Note: These resources cannot have owner references since cross-namespace ownership is not allowed. | ||
| func reconcileGraphicalConsoleRbac( | ||
| ctx context.Context, | ||
| h *helper.Helper, | ||
| instance common_rbac.Reconciler, | ||
| serviceAccountName string, | ||
| consoleNamespace string, | ||
| rules []rbacv1.PolicyRule, | ||
| ) (ctrl.Result, error) { | ||
| serviceNamespace := instance.RbacNamespace() | ||
| roleName := serviceAccountName + "-console-role" | ||
| roleBindingName := serviceAccountName + "-console-rolebinding" | ||
|
|
||
| labels := map[string]string{ | ||
| "app": "ironic", | ||
| "ironic.openstack.org/service": serviceAccountName, | ||
| } | ||
|
|
||
| // Create or update Role in the console namespace without owner references | ||
| role := &rbacv1.Role{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: roleName, | ||
| Namespace: consoleNamespace, | ||
| }, | ||
| } | ||
|
|
||
| op, err := controllerutil.CreateOrPatch(ctx, h.GetClient(), role, func() error { | ||
| // Set labels | ||
| role.Labels = labels | ||
| // Set rules | ||
| role.Rules = rules | ||
| return nil | ||
| }) | ||
|
|
||
| if err != nil { | ||
| instance.RbacConditionsSet(condition.FalseCondition( | ||
| condition.RoleReadyCondition, | ||
| condition.ErrorReason, | ||
| condition.SeverityWarning, | ||
| condition.RoleReadyErrorMessage, | ||
| err.Error())) | ||
| return ctrl.Result{}, err | ||
| } | ||
|
|
||
| if op != controllerutil.OperationResultNone { | ||
| h.GetLogger().Info(fmt.Sprintf("Role %s %s in namespace %s", roleName, op, consoleNamespace)) | ||
| } | ||
|
|
||
| instance.RbacConditionsSet(condition.TrueCondition( | ||
| condition.RoleReadyCondition, | ||
| condition.RoleReadyMessage)) | ||
|
|
||
| // Create or update RoleBinding in the console namespace that references the ServiceAccount | ||
| // from the service namespace (cross-namespace reference) | ||
| roleBinding := &rbacv1.RoleBinding{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: roleBindingName, | ||
| Namespace: consoleNamespace, | ||
| }, | ||
| } | ||
|
|
||
| op, err = controllerutil.CreateOrPatch(ctx, h.GetClient(), roleBinding, func() error { | ||
| // Set labels | ||
| roleBinding.Labels = labels | ||
| // Set RoleRef (immutable, but safe to set on create) | ||
| roleBinding.RoleRef = rbacv1.RoleRef{ | ||
| APIGroup: "rbac.authorization.k8s.io", | ||
| Kind: "Role", | ||
| Name: roleName, | ||
| } | ||
| // Set Subjects | ||
| roleBinding.Subjects = []rbacv1.Subject{ | ||
| { | ||
| Kind: "ServiceAccount", | ||
| Name: serviceAccountName, | ||
| Namespace: serviceNamespace, | ||
| }, | ||
| } | ||
| return nil | ||
| }) | ||
|
|
||
| if err != nil { | ||
| instance.RbacConditionsSet(condition.FalseCondition( | ||
| condition.RoleBindingReadyCondition, | ||
| condition.ErrorReason, | ||
| condition.SeverityWarning, | ||
| condition.RoleBindingReadyErrorMessage, | ||
| err.Error())) | ||
| return ctrl.Result{}, err | ||
| } | ||
|
|
||
| if op != controllerutil.OperationResultNone { | ||
| h.GetLogger().Info(fmt.Sprintf("RoleBinding %s %s in namespace %s", roleBindingName, op, consoleNamespace)) | ||
| } | ||
|
|
||
| instance.RbacConditionsSet(condition.TrueCondition( | ||
| condition.RoleBindingReadyCondition, | ||
| condition.RoleBindingReadyMessage)) | ||
|
|
||
| return ctrl.Result{}, nil | ||
| } | ||
|
Comment on lines
+248
to
+347
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift Delete the cross-namespace Role and RoleBinding during cleanup. These resources have no owner references, and nothing removes them. Cleanup is missing when consoles are disabled and when the CR is deleted. The bindings remain and continue to grant secret and pod access to any later service account with the same name. Add explicit deletion to 🤖 Prompt for AI AgentsSource: Path instructions |
||
|
|
||
| // setApplicationCredentialParams - shared function to set ApplicationCredential template parameters | ||
| // secretName is the name of the secret containing the application credentials | ||
| // Returns true if application credentials are available and configured | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1053,21 +1053,29 @@ func (r *IronicReconciler) conductorDeploymentCreateOrUpdate( | |
|
|
||
| IronicConductorSpec := ironicv1.IronicConductorSpec{ | ||
| IronicConductorTemplate: conductorSpec, | ||
| ContainerImage: instance.Spec.Images.Conductor, | ||
| PxeContainerImage: instance.Spec.Images.Pxe, | ||
| IronicPythonAgentImage: instance.Spec.Images.IronicPythonAgent, | ||
| Standalone: instance.Spec.Standalone, | ||
| RPCTransport: instance.Spec.RPCTransport, | ||
| Secret: instance.Spec.Secret, | ||
| PasswordSelectors: instance.Spec.PasswordSelectors, | ||
| ServiceUser: instance.Spec.ServiceUser, | ||
| DatabaseAccount: instance.Spec.DatabaseAccount, | ||
| DatabaseHostname: instance.Status.DatabaseHostname, | ||
| TransportURLSecret: transportURLSecretName, | ||
| KeystoneEndpoints: *keystoneEndpoints, | ||
| Region: keystoneRegion, | ||
| TLS: instance.Spec.IronicAPI.TLS.Ca, | ||
| Auth: instance.Spec.Auth, | ||
| // FIXME(stevebaker) drop this when https://github.com/openstack-k8s-operators/s2i-openstack-containers/pull/259 lands | ||
| // ContainerImage: instance.Spec.Images.Conductor, | ||
| ContainerImage: "quay.io/steveb/openstack-ironic-conductor:kubectl", | ||
| PxeContainerImage: instance.Spec.Images.Pxe, | ||
| IronicPythonAgentImage: instance.Spec.Images.IronicPythonAgent, | ||
| Standalone: instance.Spec.Standalone, | ||
| RPCTransport: instance.Spec.RPCTransport, | ||
| Secret: instance.Spec.Secret, | ||
| PasswordSelectors: instance.Spec.PasswordSelectors, | ||
| ServiceUser: instance.Spec.ServiceUser, | ||
| DatabaseAccount: instance.Spec.DatabaseAccount, | ||
| DatabaseHostname: instance.Status.DatabaseHostname, | ||
| TransportURLSecret: transportURLSecretName, | ||
| KeystoneEndpoints: *keystoneEndpoints, | ||
| Region: keystoneRegion, | ||
| TLS: instance.Spec.IronicAPI.TLS.Ca, | ||
| Auth: instance.Spec.Auth, | ||
| GraphicalConsoles: instance.Spec.GraphicalConsoles, | ||
| // FIXME(stevebaker) drop this when https://github.com/openstack-k8s-operators/openstack-operator/pull/1633 lands | ||
| // ConsoleImage: instance.Spec.Images.GraphicalConsole, | ||
| // NoVNCProxyImage: instance.Spec.Images.NoVNCProxy, | ||
| ConsoleImage: "quay.io/openstack-s2i-containers/openstack-ironic-graphical-console:master-latest", | ||
| NoVNCProxyImage: "quay.io/openstack-s2i-containers/openstack-ironic-api:master-latest", | ||
|
Comment on lines
+1056
to
+1078
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win Remove the hardcoded personal and Line 1058 replaces 🧰 Tools🪛 golangci-lint (2.13.2)[error] 1077-1077: File is not properly formatted (gofmt) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| } | ||
|
|
||
| if instance.Status.NotificationsURLSecret != nil { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Remove pod CRUD from
getCommonRbacRules.getCommonRbacRulesis used by every component's service-namespace Role, including Ironic, API, inspector, and INA. Pod create and delete are needed only in the console namespace, andgetGraphicalConsoleRbacRulesalready grants them there. This change broadens privileges for all components. Revert the pod rule and update the test that expects three rules.🤖 Prompt for AI Agents