Skip to content

Commit

Permalink
Merge pull request #89 from NETWAYS/chore/update-versions
Browse files Browse the repository at this point in the history
Update HPE ILO versions to be checked
  • Loading branch information
martialblog authored Mar 15, 2024
2 parents 9bce804 + 7ae9bbb commit e463e88
Show file tree
Hide file tree
Showing 9 changed files with 927 additions and 85 deletions.
2 changes: 2 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ issues:
linters:
- nosnakecase
- funlen
- gocognit
- path: 'hp/ilo/firmware.go'
linters:
- wastedassign
- exhaustive
- path: 'snmp/snmpwalk.go'
linters:
- funlen
Expand Down
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,12 @@ the drive is patched with `firmware update applied`.

## HPE Integrated Lights-Out

Multiple security vulnerabilities have been identified in Integrated Lights-Out 3 (iLO 3),
Integrated Lights-Out 4 (iLO 4), and Integrated Lights-Out 5 (iLO 5) firmware. The vulnerabilities could be remotely
exploited to execute code, cause denial of service, and expose sensitive information. HPE has released updated
firmware to mitigate these vulnerabilities.

The check will raise a CRITICAL when the Integrated Lights-Out needs to be updated. Below you will find a list with
The check will raise a WARNING when the Integrated Lights-Out needs to be updated. Below you will find a list with
the least version of each Integrated Lights-Out version:

- HPE Integrated Lights-Out 3 (iLO 3) firmware v1.93 or later.
- HPE Integrated Lights-Out 4 (iLO 4) firmware v2.75 or later
- HPE Integrated Lights-Out 5 (iLO 5) firmware v2.18 or later.
- HPE iLO 6 v1.56 or later
- HPE iLO 5 v3.01 or later
- HPE iLO 4 v2.82 or later

**IMPORTANT:** Always read the latest HPE Security Bulletins. https://support.hpe.com/connect/s/securitybulletinlibrary

