diff --git a/internal/action/action.go b/internal/action/action.go index 246d973..da0b551 100644 --- a/internal/action/action.go +++ b/internal/action/action.go @@ -23,16 +23,19 @@ const ( // immidiately on the file-system. Therefore a delay is not required // for actions that peform file changes DefaultFileActionDelay = 0 + DisabledWarning = "" + FormatActionWarning = "Formatting larger disks can take several seconds ⌛ Please wait patiently 🙇" ) type Action interface { Execute(rc *utils.RunnerCache) error - GetdeviceName() string + GetDeviceName() string + IsTrusted() bool + GetDelay() time.Duration Success() string Prompt() string Refuse() string - IsTrusted() bool - GetDelay() time.Duration + Warning() string } type ActionExecutor struct { @@ -48,7 +51,7 @@ func NewActionExecutor(rc *utils.RunnerCache, c *config.Config) *ActionExecutor } func (ae *ActionExecutor) ExecuteAction(action Action) error { - name := action.GetdeviceName() + name := action.GetDeviceName() mode, err := ae.config.GetMode(name) if err != nil { return err @@ -60,25 +63,27 @@ func (ae *ActionExecutor) ExecuteAction(action Action) error { return fmt.Errorf("🔴 Action rejected. %s", action.Refuse()) } case config.Healtcheck: - // Special handling for trusted actions when device is under - // healthcheck mode + // Special handling for trusted actions if action.IsTrusted() { if !ae.config.GetAllowTrustedActions() { log.Printf("🙅 Skipped trusted action. %s", action.Refuse()) return nil } - break + break; } - return fmt.Errorf("🔴%s: Healthcheck mode enabled. %s", name, action.Refuse()) + return fmt.Errorf("🔴 %s: Healthcheck mode enabled. %s", name, action.Refuse()) } return ae.executeAction(action) } func (ae *ActionExecutor) executeAction(action Action) error { + if w := action.Warning(); w != DisabledWarning { + log.Printf("🟠 %s: %s", action.GetDeviceName(), w) + } if err := action.Execute(ae.runnerCache); err != nil { return err } - log.Printf("⭐ %s: %s", action.GetdeviceName(), action.Success()) + log.Printf("⭐ %s: %s", action.GetDeviceName(), action.Success()) // Add a delay because from experience, the operating system needs some time // to catch up to device changes performed through C tools like e2label and mkfs.ext4 time.Sleep(action.GetDelay()) diff --git a/internal/action/file.go b/internal/action/file.go index ecfc137..0eb302a 100644 --- a/internal/action/file.go +++ b/internal/action/file.go @@ -29,12 +29,20 @@ func (a *CreateDirectoryAction) Execute(rc *utils.RunnerCache) error { return os.MkdirAll(a.path, DefaultDirectoryPermissions) } -func (a *CreateDirectoryAction) Prompt() string { - return fmt.Sprintf("Would you like to recursively create directory %s", a.path) +func (a *CreateDirectoryAction) GetDeviceName() string { + return a.deviceName } -func (a *CreateDirectoryAction) GetdeviceName() string { - return a.deviceName +func (a *CreateDirectoryAction) IsTrusted() bool { + return false +} + +func (a *CreateDirectoryAction) GetDelay() time.Duration { + return DefaultFileActionDelay +} + +func (a *CreateDirectoryAction) Prompt() string { + return fmt.Sprintf("Would you like to recursively create directory %s", a.path) } func (a *CreateDirectoryAction) Refuse() string { @@ -45,12 +53,8 @@ func (a *CreateDirectoryAction) Success() string { return fmt.Sprintf("Successfully created directory %s", a.path) } -func (a *CreateDirectoryAction) IsTrusted() bool { - return false -} - -func (a *CreateDirectoryAction) GetDelay() time.Duration { - return DefaultFileActionDelay +func (a *CreateDirectoryAction) Warning() string { + return DisabledWarning } type ChangeOwnerAction struct { @@ -73,13 +77,22 @@ func (a *ChangeOwnerAction) Execute(rc *utils.RunnerCache) error { return os.Chown(a.path, a.uid, a.gid) } +func (a *ChangeOwnerAction) GetDeviceName() string { + return a.deviceName +} + +func (a *ChangeOwnerAction) IsTrusted() bool { + return false +} + +func (a *ChangeOwnerAction) GetDelay() time.Duration { + return DefaultFileActionDelay +} + func (a *ChangeOwnerAction) Prompt() string { return fmt.Sprintf("Would you like to change ownership (%d:%d) of %s", a.uid, a.gid, a.path) } -func (a *ChangeOwnerAction) GetdeviceName() string { - return a.deviceName -} func (a *ChangeOwnerAction) Refuse() string { return fmt.Sprintf("Refused to to change ownership (%d:%d) of %s", a.uid, a.gid, a.path) @@ -89,12 +102,8 @@ func (a *ChangeOwnerAction) Success() string { return fmt.Sprintf("Successfully changed ownership (%d:%d) of %s", a.uid, a.gid, a.path) } -func (a *ChangeOwnerAction) IsTrusted() bool { - return false -} - -func (a *ChangeOwnerAction) GetDelay() time.Duration { - return DefaultFileActionDelay +func (a *ChangeOwnerAction) Warning() string { + return DisabledWarning } type ChangePermissionsAction struct { @@ -119,13 +128,22 @@ func (a *ChangePermissionsAction) Execute(rc *utils.RunnerCache) error { return os.Chmod(a.path, mode) } +func (a *ChangePermissionsAction) GetDeviceName() string { + return a.deviceName +} + +func (a *ChangePermissionsAction) IsTrusted() bool { + return false +} + +func (a *ChangePermissionsAction) GetDelay() time.Duration { + return DefaultFileActionDelay +} + func (a *ChangePermissionsAction) Prompt() string { return fmt.Sprintf("Would you like to change permissions of %s to %s", a.path, a.perms) } -func (a *ChangePermissionsAction) GetdeviceName() string { - return a.deviceName -} func (a *ChangePermissionsAction) Refuse() string { return fmt.Sprintf("Refused to to change permissions of %s to %s", a.path, a.perms) @@ -135,10 +153,8 @@ func (a *ChangePermissionsAction) Success() string { return fmt.Sprintf("Successfully change permissions of %s to %s", a.path, a.perms) } -func (a *ChangePermissionsAction) IsTrusted() bool { - return false +func (a *ChangePermissionsAction) Warning() string { + return DisabledWarning } -func (a *ChangePermissionsAction) GetDelay() time.Duration { - return DefaultFileActionDelay -} + diff --git a/internal/action/format.go b/internal/action/format.go index 9205a5e..ad886e0 100644 --- a/internal/action/format.go +++ b/internal/action/format.go @@ -26,12 +26,20 @@ func (a *FormatExt4Action) Execute(rc *utils.RunnerCache) error { return nil } -func (a *FormatExt4Action) Prompt() string { - return fmt.Sprintf("Would you like to format %s to ext4", a.deviceName) +func (a *FormatExt4Action) GetDeviceName() string { + return a.deviceName } -func (a *FormatExt4Action) GetdeviceName() string { - return a.deviceName +func (a *FormatExt4Action) IsTrusted() bool { + return false +} + +func (a *FormatExt4Action) GetDelay() time.Duration { + return DefaultDeviceActionDelay +} + +func (a *FormatExt4Action) Prompt() string { + return fmt.Sprintf("Would you like to format %s to ext4", a.deviceName) } func (a *FormatExt4Action) Refuse() string { @@ -42,13 +50,10 @@ func (a *FormatExt4Action) Success() string { return "Successfully formated to ext4" } -func (a *FormatExt4Action) IsTrusted() bool { - return false +func (a *FormatExt4Action) Warning() string { + return FormatActionWarning } -func (a *FormatExt4Action) GetDelay() time.Duration { - return DefaultDeviceActionDelay -} type FormatXfsAction struct { deviceName string @@ -61,6 +66,7 @@ func NewFormatXfsAction(dn string) *FormatXfsAction { } func (a *FormatXfsAction) Execute(rc *utils.RunnerCache) error { + fmt.Println("⌛ Note: Formatting larger disks can take several seconds. Please wait patiently 🙇") r := rc.GetRunner(utils.MkfsXfs) _, err := r.Command(a.deviceName) if err != nil { @@ -69,12 +75,20 @@ func (a *FormatXfsAction) Execute(rc *utils.RunnerCache) error { return nil } +func (a *FormatXfsAction) GetDeviceName() string { + return a.deviceName +} + func (a *FormatXfsAction) Prompt() string { return fmt.Sprintf("Would you like to format %s to XFS?", a.deviceName) } -func (a *FormatXfsAction) GetdeviceName() string { - return a.deviceName +func (a *FormatXfsAction) IsTrusted() bool { + return false +} + +func (a *FormatXfsAction) GetDelay() time.Duration { + return DefaultDeviceActionDelay } func (a *FormatXfsAction) Refuse() string { @@ -85,10 +99,6 @@ func (a *FormatXfsAction) Success() string { return "Successfully formated to XFS" } -func (a *FormatXfsAction) IsTrusted() bool { - return false -} - -func (a *FormatXfsAction) GetDelay() time.Duration { - return DefaultDeviceActionDelay +func (a *FormatXfsAction) Warning() string { + return FormatActionWarning } diff --git a/internal/action/label.go b/internal/action/label.go index 5af7182..495be06 100644 --- a/internal/action/label.go +++ b/internal/action/label.go @@ -31,13 +31,22 @@ func (a *LabelExt4Action) Execute(rc *utils.RunnerCache) error { return nil } +func (a *LabelExt4Action) GetDeviceName() string { + return a.deviceName +} + +func (a *LabelExt4Action) IsTrusted() bool { + return false +} + +func (a *LabelExt4Action) GetDelay() time.Duration { + return DefaultDeviceActionDelay +} + func (a *LabelExt4Action) Prompt() string { return fmt.Sprintf("Would you like to label device %s to %s", a.deviceName, a.label) } -func (a *LabelExt4Action) GetdeviceName() string { - return a.deviceName -} func (a *LabelExt4Action) Refuse() string { return fmt.Sprintf("Refused to label to %s", a.label) @@ -47,12 +56,8 @@ func (a *LabelExt4Action) Success() string { return fmt.Sprintf("Successfully labelled to %s", a.label) } -func (a *LabelExt4Action) IsTrusted() bool { - return false -} - -func (a *LabelExt4Action) GetDelay() time.Duration { - return DefaultDeviceActionDelay +func (a *LabelExt4Action) Warning() string { + return DisabledWarning } type LabelXfsAction struct { @@ -79,12 +84,20 @@ func (a *LabelXfsAction) Execute(rc *utils.RunnerCache) error { return nil } -func (a *LabelXfsAction) Prompt() string { - return fmt.Sprintf("Would you like to label device %s to %s", a.deviceName, a.label) +func (a *LabelXfsAction) GetDeviceName() string { + return a.deviceName } -func (a *LabelXfsAction) GetdeviceName() string { - return a.deviceName +func (a *LabelXfsAction) IsTrusted() bool { + return false +} + +func (a *LabelXfsAction) GetDelay() time.Duration { + return DefaultDeviceActionDelay +} + +func (a *LabelXfsAction) Prompt() string { + return fmt.Sprintf("Would you like to label device %s to %s", a.deviceName, a.label) } func (a *LabelXfsAction) Refuse() string { @@ -95,10 +108,6 @@ func (a *LabelXfsAction) Success() string { return fmt.Sprintf("Successfully labelled to %s", a.label) } -func (a *LabelXfsAction) IsTrusted() bool { - return false -} - -func (a *LabelXfsAction) GetDelay() time.Duration { - return DefaultDeviceActionDelay +func (a *LabelXfsAction) Warning() string { + return DisabledWarning } diff --git a/internal/action/mount.go b/internal/action/mount.go index 8b6f51e..c72c950 100644 --- a/internal/action/mount.go +++ b/internal/action/mount.go @@ -33,12 +33,20 @@ func (a *MountDeviceAction) Execute(rc *utils.RunnerCache) error { return nil } -func (a *MountDeviceAction) Prompt() string { - return fmt.Sprintf("Would you like to mount %s to %s (%s)", a.source, a.target, a.options) +func (a *MountDeviceAction) GetDeviceName() string { + return a.source } -func (a *MountDeviceAction) GetdeviceName() string { - return a.source +func (a *MountDeviceAction) IsTrusted() bool { + return false +} + +func (a *MountDeviceAction) GetDelay() time.Duration { + return DefaultDeviceActionDelay +} + +func (a *MountDeviceAction) Prompt() string { + return fmt.Sprintf("Would you like to mount %s to %s (%s)", a.source, a.target, a.options) } func (a *MountDeviceAction) Refuse() string { @@ -49,12 +57,8 @@ func (a *MountDeviceAction) Success() string { return fmt.Sprintf("Successfully mounted %s to %s (%s)", a.source, a.target, a.options) } -func (a *MountDeviceAction) IsTrusted() bool { - return false -} - -func (a *MountDeviceAction) GetDelay() time.Duration { - return DefaultDeviceActionDelay +func (a *MountDeviceAction) Warning() string { + return DisabledWarning } type RemountDeviceAction struct { @@ -82,13 +86,22 @@ func (a *RemountDeviceAction) Execute(rc *utils.RunnerCache) error { return nil } +func (a *RemountDeviceAction) GetDeviceName() string { + return a.source +} + +func (a *RemountDeviceAction) IsTrusted() bool { + return true +} + +func (a *RemountDeviceAction) GetDelay() time.Duration { + return DefaultDeviceActionDelay +} + func (a *RemountDeviceAction) Prompt() string { return fmt.Sprintf("Would you like to remount %s to %s (%s)", a.source, a.target, a.options) } -func (a *RemountDeviceAction) GetdeviceName() string { - return a.source -} func (a *RemountDeviceAction) Refuse() string { return fmt.Sprintf("Refused to remount %s to %s (%s)", a.source, a.target, a.options) @@ -98,13 +111,10 @@ func (a *RemountDeviceAction) Success() string { return fmt.Sprintf("Successfully remounted %s to %s (%s)", a.source, a.target, a.options) } -func (a *RemountDeviceAction) IsTrusted() bool { - return true +func (a *RemountDeviceAction) Warning() string { + return DisabledWarning } -func (a *RemountDeviceAction) GetDelay() time.Duration { - return DefaultDeviceActionDelay -} type UnmountDeviceAction struct { source string @@ -127,12 +137,20 @@ func (a *UnmountDeviceAction) Execute(rc *utils.RunnerCache) error { return nil } -func (a *UnmountDeviceAction) Prompt() string { - return fmt.Sprintf("Would you like to unmount %s from %s", a.source, a.target) +func (a *UnmountDeviceAction) GetDeviceName() string { + return a.source } -func (a *UnmountDeviceAction) GetdeviceName() string { - return a.source +func (a *UnmountDeviceAction) IsTrusted() bool { + return false +} + +func (a *UnmountDeviceAction) GetDelay() time.Duration { + return DefaultDeviceActionDelay +} + +func (a *UnmountDeviceAction) Prompt() string { + return fmt.Sprintf("Would you like to unmount %s from %s", a.source, a.target) } func (a *UnmountDeviceAction) Refuse() string { @@ -143,10 +161,7 @@ func (a *UnmountDeviceAction) Success() string { return fmt.Sprintf("Successfully unmounted %s from %s", a.source, a.target) } -func (a *UnmountDeviceAction) IsTrusted() bool { - return false +func (a *UnmountDeviceAction) Warning() string { + return DisabledWarning } -func (a *UnmountDeviceAction) GetDelay() time.Duration { - return DefaultDeviceActionDelay -} diff --git a/internal/config/modifier.go b/internal/config/modifier.go index f9ebfb0..32f07b5 100644 --- a/internal/config/modifier.go +++ b/internal/config/modifier.go @@ -69,6 +69,12 @@ func (andm *AwsNitroNVMeModifier) Modify(c *Config) error { return err } for _, name := range bds { + // Check if device already exists in the config + // No need to make additional queries if this is the case + cd, exists := c.Devices[name] + if exists { + continue + } if !strings.HasPrefix(name, "/dev/nvme") { continue } @@ -76,11 +82,11 @@ func (andm *AwsNitroNVMeModifier) Modify(c *Config) error { if err != nil { continue } - cd, exists := c.Devices[bdm] + cd, exists = c.Devices[bdm] if !exists { continue } - log.Printf("🔵 Detected that %s is a nitro-based AWS NVMe device, originally mapped to %s", name, bdm) + log.Printf("🔵 %s: Detected Nitro-based AWS NVMe device => %s", name, bdm) c.Devices[name] = cd delete(c.Devices, bdm) } diff --git a/internal/service/nvme.go b/internal/service/nvme.go index e125aa1..9684e3c 100644 --- a/internal/service/nvme.go +++ b/internal/service/nvme.go @@ -14,6 +14,7 @@ const ( NVME_IOCTL_ADMIN_CMD = 0xC0484E41 AMZN_NVME_VID = 0x1D0F AMZN_NVME_EBS_MN = "Amazon Elastic Block Store" + AMZN_NVME_INS_MN = "Amazon EC2 NVMe Instance Storage" ) type nvmeAdminCommand struct { @@ -154,10 +155,29 @@ func (ns *AwsNitroNVMeService) isEBSVolume(nd *NVMeDevice) bool { return vid == AMZN_NVME_VID && mn == AMZN_NVME_EBS_MN } +func (ns *AwsNitroNVMeService) isInstanceStoreVolume(nd *NVMeDevice) bool { + vid := nd.IdCtrl.Vid + mn := strings.TrimRightFunc(string(nd.IdCtrl.Mn[:]), unicode.IsSpace) + return vid == AMZN_NVME_VID && mn == AMZN_NVME_INS_MN +} + func (ns *AwsNitroNVMeService) getBlockDeviceMapping(nd *NVMeDevice) (string, error) { var bdm string if ns.isEBSVolume(nd) { - bdm = strings.TrimRightFunc(string(nd.IdCtrl.Vs.Bdev[:]), unicode.IsSpace) + bdm = strings.TrimRightFunc(string(nd.IdCtrl.Vs.Bdev[:]), ns.TrimVendorSpecfic) + } + if ns.isInstanceStoreVolume(nd) { + // Vendor Specfic (vs) + vs := strings.TrimRightFunc(string(nd.IdCtrl.Vs.Bdev[:]), ns.TrimVendorSpecfic) + // Vendor Specific Fragments (vsf) + vsf := strings.Split(vs, ":") + if len(vsf) != 2 { + return "", fmt.Errorf("🔴 %s: Received unexpected Instance Store NVMe metadata", nd.Name) + } + if vsf[1] == "none" { + return "", fmt.Errorf("🔴 %s: Must provide a device name to the Instance Store NVMe block device mapping", nd.Name) + } + bdm = vsf[1] } if bdm == "" { return "", fmt.Errorf("🔴 %s is not an AWS-managed NVME device", nd.Name) @@ -167,3 +187,21 @@ func (ns *AwsNitroNVMeService) getBlockDeviceMapping(nd *NVMeDevice) (string, er } return bdm, nil } + +func (ns *AwsNitroNVMeService) TrimVendorSpecfic(r rune) bool { +// Explanation: +// - The AWS EC2 team uses the 0x00 (null) byte to pad out the Vendor Specific allocation +// - The AWS EBS team uses the 0x20 (space) byte to pad out the Vendor Specific allocation +// - It is frustrating that the padding character is not standardised, but we can +// work around this by checking for both bytes when trimming the Vendor Specific allocation +// Examples: +// nd.IdCtrl.Vs.Bdev[:] ("Amazon EC2 NVMe Instance Storage") +// 0 1 2 3 4 5 6 7 8 9 a b c d e f +// 0000: 65 70 68 65 6d 65 72 61 6c 30 3a 73 64 68 00 00 "ephemeral0:sdh.." +// 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "................" +// nd.IdCtrl.Vs.Bdev[:] (Amazon Elastic Block Store) +// 0 1 2 3 4 5 6 7 8 9 a b c d e f +// 0000: 2f 64 65 76 2f 73 64 63 20 20 20 20 20 20 20 20 "/dev/sdc........" +// 0010: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 "................" + return r == 0x00 || r == 0x20 +}