mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-05 12:26:21 +02:00
feat: allow taloscl disk wipe in maintenance mode
Fixes #10011 Also implement a hidden option to skip secondary disks check which allows to wipe disks which are used as part of active LVM volume. This is unsafe in general, but sometimes if you know what you're doing, it's fine. Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
parent
850579448e
commit
52656cc3c1
@ -93,6 +93,8 @@ message BlockDeviceWipeDescriptor {
|
||||
Method method = 2;
|
||||
// Skip the volume in use check.
|
||||
bool skip_volume_check = 3;
|
||||
// Skip the secondary disk check (e.g. underlying disk for RAID or LVM).
|
||||
bool skip_secondary_check = 5;
|
||||
// Drop the partition (only applies if the device is a partition).
|
||||
bool drop_partition = 4;
|
||||
}
|
||||
|
||||
@ -23,9 +23,11 @@ var wipeCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
var wipeDiskCmdFlags struct {
|
||||
wipeMethod string
|
||||
skipVolumeCheck bool
|
||||
dropPartition bool
|
||||
wipeMethod string
|
||||
skipVolumeCheck bool
|
||||
skipSecondaryCheck bool
|
||||
dropPartition bool
|
||||
insecure bool
|
||||
}
|
||||
|
||||
// wipeDiskCmd represents the wipe disk command.
|
||||
@ -37,26 +39,35 @@ var wipeDiskCmd = &cobra.Command{
|
||||
Use device names as arguments, for example: vda or sda5.`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return WithClient(func(ctx context.Context, c *client.Client) error {
|
||||
method, ok := storage.BlockDeviceWipeDescriptor_Method_value[wipeDiskCmdFlags.wipeMethod]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid wipe method %q", wipeDiskCmdFlags.wipeMethod)
|
||||
}
|
||||
if wipeDiskCmdFlags.insecure {
|
||||
return WithClientMaintenance(nil, cmdWipe(args))
|
||||
}
|
||||
|
||||
return c.BlockDeviceWipe(ctx, &storage.BlockDeviceWipeRequest{
|
||||
Devices: xslices.Map(args, func(devName string) *storage.BlockDeviceWipeDescriptor {
|
||||
return &storage.BlockDeviceWipeDescriptor{
|
||||
Device: devName,
|
||||
Method: storage.BlockDeviceWipeDescriptor_Method(method),
|
||||
SkipVolumeCheck: wipeDiskCmdFlags.skipVolumeCheck,
|
||||
DropPartition: wipeDiskCmdFlags.dropPartition,
|
||||
}
|
||||
}),
|
||||
})
|
||||
})
|
||||
return WithClient(cmdWipe(args))
|
||||
},
|
||||
}
|
||||
|
||||
func cmdWipe(args []string) func(ctx context.Context, c *client.Client) error {
|
||||
return func(ctx context.Context, c *client.Client) error {
|
||||
method, ok := storage.BlockDeviceWipeDescriptor_Method_value[wipeDiskCmdFlags.wipeMethod]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid wipe method %q", wipeDiskCmdFlags.wipeMethod)
|
||||
}
|
||||
|
||||
return c.BlockDeviceWipe(ctx, &storage.BlockDeviceWipeRequest{
|
||||
Devices: xslices.Map(args, func(devName string) *storage.BlockDeviceWipeDescriptor {
|
||||
return &storage.BlockDeviceWipeDescriptor{
|
||||
Device: devName,
|
||||
Method: storage.BlockDeviceWipeDescriptor_Method(method),
|
||||
SkipVolumeCheck: wipeDiskCmdFlags.skipVolumeCheck,
|
||||
SkipSecondaryCheck: wipeDiskCmdFlags.skipSecondaryCheck,
|
||||
DropPartition: wipeDiskCmdFlags.dropPartition,
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func wipeMethodValues() []string {
|
||||
var method storage.BlockDeviceWipeDescriptor_Method
|
||||
|
||||
@ -74,8 +85,11 @@ func init() {
|
||||
|
||||
wipeDiskCmd.Flags().StringVar(&wipeDiskCmdFlags.wipeMethod, "method", wipeMethodValues()[0], fmt.Sprintf("wipe method to use %s", wipeMethodValues()))
|
||||
wipeDiskCmd.Flags().BoolVar(&wipeDiskCmdFlags.skipVolumeCheck, "skip-volume-check", false, "skip volume check")
|
||||
wipeDiskCmd.Flags().BoolVar(&wipeDiskCmdFlags.skipSecondaryCheck, "skip-secondary-check", false, "skip secondary disk check (e.g. underlying disk for RAID or LVM), use with caution")
|
||||
wipeDiskCmd.Flags().BoolVar(&wipeDiskCmdFlags.dropPartition, "drop-partition", false, "drop partition after wipe (if applicable)")
|
||||
wipeDiskCmd.Flags().MarkHidden("skip-volume-check") //nolint:errcheck
|
||||
wipeDiskCmd.Flags().MarkHidden("skip-volume-check") //nolint:errcheck
|
||||
wipeDiskCmd.Flags().MarkHidden("skip-secondary-check") //nolint:errcheck
|
||||
wipeDiskCmd.Flags().BoolVarP(&wipeDiskCmdFlags.insecure, "insecure", "i", false, "use Talos maintenance mode API")
|
||||
|
||||
wipeCmd.AddCommand(wipeDiskCmd)
|
||||
}
|
||||
|
||||
@ -95,6 +95,12 @@ Talosctl now returns the loaded modules, not the modules configured to be loaded
|
||||
description = """\
|
||||
Talos now publishes Software Bill of Materials (SBOM) in the SPDX format.
|
||||
The SBOM is available in the `/usr/share/sbom` directory on the machine and can be retrieved using `talosctl get sbom`.
|
||||
"""
|
||||
|
||||
[notes.disk_wipe]
|
||||
title = "Disk Wipe"
|
||||
description = """\
|
||||
Talos now supports `talosctl disk wipe` command in maintenance mode (`talosctl disk wipe <disk> --insecure`).
|
||||
"""
|
||||
|
||||
[make_deps]
|
||||
|
||||
@ -24,10 +24,8 @@ import (
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
|
||||
"github.com/siderolabs/talos/pkg/grpc/middleware/authz"
|
||||
"github.com/siderolabs/talos/pkg/machinery/api/storage"
|
||||
"github.com/siderolabs/talos/pkg/machinery/resources/block"
|
||||
"github.com/siderolabs/talos/pkg/machinery/role"
|
||||
)
|
||||
|
||||
// Server implements storage.StorageService.
|
||||
@ -103,14 +101,11 @@ func (s *Server) Disks(ctx context.Context, in *emptypb.Empty) (reply *storage.D
|
||||
func (s *Server) BlockDeviceWipe(ctx context.Context, req *storage.BlockDeviceWipeRequest) (*storage.BlockDeviceWipeResponse, error) {
|
||||
// the storage server is included both into machined and maintenance service
|
||||
// in apid/machined mode, the normal authz checks are used before reaching this method
|
||||
// in maintenance mode, do the role check, which maps today to SideroLink API connection
|
||||
if s.MaintenanceMode && !authz.HasRole(ctx, role.Admin) {
|
||||
return nil, status.Error(codes.Unimplemented, "API is not implemented in maintenance mode")
|
||||
}
|
||||
|
||||
// in maintenance mode, we allow this method to be accessible, as it only allows to wipe block devices
|
||||
//
|
||||
// validate the list of devices
|
||||
for _, deviceRequest := range req.GetDevices() {
|
||||
if err := s.validateDeviceForWipe(ctx, deviceRequest.GetDevice(), deviceRequest.GetSkipVolumeCheck()); err != nil {
|
||||
if err := s.validateDeviceForWipe(ctx, deviceRequest.GetDevice(), deviceRequest.GetSkipVolumeCheck(), deviceRequest.GetSkipSecondaryCheck()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@ -130,7 +125,7 @@ func (s *Server) BlockDeviceWipe(ctx context.Context, req *storage.BlockDeviceWi
|
||||
}
|
||||
|
||||
//nolint:gocyclo,cyclop
|
||||
func (s *Server) validateDeviceForWipe(ctx context.Context, deviceName string, skipVolumeCheck bool) error {
|
||||
func (s *Server) validateDeviceForWipe(ctx context.Context, deviceName string, skipVolumeCheck, skipSecondaryCheck bool) error {
|
||||
// first, resolve the blockdevice and figure out what type it is
|
||||
st := s.Controller.Runtime().State().V1Alpha2().Resources()
|
||||
|
||||
@ -177,59 +172,59 @@ func (s *Server) validateDeviceForWipe(ctx context.Context, deviceName string, s
|
||||
}
|
||||
|
||||
// secondaries check
|
||||
switch deviceType {
|
||||
case block.DeviceTypeDisk: // for disks, check secondaries even if the partition is used as secondary (track via Disk resource)
|
||||
disks, err := safe.StateListAll[*block.Disk](ctx, st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !skipSecondaryCheck {
|
||||
switch deviceType {
|
||||
case block.DeviceTypeDisk: // for disks, check secondaries even if the partition is used as secondary (track via Disk resource)
|
||||
disks, err := safe.StateListAll[*block.Disk](ctx, st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for disk := range disks.All() {
|
||||
if slices.Index(disk.TypedSpec().SecondaryDisks, deviceName) != -1 {
|
||||
return status.Errorf(codes.FailedPrecondition, "blockdevice %q is in use by disk %q", deviceName, disk.Metadata().ID())
|
||||
for disk := range disks.All() {
|
||||
if slices.Index(disk.TypedSpec().SecondaryDisks, deviceName) != -1 {
|
||||
return status.Errorf(codes.FailedPrecondition, "blockdevice %q is in use by disk %q", deviceName, disk.Metadata().ID())
|
||||
}
|
||||
}
|
||||
case block.DeviceTypePartition: // for partitions, check secondaries only if the partition is used as a secondary
|
||||
blockdevices, err := safe.StateListAll[*block.Device](ctx, st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for blockdevice := range blockdevices.All() {
|
||||
if slices.Index(blockdevice.TypedSpec().Secondaries, deviceName) != -1 {
|
||||
return status.Errorf(codes.FailedPrecondition, "blockdevice %q is in use by blockdevice %q", deviceName, blockdevice.Metadata().ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
case block.DeviceTypePartition: // for partitions, check secondaries only if the partition is used as a secondary
|
||||
blockdevices, err := safe.StateListAll[*block.Device](ctx, st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for blockdevice := range blockdevices.All() {
|
||||
if slices.Index(blockdevice.TypedSpec().Secondaries, deviceName) != -1 {
|
||||
return status.Errorf(codes.FailedPrecondition, "blockdevice %q is in use by blockdevice %q", deviceName, blockdevice.Metadata().ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if skipVolumeCheck {
|
||||
return nil
|
||||
}
|
||||
|
||||
// volume in use checks
|
||||
volumeStatuses, err := safe.StateListAll[*block.VolumeStatus](ctx, st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for volumeStatus := range volumeStatuses.All() {
|
||||
for _, location := range []string{
|
||||
filepath.Base(volumeStatus.TypedSpec().Location),
|
||||
filepath.Base(volumeStatus.TypedSpec().MountLocation),
|
||||
} {
|
||||
for _, dev := range []string{deviceName, parent} {
|
||||
if dev == "" || location == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if location == dev {
|
||||
return status.Errorf(codes.FailedPrecondition, "blockdevice %q is in use by volume %q", dev, volumeStatus.Metadata().ID())
|
||||
}
|
||||
}
|
||||
if !skipVolumeCheck {
|
||||
volumeStatuses, err := safe.StateListAll[*block.VolumeStatus](ctx, st)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if filepath.Base(volumeStatus.TypedSpec().ParentLocation) == deviceName {
|
||||
return status.Errorf(codes.FailedPrecondition, "blockdevice %q is in use by volume %q", deviceName, volumeStatus.Metadata().ID())
|
||||
for volumeStatus := range volumeStatuses.All() {
|
||||
for _, location := range []string{
|
||||
filepath.Base(volumeStatus.TypedSpec().Location),
|
||||
filepath.Base(volumeStatus.TypedSpec().MountLocation),
|
||||
} {
|
||||
for _, dev := range []string{deviceName, parent} {
|
||||
if dev == "" || location == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if location == dev {
|
||||
return status.Errorf(codes.FailedPrecondition, "blockdevice %q is in use by volume %q", dev, volumeStatus.Metadata().ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if filepath.Base(volumeStatus.TypedSpec().ParentLocation) == deviceName {
|
||||
return status.Errorf(codes.FailedPrecondition, "blockdevice %q is in use by volume %q", deviceName, volumeStatus.Metadata().ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -441,6 +441,8 @@ type BlockDeviceWipeDescriptor struct {
|
||||
Method BlockDeviceWipeDescriptor_Method `protobuf:"varint,2,opt,name=method,proto3,enum=storage.BlockDeviceWipeDescriptor_Method" json:"method,omitempty"`
|
||||
// Skip the volume in use check.
|
||||
SkipVolumeCheck bool `protobuf:"varint,3,opt,name=skip_volume_check,json=skipVolumeCheck,proto3" json:"skip_volume_check,omitempty"`
|
||||
// Skip the secondary disk check (e.g. underlying disk for RAID or LVM).
|
||||
SkipSecondaryCheck bool `protobuf:"varint,5,opt,name=skip_secondary_check,json=skipSecondaryCheck,proto3" json:"skip_secondary_check,omitempty"`
|
||||
// Drop the partition (only applies if the device is a partition).
|
||||
DropPartition bool `protobuf:"varint,4,opt,name=drop_partition,json=dropPartition,proto3" json:"drop_partition,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
@ -498,6 +500,13 @@ func (x *BlockDeviceWipeDescriptor) GetSkipVolumeCheck() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *BlockDeviceWipeDescriptor) GetSkipSecondaryCheck() bool {
|
||||
if x != nil {
|
||||
return x.SkipSecondaryCheck
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *BlockDeviceWipeDescriptor) GetDropPartition() bool {
|
||||
if x != nil {
|
||||
return x.DropPartition
|
||||
@ -628,11 +637,12 @@ const file_storage_storage_proto_rawDesc = "" +
|
||||
"\rDisksResponse\x12*\n" +
|
||||
"\bmessages\x18\x01 \x03(\v2\x0e.storage.DisksR\bmessages\"V\n" +
|
||||
"\x16BlockDeviceWipeRequest\x12<\n" +
|
||||
"\adevices\x18\x01 \x03(\v2\".storage.BlockDeviceWipeDescriptorR\adevices\"\xe9\x01\n" +
|
||||
"\adevices\x18\x01 \x03(\v2\".storage.BlockDeviceWipeDescriptorR\adevices\"\x9b\x02\n" +
|
||||
"\x19BlockDeviceWipeDescriptor\x12\x16\n" +
|
||||
"\x06device\x18\x01 \x01(\tR\x06device\x12A\n" +
|
||||
"\x06method\x18\x02 \x01(\x0e2).storage.BlockDeviceWipeDescriptor.MethodR\x06method\x12*\n" +
|
||||
"\x11skip_volume_check\x18\x03 \x01(\bR\x0fskipVolumeCheck\x12%\n" +
|
||||
"\x11skip_volume_check\x18\x03 \x01(\bR\x0fskipVolumeCheck\x120\n" +
|
||||
"\x14skip_secondary_check\x18\x05 \x01(\bR\x12skipSecondaryCheck\x12%\n" +
|
||||
"\x0edrop_partition\x18\x04 \x01(\bR\rdropPartition\"\x1e\n" +
|
||||
"\x06Method\x12\b\n" +
|
||||
"\x04FAST\x10\x00\x12\n" +
|
||||
|
||||
@ -335,6 +335,16 @@ func (m *BlockDeviceWipeDescriptor) MarshalToSizedBufferVT(dAtA []byte) (int, er
|
||||
i -= len(m.unknownFields)
|
||||
copy(dAtA[i:], m.unknownFields)
|
||||
}
|
||||
if m.SkipSecondaryCheck {
|
||||
i--
|
||||
if m.SkipSecondaryCheck {
|
||||
dAtA[i] = 1
|
||||
} else {
|
||||
dAtA[i] = 0
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x28
|
||||
}
|
||||
if m.DropPartition {
|
||||
i--
|
||||
if m.DropPartition {
|
||||
@ -605,6 +615,9 @@ func (m *BlockDeviceWipeDescriptor) SizeVT() (n int) {
|
||||
if m.DropPartition {
|
||||
n += 2
|
||||
}
|
||||
if m.SkipSecondaryCheck {
|
||||
n += 2
|
||||
}
|
||||
n += len(m.unknownFields)
|
||||
return n
|
||||
}
|
||||
@ -1481,6 +1494,26 @@ func (m *BlockDeviceWipeDescriptor) UnmarshalVT(dAtA []byte) error {
|
||||
}
|
||||
}
|
||||
m.DropPartition = bool(v != 0)
|
||||
case 5:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field SkipSecondaryCheck", 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.SkipSecondaryCheck = bool(v != 0)
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
|
||||
|
||||
@ -9280,6 +9280,7 @@ The device should not be used as a secondary (e.g. part of LVM).
|
||||
The name should be submitted without `/dev/` prefix. |
|
||||
| method | [BlockDeviceWipeDescriptor.Method](#storage.BlockDeviceWipeDescriptor.Method) | | Wipe method to use. |
|
||||
| skip_volume_check | [bool](#bool) | | Skip the volume in use check. |
|
||||
| skip_secondary_check | [bool](#bool) | | Skip the secondary disk check (e.g. underlying disk for RAID or LVM). |
|
||||
| drop_partition | [bool](#bool) | | Drop the partition (only applies if the device is a partition). |
|
||||
|
||||
|
||||
|
||||
@ -3162,6 +3162,7 @@ talosctl wipe disk <device names>... [flags]
|
||||
```
|
||||
--drop-partition drop partition after wipe (if applicable)
|
||||
-h, --help help for disk
|
||||
-i, --insecure use Talos maintenance mode API
|
||||
--method string wipe method to use [FAST ZEROES] (default "FAST")
|
||||
```
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user