Expand All @@ -61,7 +56,10 @@ Arguments:
-P, --protocol string SNMP protocol (default "2c")
--timeout int SNMP timeout in seconds (default 15)
--snmpwalk-file string Read output from snmpwalk
-e, --ilo-exit-state int Exit with specified code if iLO requires patch (default 1)
-I, --ignore-ilo-version Don't check the ILO version
-D, --ignore-drives Don't check the drive firmware
-C, --ignore-controller Don't check the controller firmware
-4, --ipv4 Use IPv4
-6, --ipv6 Use IPv6
-V, --version Show version
Expand Down
42 changes: 24 additions & 18 deletions hp/ilo/firmware.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,42 @@ type Ilo struct {
// GetIloInformation retrieves the iLO's Model and Rom Revision via SNMP
// and returns an Ilo struct.
func GetIloInformation(client gosnmp.Handler) (ilo *Ilo, err error) {
oidModel := []string{mib.CpqSm2CntlrModel + ".0"}
oidRev := []string{mib.CpqSm2CntlrRomRevision + ".0"}
oids := []string{
mib.CpqSm2CntlrModel + ".0",
mib.CpqSm2CntlrRomRevision + ".0",
}

ilo = &Ilo{}

iloModel, err := client.Get(oidModel)
if err != nil {
err = fmt.Errorf("could not get model for Ilo: %s", oidModel[0])
return
}

ilo.ModelID = iloModel.Variables[0].Value.(int)
if model, ok := mib.CpqSm2CntlrModelMap[ilo.ModelID]; ok {
ilo.Model = model
}
iloVariables, err := client.Get(oids)

iloRev, err := client.Get(oidRev)
if err != nil {
err = fmt.Errorf("could not get revision for Ilo: %s", oidRev[0])
err = fmt.Errorf("could not get SNMP data for iLO: %w", err)
return
}

ilo.RomRevision = iloRev.Variables[0].Value.(string)
// Since we only have two variable of different type we don't need to check their names
for _, v := range iloVariables.Variables {
switch v.Type {
case gosnmp.OctetString: // CpqSm2CntlrRomRevision
// Using Sprintf makes this work for (string) and ([]byte)
ilo.RomRevision = fmt.Sprintf("%s", v.Value)
case gosnmp.Integer: // CpqSm2CntlrModel
modelID := v.Value.(int)
ilo.ModelID = modelID

if model, ok := mib.CpqSm2CntlrModelMap[modelID]; ok {
ilo.Model = model
}
}
}

return
}

// GetNagiosStatus validates the iLO's data against the known models
// in this plugin.
func (ilo *Ilo) GetNagiosStatus() (state int, output string) {
func (ilo *Ilo) GetNagiosStatus(returnStateforPatch int) (state int, output string) {
// nolint: ineffassign
state = check.Unknown

Expand All @@ -72,8 +78,8 @@ func (ilo *Ilo) GetNagiosStatus() (state int, output string) {
output = fmt.Sprintf("Integrated Lights-Out %s revision %s ", modelInfo.Name, ilo.RomRevision)

if !isNewerVersion(modelInfo.FixedRelease, ilo.RomRevision) {
state = check.Critical
output += "- version too old, should be at least " + modelInfo.FixedRelease
state = returnStateforPatch
output += "- Patch available, should be at least " + modelInfo.FixedRelease
} else {
state = check.OK
output += "- version newer than affected"
Expand Down
8 changes: 6 additions & 2 deletions hp/ilo/firmware_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ const OlderModels = 8
// See mib.CpqSm2CntlrModelMap - not all models are mentioned here
//
// For vendor details see HPESBHF04012 https://support.hpe.com/hpesc/public/docDisplay?docId=hpesbhf04012en_us
// HPE iLO 6 v1.56 Release Notes https://support.hpe.com/hpesc/public/docDisplay?docLocale=en_US&docId=sd00003963en_us
// HPE iLO 5 v3.01 Release Notes https://support.hpe.com/hpesc/public/docDisplay?docLocale=en_US&docId=sd00003959en_us
// HPE iLO 4 v2.82 Release Notes https://support.hpe.com/hpesc/public/docDisplay?docId=c03334036en_us&page=index.html
var FixedVersionMap = map[string]PlatformInfo{
"pciIntegratedLightsOutRemoteInsight3": {"3", "1.93"},
"pciIntegratedLightsOutRemoteInsight4": {"4", "2.75"},
"pciIntegratedLightsOutRemoteInsight5": {"5", "2.18"},
"pciIntegratedLightsOutRemoteInsight4": {"4", "2.82"},
"pciIntegratedLightsOutRemoteInsight5": {"5", "3.01"},
"pciIntegratedLightsOutRemoteInsight6": {"6", "1.56"},
}
32 changes: 29 additions & 3 deletions hp/ilo/firmware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package ilo
import (
"testing"

"github.com/NETWAYS/check_hp_firmware/snmp"

"github.com/NETWAYS/go-check"
"github.com/stretchr/testify/assert"
)
Expand All @@ -19,8 +21,8 @@ func TestIlo_GetNagiosStatus(t *testing.T) {
ModelID: 9,
RomRevision: "1.40",
},
expectedState: check.Critical,
expectedOutput: "too old",
expectedState: check.Warning,
expectedOutput: "Patch available",
},
"newer": {
ilo: Ilo{
Expand Down Expand Up @@ -53,7 +55,7 @@ func TestIlo_GetNagiosStatus(t *testing.T) {

for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
state, output := tc.ilo.GetNagiosStatus()
state, output := tc.ilo.GetNagiosStatus(1)
assert.Equal(t, state, tc.expectedState)
assert.Contains(t, output, tc.expectedOutput)
})
Expand All @@ -76,3 +78,27 @@ func TestIsNewerVersion(t *testing.T) {
assert.False(t, isNewerVersion("foobar", "1.0"))
assert.False(t, isNewerVersion("xxx", "xxx"))
}

func TestGetIloInformation_ilo5(t *testing.T) {
c, _ := snmp.NewFileHandlerFromFile("../../testdata/ilo5.txt")

i, err := GetIloInformation(c)

assert.NoError(t, err)

assert.Equal(t, 11, i.ModelID)
assert.Equal(t, "pciIntegratedLightsOutRemoteInsight5", i.Model)
assert.Equal(t, "3.00", i.RomRevision)
}

func TestGetIloInformation_ilo6(t *testing.T) {
c, _ := snmp.NewFileHandlerFromFile("../../testdata/ilo6.txt")

i, err := GetIloInformation(c)

assert.NoError(t, err)

assert.Equal(t, 12, i.ModelID)
assert.Equal(t, "pciIntegratedLightsOutRemoteInsight6", i.Model)
assert.Equal(t, "1.55", i.RomRevision)
}
1 change: 1 addition & 0 deletions hp/mib/cpq_sm_cntrl.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ var CpqSm2CntlrModelMap = StringMap{
9: "pciIntegratedLightsOutRemoteInsight3", // Integrated Lights-Out 3 Edition
10: "pciIntegratedLightsOutRemoteInsight4", // Integrated Lights-Out 4 Edition
11: "pciIntegratedLightsOutRemoteInsight5", // Integrated Lights-Out 5 Edition
12: "pciIntegratedLightsOutRemoteInsight6", // Integrated Lights-Out 6 Edition
}
116 changes: 63 additions & 53 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,21 @@ func main() {
config.Timeout = 15

// Variables for CLI flags
// Personally, I would have preferred to add "enable" flags that enable further subchecks.
// However, this would have broken the current behaviour completely. Thus I opted for "ignore" flags
var (
fs = config.FlagSet
host = fs.StringP("hostname", "H", "localhost", "SNMP host")
community = fs.StringP("community", "c", "public", "SNMP community")
protocol = fs.StringP("protocol", "P", "2c", "SNMP protocol")
port = fs.Uint16P("port", "p", 161, "SNMP port")
file = fs.String("snmpwalk-file", "", "Read output from snmpwalk")
ignoreIlo = fs.BoolP("ignore-ilo-version", "I", false, "Don't check the ILO version")
ipv4 = fs.BoolP("ipv4", "4", false, "Use IPv4")
ipv6 = fs.BoolP("ipv6", "6", false, "Use IPv6")
fs = config.FlagSet
host = fs.StringP("hostname", "H", "localhost", "SNMP host")
community = fs.StringP("community", "c", "public", "SNMP community")
protocol = fs.StringP("protocol", "P", "2c", "SNMP protocol")
port = fs.Uint16P("port", "p", 161, "SNMP port")
file = fs.String("snmpwalk-file", "", "Read output from snmpwalk")
iloExitState = fs.IntP("ilo-exit-state", "e", 1, "Exit with specified code if iLO requires patch")
ignoreIlo = fs.BoolP("ignore-ilo-version", "I", false, "Don't check the ILO version")
ignoreDrives = fs.BoolP("ignore-drives", "D", false, "Don't check the drive firmware")
ignoreController = fs.BoolP("ignore-controller", "C", false, "Don't check the controller firmware")
ipv4 = fs.BoolP("ipv4", "4", false, "Use IPv4")
ipv6 = fs.BoolP("ipv6", "6", false, "Use IPv6")
)

config.ParseArguments()
Expand Down Expand Up @@ -108,66 +113,71 @@ func main() {
_ = client.Close()
}()

// Load controller data
cntlrTable, err = cntlr.GetCpqDaCntlrTable(client)
if err != nil {
check.ExitError(err)
}
// Overall is a singleton that has several partial results
overall := result.Overall{}

// Load drive data
driveTable, err = drive.GetCpqDaPhyDrvTable(client)
if err != nil {
check.ExitError(err)
// Load iLO Version data
if !*ignoreIlo {
iloData, err := ilo.GetIloInformation(client)
if err != nil {
check.ExitError(err)
}
// Retrieve the status from the iLO and add the result
overall.Add(iloData.GetNagiosStatus(*iloExitState))
}

if len(cntlrTable.Snmp.Values) == 0 {
check.ExitRaw(3, "No HP controller data found!")
}
// Load controller data
if !*ignoreController {
cntlrTable, err = cntlr.GetCpqDaCntlrTable(client)
if err != nil {
check.ExitError(err)
}

// Extract controller data from SNMP Table
controllers, err := cntlr.GetControllersFromTable(cntlrTable)
if len(cntlrTable.Snmp.Values) == 0 {
check.ExitRaw(3, "No HP controller data found!")
}

if err != nil {
check.ExitError(err)
}
// Extract controller data from SNMP Table
controllers, err := cntlr.GetControllersFromTable(cntlrTable)

if len(driveTable.Snmp.Values) == 0 {
check.ExitRaw(3, "No HP drive data found!")
}
if err != nil {
check.ExitError(err)
}

// Extract drive data from SNMP Table
drives, err := drive.GetPhysicalDrivesFromTable(driveTable)
if err != nil {
check.ExitError(err)
// Retrieve the status from each controller and add the result
for _, controller := range controllers {
controllerStatus, desc := controller.GetNagiosStatus()
overall.Add(controllerStatus, desc)
countControllers++
}
}

// Overall is a singleton that has several partial results
overall := result.Overall{}

// Load iLO Version if flag is set
if !*ignoreIlo {
iloData, err := ilo.GetIloInformation(client)
// Load drive data
if !*ignoreDrives {
driveTable, err = drive.GetCpqDaPhyDrvTable(client)
if err != nil {
check.ExitError(err)
}
// Retrieve the status from the iLO and add the result
overall.Add(iloData.GetNagiosStatus())
}

// Retrieve the status from each controller and add the result
for _, controller := range controllers {
controllerStatus, desc := controller.GetNagiosStatus()
overall.Add(controllerStatus, desc)
countControllers++
}
if len(driveTable.Snmp.Values) == 0 {
check.ExitRaw(3, "No HP drive data found!")
}

// Retrieve the status from each drive and add the result
for _, drive := range drives {
driveStatus, desc := drive.GetNagiosStatus()
overall.Add(driveStatus, desc)
countDrives++
// Extract drive data from SNMP Table
drives, err := drive.GetPhysicalDrivesFromTable(driveTable)
if err != nil {
check.ExitError(err)
}

// Retrieve the status from each drive and add the result
for _, drive := range drives {
driveStatus, desc := drive.GetNagiosStatus()
overall.Add(driveStatus, desc)
countDrives++
}
}

// Get the overall status for all subchecks
status := overall.GetStatus()

switch status {
Expand Down
55 changes: 55 additions & 0 deletions testdata/ilo5.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.1.3.6.1.4.1.232.3.1.1.0 = INTEGER: 1
.1.3.6.1.4.1.232.3.1.2.0 = INTEGER: 90
.1.3.6.1.4.1.232.3.1.3.0 = INTEGER: 1
.1.3.6.1.4.1.232.3.2.2.2.1.1.0 = INTEGER: 0
.1.3.6.1.4.1.232.3.2.2.2.1.2.0 = INTEGER: 1
.1.3.6.1.4.1.232.3.2.2.2.1.3.0 = INTEGER: 0
.1.3.6.1.4.1.232.3.2.2.2.1.4.0 = INTEGER: 2
.1.3.6.1.4.1.232.3.2.2.2.1.5.0 = INTEGER: 2
.1.3.6.1.4.1.232.3.2.2.2.1.6.0 = INTEGER: 2
.1.3.6.1.4.1.232.3.2.2.2.1.7.0 = Counter32: 0
.1.3.6.1.4.1.232.3.2.2.2.1.8.0 = Counter32: 0
.1.3.6.1.4.1.232.3.2.2.2.1.9.0 = INTEGER: 1
.1.3.6.1.4.1.232.3.2.2.2.1.10.0 = INTEGER: 0
.1.3.6.1.4.1.232.3.2.2.2.1.11.0 = STRING: " "
.1.3.6.1.4.1.232.3.2.2.2.1.12.0 = INTEGER: 1048576
.1.3.6.1.4.1.232.3.2.2.2.1.13.0 = Gauge32: 0
.1.3.6.1.4.1.232.3.2.2.2.1.14.0 = Gauge32: 0
.1.3.6.1.4.1.232.3.2.2.2.1.15.0 = ""
.1.3.6.1.4.1.232.3.2.2.2.1.16.0 = INTEGER: 4
.1.3.6.1.4.1.232.3.2.2.2.1.17.0 = INTEGER: -1
.1.3.6.1.4.1.232.3.2.2.2.1.18.0 = INTEGER: -1
.1.3.6.1.4.1.232.3.2.2.2.1.19.0 = INTEGER: -1
.1.3.6.1.4.1.232.3.2.2.2.1.20.0 = INTEGER: -1
.1.3.6.1.4.1.232.9.2.2.1.0 = STRING: "12/14/2023"
.1.3.6.1.4.1.232.9.2.2.2.0 = STRING: "3.00"
.1.3.6.1.4.1.232.9.2.2.3.0 = INTEGER: 1
.1.3.6.1.4.1.232.9.2.2.4.0 = INTEGER: 4
.1.3.6.1.4.1.232.9.2.2.5.0 = INTEGER: 1
.1.3.6.1.4.1.232.9.2.2.6.0 = INTEGER: 0
.1.3.6.1.4.1.232.9.2.2.7.0 = INTEGER: 2
.1.3.6.1.4.1.232.9.2.2.8.0 = INTEGER: 2
.1.3.6.1.4.1.232.9.2.2.9.0 = INTEGER: 0
.1.3.6.1.4.1.232.9.2.2.10.0 = INTEGER: 2
.1.3.6.1.4.1.232.9.2.2.11.0 = Hex-STRING: 00 00 00 00 00 00 00
.1.3.6.1.4.1.232.9.2.2.12.0 = INTEGER: 3
.1.3.6.1.4.1.232.9.2.2.13.0 = INTEGER: 1
.1.3.6.1.4.1.232.9.2.2.14.0 = INTEGER: 3
.1.3.6.1.4.1.232.9.2.2.15.0 = STRING: "iLOCZ00000000"
.1.3.6.1.4.1.232.9.2.2.16.0 = INTEGER: 3
.1.3.6.1.4.1.232.9.2.2.17.0 = INTEGER: 2
.1.3.6.1.4.1.232.9.2.2.18.0 = Hex-STRING: 00 00 00 00 00 00 00 00 00 00
.1.3.6.1.4.1.232.9.2.2.19.0 = INTEGER: 1
.1.3.6.1.4.1.232.9.2.2.20.0 = IpAddress: 0.0.0.0
.1.3.6.1.4.1.232.9.2.2.21.0 = INTEGER: 11
.1.3.6.1.4.1.232.9.2.2.22.0 = INTEGER: 1076928768
.1.3.6.1.4.1.232.9.2.2.23.0 = INTEGER: 1
.1.3.6.1.4.1.232.9.2.2.24.0 = INTEGER: 4
.1.3.6.1.4.1.232.9.2.2.25.0 = INTEGER: 4
.1.3.6.1.4.1.232.9.2.2.26.0 = STRING: ""
.1.3.6.1.4.1.232.9.2.2.27.0 = INTEGER: 3
.1.3.6.1.4.1.232.9.2.2.28.0 = INTEGER: 6
.1.3.6.1.4.1.232.9.2.2.29.0 = INTEGER: 19
.1.3.6.1.4.1.232.9.2.2.30.0 = INTEGER: 2
.1.3.6.1.4.1.232.9.2.2.31.0 = STRING: "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
.1.3.6.1.4.1.232.9.2.2.32.0 = INTEGER: 3
Loading

0 comments on commit e463e88

Please sign in to comment.