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
44 changes: 44 additions & 0 deletions config/sed.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,47 @@ def reset_password():
click.echo("Error: SED password reset failed")
except Exception as e:
click.echo(f"Error resetting SED password: {str(e)}")


WIPE_SSD_START_BANNER = (
"=========================================================================\n"
" SSD ERASE STARTED\n"
" * Do NOT power off the switch or interrupt this session.\n"
" * The wipe runs from a RAM-disk and will keep going even if SSH drops.\n"
" * Follow progress in syslog: journalctl -f -t ssd_erase.sh\n"
" * When it finishes, reboot with: sudo /sbin/reboot\n"
"========================================================================="
)


@sed.command('wipe-ssd')
@click.option('-y', '--yes', is_flag=True, default=False,
help='Skip the interactive confirmation prompt.')
def wipe_ssd(yes):
"""Securely wipe the boot SSD (SED PSID revert + NVMe sanitize).

IRREVERSIBLE: after wipe the switch cannot boot until re-imaged.
"""
try:
from sonic_platform import platform
chassis = platform.Platform().get_chassis()
sed_mgmt = chassis.get_sed_mgmt()
if sed_mgmt is None:
click.echo("Error: SED management not supported on this platform")
return
if not yes:
click.confirm(
'This will PERMANENTLY erase the SSD. Continue?',
default=False,
abort=True,
)
click.echo(WIPE_SSD_START_BANNER)
success = sed_mgmt.wipe_ssd()
if success:
click.echo("SSD wipe completed successfully. Reboot now with `sudo /sbin/reboot`.")
else:
click.echo("Error: SSD wipe failed")
except click.exceptions.Abort:
raise
except Exception as e:
click.echo(f"Error wiping SSD: {str(e)}")
32 changes: 32 additions & 0 deletions doc/Command-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -13102,6 +13102,38 @@ This command resets the SED password to the default value.
SED password reset process completed successfully
```

**config sed wipe-ssd**

This command performs a graceful SSD wipe: SED PSID revert (crypto erase) followed by NVMe sanitize (block erase). The operation is **irreversible** - after wipe the switch cannot boot until re-imaged (ONIE / rescue image).

The wipe runs from a RAM-disk after the OS root is pivoted, so it survives an SSH disconnect. Follow progress in syslog via `journalctl -f -t ssd_erase.sh`.

- Usage:

```console
config sed wipe-ssd [-y | --yes]
```

- Options:
- `-y, --yes` : skip the interactive confirmation prompt (for automation).

- Example (interactive):

```console
admin@sonic:~$ config sed wipe-ssd
This will PERMANENTLY erase the SSD. Continue? [y/N]: y
=========================================================================
SSD ERASE STARTED
* Do NOT power off the switch or interrupt this session.
* The wipe runs from a RAM-disk and will keep going even if SSH drops.
* Follow progress in syslog: journalctl -f -t ssd_erase.sh
* When it finishes, reboot with: sudo /sbin/reboot
=========================================================================
SSD wipe completed successfully. Reboot now with `sudo /sbin/reboot`.
```

After the wipe completes, reboot with `sudo /sbin/reboot` (or via BMC / power cycle). The full SONiC `/usr/local/bin/reboot` script is not supported after wipe because it depends on `/host` and Redis, which no longer exist.

Go Back To [Beginning of the document](#) or [Beginning of this section](#sed)

## SNMP
Expand Down
94 changes: 94 additions & 0 deletions tests/sed_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,97 @@ def test_reset_password_exception(self, mock_platform):
[]
)
assert "Error resetting SED password: Reset test error" in result.output

@patch('sonic_platform.platform.Platform')
def test_wipe_ssd_success_confirm_yes(self, mock_platform):
"""wipe-ssd success path with interactive 'y' confirmation."""
runner = CliRunner()
mock_chassis = MagicMock()
mock_sed_mgmt = MagicMock()
mock_sed_mgmt.wipe_ssd.return_value = True
mock_chassis.get_sed_mgmt.return_value = mock_sed_mgmt
mock_platform.return_value.get_chassis.return_value = mock_chassis
result = runner.invoke(
config.config.commands["sed"].commands["wipe-ssd"],
[],
input='y\n',
)
assert "SSD ERASE STARTED" in result.output
assert "SSD wipe completed successfully" in result.output
mock_sed_mgmt.wipe_ssd.assert_called_once()

@patch('sonic_platform.platform.Platform')
def test_wipe_ssd_confirm_abort(self, mock_platform):
"""wipe-ssd aborts and does NOT call wipe_ssd when user answers 'n'."""
runner = CliRunner()
mock_chassis = MagicMock()
mock_sed_mgmt = MagicMock()
mock_chassis.get_sed_mgmt.return_value = mock_sed_mgmt
mock_platform.return_value.get_chassis.return_value = mock_chassis
result = runner.invoke(
config.config.commands["sed"].commands["wipe-ssd"],
[],
input='n\n',
)
mock_sed_mgmt.wipe_ssd.assert_not_called()
# click.confirm(abort=True) exits non-zero on abort.
assert result.exit_code != 0

@patch('sonic_platform.platform.Platform')
def test_wipe_ssd_yes_flag_skips_prompt(self, mock_platform):
"""wipe-ssd with -y skips the confirmation prompt."""
runner = CliRunner()
mock_chassis = MagicMock()
mock_sed_mgmt = MagicMock()
mock_sed_mgmt.wipe_ssd.return_value = True
mock_chassis.get_sed_mgmt.return_value = mock_sed_mgmt
mock_platform.return_value.get_chassis.return_value = mock_chassis
result = runner.invoke(
config.config.commands["sed"].commands["wipe-ssd"],
['--yes'],
)
assert "SSD wipe completed successfully" in result.output
mock_sed_mgmt.wipe_ssd.assert_called_once()

@patch('sonic_platform.platform.Platform')
def test_wipe_ssd_failure(self, mock_platform):
"""wipe-ssd reports failure when SedMgmt.wipe_ssd returns False."""
runner = CliRunner()
mock_chassis = MagicMock()
mock_sed_mgmt = MagicMock()
mock_sed_mgmt.wipe_ssd.return_value = False
mock_chassis.get_sed_mgmt.return_value = mock_sed_mgmt
mock_platform.return_value.get_chassis.return_value = mock_chassis
result = runner.invoke(
config.config.commands["sed"].commands["wipe-ssd"],
['--yes'],
)
assert "Error: SSD wipe failed" in result.output

@patch('sonic_platform.platform.Platform')
def test_wipe_ssd_not_supported(self, mock_platform):
"""wipe-ssd exits cleanly when get_sed_mgmt() returns None."""
runner = CliRunner()
mock_chassis = MagicMock()
mock_chassis.get_sed_mgmt.return_value = None
mock_platform.return_value.get_chassis.return_value = mock_chassis
result = runner.invoke(
config.config.commands["sed"].commands["wipe-ssd"],
['--yes'],
)
assert "Error: SED management not supported on this platform" in result.output

@patch('sonic_platform.platform.Platform')
def test_wipe_ssd_exception(self, mock_platform):
"""wipe-ssd surfaces unexpected exceptions."""
runner = CliRunner()
mock_chassis = MagicMock()
mock_sed_mgmt = MagicMock()
mock_sed_mgmt.wipe_ssd.side_effect = Exception("boom")
mock_chassis.get_sed_mgmt.return_value = mock_sed_mgmt
mock_platform.return_value.get_chassis.return_value = mock_chassis
result = runner.invoke(
config.config.commands["sed"].commands["wipe-ssd"],
['--yes'],
)
assert "Error wiping SSD: boom" in result.output
Loading