feat: allow reader access to join token

Explicitly allow readers to read join tokens

Signed-off-by: Edward Sammut Alessi <edward.sammutalessi@siderolabs.com>
This commit is contained in:
Edward Sammut Alessi 2026-04-21 11:47:56 +02:00
parent f221168823
commit be67f710f8
No known key found for this signature in database
GPG Key ID: 65558E016966977A
16 changed files with 574 additions and 63 deletions

View File

@ -196,6 +196,10 @@ type PermissionsSpec struct {
CanManageBackupStore bool `protobuf:"varint,11,opt,name=can_manage_backup_store,json=canManageBackupStore,proto3" json:"can_manage_backup_store,omitempty"`
CanAccessMaintenanceNodes bool `protobuf:"varint,12,opt,name=can_access_maintenance_nodes,json=canAccessMaintenanceNodes,proto3" json:"can_access_maintenance_nodes,omitempty"`
CanReadAuditLog bool `protobuf:"varint,13,opt,name=can_read_audit_log,json=canReadAuditLog,proto3" json:"can_read_audit_log,omitempty"`
CanReadJoinTokens bool `protobuf:"varint,14,opt,name=can_read_join_tokens,json=canReadJoinTokens,proto3" json:"can_read_join_tokens,omitempty"`
CanManageJoinTokens bool `protobuf:"varint,15,opt,name=can_manage_join_tokens,json=canManageJoinTokens,proto3" json:"can_manage_join_tokens,omitempty"`
CanReadInstallationMedia bool `protobuf:"varint,16,opt,name=can_read_installation_media,json=canReadInstallationMedia,proto3" json:"can_read_installation_media,omitempty"`
CanManageInstallationMedia bool `protobuf:"varint,17,opt,name=can_manage_installation_media,json=canManageInstallationMedia,proto3" json:"can_manage_installation_media,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -307,22 +311,55 @@ func (x *PermissionsSpec) GetCanReadAuditLog() bool {
return false
}
func (x *PermissionsSpec) GetCanReadJoinTokens() bool {
if x != nil {
return x.CanReadJoinTokens
}
return false
}
func (x *PermissionsSpec) GetCanManageJoinTokens() bool {
if x != nil {
return x.CanManageJoinTokens
}
return false
}
func (x *PermissionsSpec) GetCanReadInstallationMedia() bool {
if x != nil {
return x.CanReadInstallationMedia
}
return false
}
func (x *PermissionsSpec) GetCanManageInstallationMedia() bool {
if x != nil {
return x.CanManageInstallationMedia
}
return false
}
type ClusterPermissionsSpec struct {
state protoimpl.MessageState `protogen:"open.v1"`
CanAddMachines bool `protobuf:"varint,1,opt,name=can_add_machines,json=canAddMachines,proto3" json:"can_add_machines,omitempty"`
CanRemoveMachines bool `protobuf:"varint,2,opt,name=can_remove_machines,json=canRemoveMachines,proto3" json:"can_remove_machines,omitempty"`
CanRebootMachines bool `protobuf:"varint,3,opt,name=can_reboot_machines,json=canRebootMachines,proto3" json:"can_reboot_machines,omitempty"`
CanUpdateKubernetes bool `protobuf:"varint,4,opt,name=can_update_kubernetes,json=canUpdateKubernetes,proto3" json:"can_update_kubernetes,omitempty"`
CanDownloadKubeconfig bool `protobuf:"varint,5,opt,name=can_download_kubeconfig,json=canDownloadKubeconfig,proto3" json:"can_download_kubeconfig,omitempty"`
CanSyncKubernetesManifests bool `protobuf:"varint,6,opt,name=can_sync_kubernetes_manifests,json=canSyncKubernetesManifests,proto3" json:"can_sync_kubernetes_manifests,omitempty"`
CanUpdateTalos bool `protobuf:"varint,7,opt,name=can_update_talos,json=canUpdateTalos,proto3" json:"can_update_talos,omitempty"`
CanDownloadTalosconfig bool `protobuf:"varint,8,opt,name=can_download_talosconfig,json=canDownloadTalosconfig,proto3" json:"can_download_talosconfig,omitempty"`
CanReadConfigPatches bool `protobuf:"varint,9,opt,name=can_read_config_patches,json=canReadConfigPatches,proto3" json:"can_read_config_patches,omitempty"`
CanManageConfigPatches bool `protobuf:"varint,10,opt,name=can_manage_config_patches,json=canManageConfigPatches,proto3" json:"can_manage_config_patches,omitempty"`
CanManageClusterFeatures bool `protobuf:"varint,11,opt,name=can_manage_cluster_features,json=canManageClusterFeatures,proto3" json:"can_manage_cluster_features,omitempty"`
CanDownloadSupportBundle bool `protobuf:"varint,12,opt,name=can_download_support_bundle,json=canDownloadSupportBundle,proto3" json:"can_download_support_bundle,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
CanAddMachines bool `protobuf:"varint,1,opt,name=can_add_machines,json=canAddMachines,proto3" json:"can_add_machines,omitempty"`
CanRemoveMachines bool `protobuf:"varint,2,opt,name=can_remove_machines,json=canRemoveMachines,proto3" json:"can_remove_machines,omitempty"`
CanRebootMachines bool `protobuf:"varint,3,opt,name=can_reboot_machines,json=canRebootMachines,proto3" json:"can_reboot_machines,omitempty"`
CanUpdateKubernetes bool `protobuf:"varint,4,opt,name=can_update_kubernetes,json=canUpdateKubernetes,proto3" json:"can_update_kubernetes,omitempty"`
CanDownloadKubeconfig bool `protobuf:"varint,5,opt,name=can_download_kubeconfig,json=canDownloadKubeconfig,proto3" json:"can_download_kubeconfig,omitempty"`
CanSyncKubernetesManifests bool `protobuf:"varint,6,opt,name=can_sync_kubernetes_manifests,json=canSyncKubernetesManifests,proto3" json:"can_sync_kubernetes_manifests,omitempty"`
CanUpdateTalos bool `protobuf:"varint,7,opt,name=can_update_talos,json=canUpdateTalos,proto3" json:"can_update_talos,omitempty"`
CanDownloadTalosconfig bool `protobuf:"varint,8,opt,name=can_download_talosconfig,json=canDownloadTalosconfig,proto3" json:"can_download_talosconfig,omitempty"`
CanReadConfigPatches bool `protobuf:"varint,9,opt,name=can_read_config_patches,json=canReadConfigPatches,proto3" json:"can_read_config_patches,omitempty"`
CanManageConfigPatches bool `protobuf:"varint,10,opt,name=can_manage_config_patches,json=canManageConfigPatches,proto3" json:"can_manage_config_patches,omitempty"`
CanManageClusterFeatures bool `protobuf:"varint,11,opt,name=can_manage_cluster_features,json=canManageClusterFeatures,proto3" json:"can_manage_cluster_features,omitempty"`
CanDownloadSupportBundle bool `protobuf:"varint,12,opt,name=can_download_support_bundle,json=canDownloadSupportBundle,proto3" json:"can_download_support_bundle,omitempty"`
CanReadMachineConfig bool `protobuf:"varint,13,opt,name=can_read_machine_config,json=canReadMachineConfig,proto3" json:"can_read_machine_config,omitempty"`
CanManageMachineConfig bool `protobuf:"varint,14,opt,name=can_manage_machine_config,json=canManageMachineConfig,proto3" json:"can_manage_machine_config,omitempty"`
CanReadKernelArgs bool `protobuf:"varint,15,opt,name=can_read_kernel_args,json=canReadKernelArgs,proto3" json:"can_read_kernel_args,omitempty"`
CanManageKernelArgs bool `protobuf:"varint,16,opt,name=can_manage_kernel_args,json=canManageKernelArgs,proto3" json:"can_manage_kernel_args,omitempty"`
CanReadMachinePendingUpdates bool `protobuf:"varint,17,opt,name=can_read_machine_pending_updates,json=canReadMachinePendingUpdates,proto3" json:"can_read_machine_pending_updates,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ClusterPermissionsSpec) Reset() {
@ -439,6 +476,41 @@ func (x *ClusterPermissionsSpec) GetCanDownloadSupportBundle() bool {
return false
}
func (x *ClusterPermissionsSpec) GetCanReadMachineConfig() bool {
if x != nil {
return x.CanReadMachineConfig
}
return false
}
func (x *ClusterPermissionsSpec) GetCanManageMachineConfig() bool {
if x != nil {
return x.CanManageMachineConfig
}
return false
}
func (x *ClusterPermissionsSpec) GetCanReadKernelArgs() bool {
if x != nil {
return x.CanReadKernelArgs
}
return false
}
func (x *ClusterPermissionsSpec) GetCanManageKernelArgs() bool {
if x != nil {
return x.CanManageKernelArgs
}
return false
}
func (x *ClusterPermissionsSpec) GetCanReadMachinePendingUpdates() bool {
if x != nil {
return x.CanReadMachinePendingUpdates
}
return false
}
type LabelsCompletionSpec struct {
state protoimpl.MessageState `protogen:"open.v1"`
Items map[string]*LabelsCompletionSpec_Values `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
@ -755,7 +827,7 @@ const file_omni_specs_virtual_proto_rawDesc = "" +
"\x0fCurrentUserSpec\x12\x1a\n" +
"\bidentity\x18\x01 \x01(\tR\bidentity\x12\x12\n" +
"\x04role\x18\x03 \x01(\tR\x04role\x12\x17\n" +
"\auser_id\x18\x04 \x01(\tR\x06userIdJ\x04\b\x02\x10\x03\"\xdb\x04\n" +
"\auser_id\x18\x04 \x01(\tR\x06userIdJ\x04\b\x02\x10\x03\"\xc3\x06\n" +
"\x0fPermissionsSpec\x12*\n" +
"\x11can_read_clusters\x18\x01 \x01(\bR\x0fcanReadClusters\x12.\n" +
"\x13can_create_clusters\x18\x02 \x01(\bR\x11canCreateClusters\x12(\n" +
@ -768,7 +840,11 @@ const file_omni_specs_virtual_proto_rawDesc = "" +
" \x01(\bR\x1dcanManageMachineConfigPatches\x125\n" +
"\x17can_manage_backup_store\x18\v \x01(\bR\x14canManageBackupStore\x12?\n" +
"\x1ccan_access_maintenance_nodes\x18\f \x01(\bR\x19canAccessMaintenanceNodes\x12+\n" +
"\x12can_read_audit_log\x18\r \x01(\bR\x0fcanReadAuditLog\"\xa5\x05\n" +
"\x12can_read_audit_log\x18\r \x01(\bR\x0fcanReadAuditLog\x12/\n" +
"\x14can_read_join_tokens\x18\x0e \x01(\bR\x11canReadJoinTokens\x123\n" +
"\x16can_manage_join_tokens\x18\x0f \x01(\bR\x13canManageJoinTokens\x12=\n" +
"\x1bcan_read_installation_media\x18\x10 \x01(\bR\x18canReadInstallationMedia\x12A\n" +
"\x1dcan_manage_installation_media\x18\x11 \x01(\bR\x1acanManageInstallationMedia\"\xc5\a\n" +
"\x16ClusterPermissionsSpec\x12(\n" +
"\x10can_add_machines\x18\x01 \x01(\bR\x0ecanAddMachines\x12.\n" +
"\x13can_remove_machines\x18\x02 \x01(\bR\x11canRemoveMachines\x12.\n" +
@ -782,7 +858,12 @@ const file_omni_specs_virtual_proto_rawDesc = "" +
"\x19can_manage_config_patches\x18\n" +
" \x01(\bR\x16canManageConfigPatches\x12=\n" +
"\x1bcan_manage_cluster_features\x18\v \x01(\bR\x18canManageClusterFeatures\x12=\n" +
"\x1bcan_download_support_bundle\x18\f \x01(\bR\x18canDownloadSupportBundle\"\xd2\x01\n" +
"\x1bcan_download_support_bundle\x18\f \x01(\bR\x18canDownloadSupportBundle\x125\n" +
"\x17can_read_machine_config\x18\r \x01(\bR\x14canReadMachineConfig\x129\n" +
"\x19can_manage_machine_config\x18\x0e \x01(\bR\x16canManageMachineConfig\x12/\n" +
"\x14can_read_kernel_args\x18\x0f \x01(\bR\x11canReadKernelArgs\x123\n" +
"\x16can_manage_kernel_args\x18\x10 \x01(\bR\x13canManageKernelArgs\x12F\n" +
" can_read_machine_pending_updates\x18\x11 \x01(\bR\x1ccanReadMachinePendingUpdates\"\xd2\x01\n" +
"\x14LabelsCompletionSpec\x12<\n" +
"\x05items\x18\x01 \x03(\v2&.specs.LabelsCompletionSpec.ItemsEntryR\x05items\x1a\x1e\n" +
"\x06Values\x12\x14\n" +

View File

@ -22,6 +22,10 @@ message PermissionsSpec {
bool can_manage_backup_store = 11;
bool can_access_maintenance_nodes = 12;
bool can_read_audit_log = 13;
bool can_read_join_tokens = 14;
bool can_manage_join_tokens = 15;
bool can_read_installation_media = 16;
bool can_manage_installation_media = 17;
}
message ClusterPermissionsSpec {
@ -37,6 +41,11 @@ message ClusterPermissionsSpec {
bool can_manage_config_patches = 10;
bool can_manage_cluster_features = 11;
bool can_download_support_bundle = 12;
bool can_read_machine_config = 13;
bool can_manage_machine_config = 14;
bool can_read_kernel_args = 15;
bool can_manage_kernel_args = 16;
bool can_read_machine_pending_updates = 17;
}
message LabelsCompletionSpec {

View File

@ -55,6 +55,10 @@ func (m *PermissionsSpec) CloneVT() *PermissionsSpec {
r.CanManageBackupStore = m.CanManageBackupStore
r.CanAccessMaintenanceNodes = m.CanAccessMaintenanceNodes
r.CanReadAuditLog = m.CanReadAuditLog
r.CanReadJoinTokens = m.CanReadJoinTokens
r.CanManageJoinTokens = m.CanManageJoinTokens
r.CanReadInstallationMedia = m.CanReadInstallationMedia
r.CanManageInstallationMedia = m.CanManageInstallationMedia
if len(m.unknownFields) > 0 {
r.unknownFields = make([]byte, len(m.unknownFields))
copy(r.unknownFields, m.unknownFields)
@ -83,6 +87,11 @@ func (m *ClusterPermissionsSpec) CloneVT() *ClusterPermissionsSpec {
r.CanManageConfigPatches = m.CanManageConfigPatches
r.CanManageClusterFeatures = m.CanManageClusterFeatures
r.CanDownloadSupportBundle = m.CanDownloadSupportBundle
r.CanReadMachineConfig = m.CanReadMachineConfig
r.CanManageMachineConfig = m.CanManageMachineConfig
r.CanReadKernelArgs = m.CanReadKernelArgs
r.CanManageKernelArgs = m.CanManageKernelArgs
r.CanReadMachinePendingUpdates = m.CanReadMachinePendingUpdates
if len(m.unknownFields) > 0 {
r.unknownFields = make([]byte, len(m.unknownFields))
copy(r.unknownFields, m.unknownFields)
@ -272,6 +281,18 @@ func (this *PermissionsSpec) EqualVT(that *PermissionsSpec) bool {
if this.CanReadAuditLog != that.CanReadAuditLog {
return false
}
if this.CanReadJoinTokens != that.CanReadJoinTokens {
return false
}
if this.CanManageJoinTokens != that.CanManageJoinTokens {
return false
}
if this.CanReadInstallationMedia != that.CanReadInstallationMedia {
return false
}
if this.CanManageInstallationMedia != that.CanManageInstallationMedia {
return false
}
return string(this.unknownFields) == string(that.unknownFields)
}
@ -324,6 +345,21 @@ func (this *ClusterPermissionsSpec) EqualVT(that *ClusterPermissionsSpec) bool {
if this.CanDownloadSupportBundle != that.CanDownloadSupportBundle {
return false
}
if this.CanReadMachineConfig != that.CanReadMachineConfig {
return false
}
if this.CanManageMachineConfig != that.CanManageMachineConfig {
return false
}
if this.CanReadKernelArgs != that.CanReadKernelArgs {
return false
}
if this.CanManageKernelArgs != that.CanManageKernelArgs {
return false
}
if this.CanReadMachinePendingUpdates != that.CanReadMachinePendingUpdates {
return false
}
return string(this.unknownFields) == string(that.unknownFields)
}
@ -581,6 +617,50 @@ func (m *PermissionsSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.CanManageInstallationMedia {
i--
if m.CanManageInstallationMedia {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0x88
}
if m.CanReadInstallationMedia {
i--
if m.CanReadInstallationMedia {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0x80
}
if m.CanManageJoinTokens {
i--
if m.CanManageJoinTokens {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x78
}
if m.CanReadJoinTokens {
i--
if m.CanReadJoinTokens {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x70
}
if m.CanReadAuditLog {
i--
if m.CanReadAuditLog {
@ -724,6 +804,60 @@ func (m *ClusterPermissionsSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.CanReadMachinePendingUpdates {
i--
if m.CanReadMachinePendingUpdates {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0x88
}
if m.CanManageKernelArgs {
i--
if m.CanManageKernelArgs {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0x80
}
if m.CanReadKernelArgs {
i--
if m.CanReadKernelArgs {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x78
}
if m.CanManageMachineConfig {
i--
if m.CanManageMachineConfig {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x70
}
if m.CanReadMachineConfig {
i--
if m.CanReadMachineConfig {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x68
}
if m.CanDownloadSupportBundle {
i--
if m.CanDownloadSupportBundle {
@ -1233,6 +1367,18 @@ func (m *PermissionsSpec) SizeVT() (n int) {
if m.CanReadAuditLog {
n += 2
}
if m.CanReadJoinTokens {
n += 2
}
if m.CanManageJoinTokens {
n += 2
}
if m.CanReadInstallationMedia {
n += 3
}
if m.CanManageInstallationMedia {
n += 3
}
n += len(m.unknownFields)
return n
}
@ -1279,6 +1425,21 @@ func (m *ClusterPermissionsSpec) SizeVT() (n int) {
if m.CanDownloadSupportBundle {
n += 2
}
if m.CanReadMachineConfig {
n += 2
}
if m.CanManageMachineConfig {
n += 2
}
if m.CanReadKernelArgs {
n += 2
}
if m.CanManageKernelArgs {
n += 3
}
if m.CanReadMachinePendingUpdates {
n += 3
}
n += len(m.unknownFields)
return n
}
@ -1809,6 +1970,86 @@ func (m *PermissionsSpec) UnmarshalVT(dAtA []byte) error {
}
}
m.CanReadAuditLog = bool(v != 0)
case 14:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CanReadJoinTokens", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.CanReadJoinTokens = bool(v != 0)
case 15:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CanManageJoinTokens", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.CanManageJoinTokens = bool(v != 0)
case 16:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CanReadInstallationMedia", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.CanReadInstallationMedia = bool(v != 0)
case 17:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CanManageInstallationMedia", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.CanManageInstallationMedia = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
@ -2100,6 +2341,106 @@ func (m *ClusterPermissionsSpec) UnmarshalVT(dAtA []byte) error {
}
}
m.CanDownloadSupportBundle = bool(v != 0)
case 13:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CanReadMachineConfig", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.CanReadMachineConfig = bool(v != 0)
case 14:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CanManageMachineConfig", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.CanManageMachineConfig = bool(v != 0)
case 15:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CanReadKernelArgs", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.CanReadKernelArgs = bool(v != 0)
case 16:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CanManageKernelArgs", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.CanManageKernelArgs = bool(v != 0)
case 17:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field CanReadMachinePendingUpdates", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.CanReadMachinePendingUpdates = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])

View File

@ -103,11 +103,6 @@
"count": 1
}
},
"src/components/Tabs/TabButton.vue": {
"vue/define-props-destructuring": {
"count": 1
}
},
"src/components/Tabs/TabContent.vue": {
"vue/define-props-destructuring": {
"count": 1

View File

@ -35,6 +35,10 @@ export type PermissionsSpec = {
can_manage_backup_store?: boolean
can_access_maintenance_nodes?: boolean
can_read_audit_log?: boolean
can_read_join_tokens?: boolean
can_manage_join_tokens?: boolean
can_read_installation_media?: boolean
can_manage_installation_media?: boolean
}
export type ClusterPermissionsSpec = {
@ -50,6 +54,11 @@ export type ClusterPermissionsSpec = {
can_manage_config_patches?: boolean
can_manage_cluster_features?: boolean
can_download_support_bundle?: boolean
can_read_machine_config?: boolean
can_manage_machine_config?: boolean
can_read_kernel_args?: boolean
can_manage_kernel_args?: boolean
can_read_machine_pending_updates?: boolean
}
export type LabelsCompletionSpecValues = {

View File

@ -7,6 +7,7 @@ included in the LICENSE file.
<script setup lang="ts">
import { TabsTrigger, type TabsTriggerProps, useForwardPropsEmits } from 'reka-ui'
// eslint-disable-next-line vue/define-props-destructuring
const props = defineProps<TabsTriggerProps>()
const forwarded = useForwardPropsEmits(props)
</script>
@ -14,7 +15,8 @@ const forwarded = useForwardPropsEmits(props)
<template>
<TabsTrigger
v-bind="forwarded"
class="text-sm transition-colors hover:text-naturals-n13 data-[state=active]:text-naturals-n13"
:as="disabled ? 'button' : as"
class="text-sm transition-colors not-data-disabled:hover:text-naturals-n13 data-disabled:cursor-not-allowed data-disabled:opacity-75 data-[state=active]:text-naturals-n13"
>
<slot></slot>
</TabsTrigger>

View File

@ -79,6 +79,10 @@ const permissions = authScope.run(() => {
canReadMachineLogs: computed(() => spec.value?.can_read_machine_logs ?? false),
canReadMachines: computed(() => spec.value?.can_read_machines ?? false),
canRemoveMachines: computed(() => spec.value?.can_remove_machines ?? false),
canReadJoinTokens: computed(() => spec.value?.can_read_join_tokens ?? false),
canManageJoinTokens: computed(() => spec.value?.can_manage_join_tokens ?? false),
canReadInstallationMedia: computed(() => spec.value?.can_read_installation_media ?? false),
canManageInstallationMedia: computed(() => spec.value?.can_manage_installation_media ?? false),
}
})!
@ -129,6 +133,13 @@ export function useClusterPermissions(cluster: MaybeRefOrGetter<string | undefin
canRebootMachines: computed(() => spec.value?.can_reboot_machines ?? false),
canRemoveMachines: computed(() => spec.value?.can_remove_machines ?? false),
canManageClusterFeatures: computed(() => spec.value?.can_manage_cluster_features ?? false),
canReadMachineConfig: computed(() => spec.value?.can_read_machine_config ?? false),
canManageMachineConfig: computed(() => spec.value?.can_manage_machine_config ?? false),
canReadKernelArgs: computed(() => spec.value?.can_read_kernel_args ?? false),
canManageKernelArgs: computed(() => spec.value?.can_manage_kernel_args ?? false),
canReadMachinePendingUpdates: computed(
() => spec.value?.can_read_machine_pending_updates ?? false,
),
}
}

View File

@ -14,13 +14,16 @@ import { DefaultNamespace, MachineStatusType } from '@/api/resources'
import TabButton from '@/components/Tabs/TabButton.vue'
import TabContent from '@/components/Tabs/TabContent.vue'
import Tabs from '@/components/Tabs/Tabs.vue'
import { useClusterPermissions } from '@/methods/auth'
import { useResourceGet } from '@/methods/useResourceGet'
import NodesHeader from '@/views/Nodes/NodesHeader.vue'
definePage({ name: 'NodeDetails' })
const route = useRoute()
const machine = computed(() => route.params.machine as string)
const machine = computed(() => route.params.machine)
const { canReadMachineConfig, canReadConfigPatches, canReadMachinePendingUpdates } =
useClusterPermissions(() => route.params.cluster)
const routes = computed(() => {
return [
@ -39,18 +42,22 @@ const routes = computed(() => {
{
name: 'Config',
to: { name: 'NodeConfig', params: { machine: machine.value } },
disabled: !canReadMachineConfig.value,
},
{
name: 'Pending Updates',
to: { name: 'NodePendingUpdates', params: { machine: machine.value } },
disabled: !canReadMachinePendingUpdates.value,
},
{
name: 'Config History',
to: { name: 'NodeConfigDiffs', params: { machine: machine.value } },
disabled: !canReadMachineConfig.value,
},
{
name: 'Patches',
to: { name: 'NodePatches', params: { machine: machine.value } },
disabled: !canReadConfigPatches.value,
},
{
name: 'Disks',
@ -96,7 +103,14 @@ const nodeName = computed(
tabs-list-class="px-4 md:px-6"
>
<template #triggers>
<TabButton v-for="{ name, to } in routes" :key="name" :as="RouterLink" :value="to.name" :to>
<TabButton
v-for="{ name, to, disabled } in routes"
:key="name"
:as="RouterLink"
:value="to.name"
:to
:disabled
>
{{ name }}
</TabButton>
</template>

View File

@ -15,6 +15,7 @@ import PageHeader from '@/components/PageHeader.vue'
import TabButton from '@/components/Tabs/TabButton.vue'
import TabContent from '@/components/Tabs/TabContent.vue'
import Tabs from '@/components/Tabs/Tabs.vue'
import { usePermissions } from '@/methods/auth'
import { useResourceGet } from '@/methods/useResourceGet'
definePage({
@ -24,6 +25,8 @@ definePage({
},
})
const { canReadMachineConfigPatches } = usePermissions()
const routes = computed(() => {
return [
{
@ -33,6 +36,7 @@ const routes = computed(() => {
{
name: 'Patches',
to: { name: 'MachineConfigPatches' },
disabled: !canReadMachineConfigPatches.value,
},
]
})
@ -73,7 +77,14 @@ const hasMatchingTab = computed(() =>
tabs-list-class="px-4 md:px-6"
>
<template #triggers>
<TabButton v-for="{ name, to } in routes" :key="name" :as="RouterLink" :value="to.name" :to>
<TabButton
v-for="{ name, to, disabled } in routes"
:key="name"
:as="RouterLink"
:value="to.name"
:to
:disabled
>
{{ name }}
</TabButton>
</template>

View File

@ -38,7 +38,7 @@ definePage({ name: 'JoinTokens' })
const router = useRouter()
const { copy } = useClipboard()
const { canManageUsers } = usePermissions()
const { canManageJoinTokens, canReadJoinTokens } = usePermissions()
const showTokens = ref(false)
@ -129,7 +129,7 @@ const openDeleteToken = (token: string) => {
icon="plus"
icon-position="left"
variant="highlighted"
:disabled="!canManageUsers"
:disabled="!canManageJoinTokens"
@click="openUserCreate"
>
Create Join Token
@ -175,14 +175,23 @@ const openDeleteToken = (token: string) => {
</div>
<TActionsBox>
<template v-if="item.spec.state === JoinTokenStatusSpecState.ACTIVE">
<TActionsBoxItem icon="copy" @select="() => copyValue(item.metadata.id!)">
<TActionsBoxItem
icon="copy"
:disabled="!canReadJoinTokens"
@select="() => copyValue(item.metadata.id!)"
>
Copy Token
</TActionsBoxItem>
<TActionsBoxItem icon="copy" @select="() => copyKernelParams(item.metadata.id!)">
<TActionsBoxItem
icon="copy"
:disabled="!canReadJoinTokens"
@select="() => copyKernelParams(item.metadata.id!)"
>
Copy Kernel Params
</TActionsBoxItem>
<TActionsBoxItem
icon="long-arrow-down"
:disabled="!canReadJoinTokens"
@select="() => getMachineJoinConfig(item.metadata.id!)"
>
Download Machine Join Config
@ -191,6 +200,7 @@ const openDeleteToken = (token: string) => {
<TActionsBoxItem
v-if="!item.spec.is_default"
icon="check"
:disabled="!canManageJoinTokens"
@select="() => makeDefault(item.metadata.id!)"
>
Make Default
@ -199,18 +209,24 @@ const openDeleteToken = (token: string) => {
<TActionsBoxItem
icon="error"
danger
:disabled="!canManageJoinTokens"
@select="() => openRevokeToken(item.metadata.id!)"
>
Revoke
</TActionsBoxItem>
</template>
<template v-else>
<TActionsBoxItem icon="reset" @select="unrevokeJoinToken(item.metadata.id!)">
<TActionsBoxItem
icon="reset"
:disabled="!canManageJoinTokens"
@select="unrevokeJoinToken(item.metadata.id!)"
>
Unrevoke
</TActionsBoxItem>
<TActionsBoxItem
icon="delete"
danger
:disabled="!canManageJoinTokens"
@select="() => openDeleteToken(item.metadata.id!)"
>
Delete

View File

@ -37,7 +37,7 @@ import { useResourceWatch } from '@/methods/useResourceWatch'
import HomeGeneralInformationCopyable from '@/views/Home/HomeGeneralInformationCopyable.vue'
const features = useFeatures()
const { canReadAuditLog } = usePermissions()
const { canReadJoinTokens, canReadAuditLog } = usePermissions()
const auditLogAvailable = computed(() => !!features.data.value?.spec.audit_log_enabled)
const { copy, copied } = useClipboard()
@ -104,6 +104,7 @@ const {
/>
<HomeGeneralInformationCopyable
v-if="canReadJoinTokens"
title="Join Token"
secret
:value="joinTokenData?.spec.token_id"
@ -124,11 +125,21 @@ const {
Download Installation Media
</TButton>
<TButton icon="long-arrow-down" icon-position="left" @click="downloadMachineJoinConfig()">
<TButton
v-if="canReadJoinTokens"
icon="long-arrow-down"
icon-position="left"
@click="downloadMachineJoinConfig()"
>
Download Machine Join Config
</TButton>
<TButton :icon="copied ? 'check' : 'copy'" icon-position="left" @click="copyKernelArgs">
<TButton
v-if="canReadJoinTokens"
:icon="copied ? 'check' : 'copy'"
icon-position="left"
@click="copyKernelArgs"
>
Copy Kernel Parameters
</TButton>
</section>

View File

@ -13,14 +13,14 @@ import WordHighlighter from 'vue-word-highlighter'
import type { Resource } from '@/api/grpc'
import type { MachineStatusLinkSpec } from '@/api/omni/specs/ephemeral.pb'
import { MachineStatusSpecPowerState } from '@/api/omni/specs/omni.pb'
import { MachineStatusLabelInstalled } from '@/api/resources'
import { LabelCluster, MachineStatusLabelInstalled } from '@/api/resources'
import TActionsBox from '@/components/ActionsBox/TActionsBox.vue'
import TActionsBoxItem from '@/components/ActionsBox/TActionsBoxItem.vue'
import TCheckbox from '@/components/Checkbox/TCheckbox.vue'
import CopyButton from '@/components/CopyButton/CopyButton.vue'
import TIcon from '@/components/Icon/TIcon.vue'
import Tooltip from '@/components/Tooltip/Tooltip.vue'
import { usePermissions } from '@/methods/auth'
import { useClusterPermissions, usePermissions } from '@/methods/auth'
import type { Label } from '@/methods/labels'
import { addMachineLabels, removeMachineLabels } from '@/methods/machine'
import ItemLabels from '@/views/ItemLabels/ItemLabels.vue'
@ -43,6 +43,10 @@ const { copy } = useClipboard()
const { canAccessMaintenanceNodes, canReadClusters, canReadMachineLogs, canRemoveMachines } =
usePermissions()
const { canManageKernelArgs, canReadConfigPatches } = useClusterPermissions(
() => machine.metadata.labels?.[LabelCluster],
)
const machineName = computed(() => {
return machine.spec.message_status?.network?.hostname ?? machine.metadata.id
})
@ -157,6 +161,7 @@ const maintenanceUpdateDescription = computed(() => {
<TActionsBox>
<TActionsBoxItem
icon="settings"
:disabled="!canReadConfigPatches"
@select="
$router.push({
name: 'MachineConfigPatches',
@ -169,6 +174,7 @@ const maintenanceUpdateDescription = computed(() => {
<TActionsBoxItem
icon="settings"
:disabled="!canManageKernelArgs"
@select="
$router.push({
query: {
@ -186,9 +192,9 @@ const maintenanceUpdateDescription = computed(() => {
</TActionsBoxItem>
<TActionsBoxItem
v-if="canRemoveMachines"
icon="delete"
danger
:disabled="!canRemoveMachines"
@select="
$router.push({
query: {

View File

@ -635,6 +635,10 @@ func (s *managementServer) MaintenanceUpgrade(ctx context.Context, req *manageme
}
func (s *managementServer) GetMachineJoinConfig(ctx context.Context, request *management.GetMachineJoinConfigRequest) (*management.GetMachineJoinConfigResponse, error) {
if _, err := auth.CheckGRPC(ctx, auth.WithRole(role.Reader)); err != nil {
return nil, err
}
ctx = actor.MarkContextAsInternalActor(ctx)
apiConfig, err := safe.StateGetByID[*siderolinkres.APIConfig](ctx, s.omniState, siderolinkres.ConfigID)

View File

@ -456,6 +456,8 @@ func filterAccess(ctx context.Context, access state.Access) error {
siderolink.PendingMachineType,
siderolink.LinkStatusType,
siderolink.JoinTokenStatusType,
siderolink.JoinTokenType,
siderolink.DefaultJoinTokenType,
siderolink.NodeUniqueTokenStatusType,
siderolink.GRPCTunnelConfigType,
omni.MachineClassType,
@ -485,6 +487,10 @@ func filterAccess(ctx context.Context, access state.Access) error {
virtual.MetalPlatformConfigType,
virtual.KubernetesUsageType:
_, err = auth.CheckGRPC(ctx, auth.WithRole(verbToRole(access.Verb)))
if err == nil && access.Verb == state.Create && access.ResourceType == siderolink.JoinTokenType {
err = status.Error(codes.PermissionDenied, "only read, update and destroy access is permitted, create should be done via the management.CreateJoinToken API call")
}
case
meta.NamespaceType,
meta.ResourceDefinitionType,
@ -514,17 +520,11 @@ func filterAccess(ctx context.Context, access state.Access) error {
authres.ServiceAccountStatusType,
authres.SAMLLabelRuleType,
authres.AccessPolicyType,
siderolink.JoinTokenType,
siderolink.DefaultJoinTokenType,
omni.EtcdBackupS3ConfType,
infra.ProviderType,
omni.InfraMachineBMCConfigType:
_, err = auth.CheckGRPC(ctx, auth.WithRole(role.Admin))
if err == nil && access.Verb == state.Create && access.ResourceType == siderolink.JoinTokenType {
err = status.Error(codes.PermissionDenied, "only read, update and destroy access is permitted, create should be done via the management.CreateJoinToken API call")
}
// Restrict direct mutations on Identity and User resources — these must go through ManagementService gRPC endpoints.
if err == nil && !access.Verb.Readonly() && (access.ResourceType == authres.IdentityType || access.ResourceType == authres.UserType) {
err = status.Errorf(codes.PermissionDenied, "only read access is permitted on resource %v, mutations should be done via ManagementService API calls", access.ResourceType)

View File

@ -255,17 +255,21 @@ func (v *State) permissions(ctx context.Context) (*virtual.Permissions, error) {
permissions.TypedSpec().Value.CanReadMachineLogs = isReader
permissions.TypedSpec().Value.CanReadClusters = isReader
permissions.TypedSpec().Value.CanReadMachines = isReader
permissions.TypedSpec().Value.CanReadJoinTokens = isReader
permissions.TypedSpec().Value.CanReadInstallationMedia = isReader
isOperator := userRole.Check(role.Operator) == nil
permissions.TypedSpec().Value.CanRemoveMachines = isOperator
permissions.TypedSpec().Value.CanCreateClusters = isOperator
permissions.TypedSpec().Value.CanManageMachineConfigPatches = isOperator
permissions.TypedSpec().Value.CanAccessMaintenanceNodes = isOperator
permissions.TypedSpec().Value.CanManageInstallationMedia = isOperator
isAdmin := userRole.Check(role.Admin) == nil
permissions.TypedSpec().Value.CanManageUsers = isAdmin
permissions.TypedSpec().Value.CanManageBackupStore = isAdmin
permissions.TypedSpec().Value.CanReadAuditLog = isAdmin
permissions.TypedSpec().Value.CanManageJoinTokens = isAdmin
if !permissions.TypedSpec().Value.CanCreateClusters {
_, err := safe.StateGet[*authres.AccessPolicy](ctx, v.PrimaryState, authres.NewAccessPolicy().Metadata())
@ -299,6 +303,9 @@ func (v *State) clusterPermissions(ctx context.Context, ptr resource.Pointer) (*
clusterPermissions.TypedSpec().Value.CanDownloadKubeconfig = true
clusterPermissions.TypedSpec().Value.CanDownloadTalosconfig = true
clusterPermissions.TypedSpec().Value.CanReadConfigPatches = true
clusterPermissions.TypedSpec().Value.CanReadMachineConfig = true
clusterPermissions.TypedSpec().Value.CanReadKernelArgs = true
clusterPermissions.TypedSpec().Value.CanReadMachinePendingUpdates = true
}
if userRole.Check(role.Operator) == nil {
@ -311,6 +318,8 @@ func (v *State) clusterPermissions(ctx context.Context, ptr resource.Pointer) (*
clusterPermissions.TypedSpec().Value.CanSyncKubernetesManifests = true
clusterPermissions.TypedSpec().Value.CanManageClusterFeatures = true
clusterPermissions.TypedSpec().Value.CanDownloadSupportBundle = true
clusterPermissions.TypedSpec().Value.CanManageMachineConfig = true
clusterPermissions.TypedSpec().Value.CanManageKernelArgs = true
}
version, err := resource.ParseVersion("1")

View File

@ -858,12 +858,10 @@ func AssertResourceAuthz(rootCtx context.Context, rootCli *client.Client, client
{
resource: joinToken,
allowedVerbSet: xslices.ToSet([]state.Verb{state.Get, state.List, state.Update, state.Destroy}),
isAdminOnly: true,
},
{
resource: defaultJoinToken,
allowedVerbSet: allVerbsSet,
isAdminOnly: true,
isDestroyNotAllowed: true,
},
{
@ -1483,26 +1481,20 @@ func AssertResourceAuthz(rootCtx context.Context, rootCli *client.Client, client
isOperator := testRole.Check(role.Operator) == nil
isAdmin := testRole.Check(role.Admin) == nil
_, verbAllowed := tc.allowedVerbSet[testVerb]
sufficientRole := true
if tc.isAdminOnly {
if !isAdmin {
sufficientRole = false
}
} else {
if testVerb.Readonly() {
if !isReader {
sufficientRole = false
}
} else {
if !isOperator {
sufficientRole = false
}
}
}
var sufficientRole bool
if tc.isSignatureSufficient || tc.isPublic {
switch {
case tc.isSignatureSufficient || tc.isPublic:
sufficientRole = true
case tc.isAdminOnly:
sufficientRole = isAdmin
default:
if testVerb.Readonly() {
sufficientRole = isReader
} else {
sufficientRole = isOperator
}
}
switch {