diff --git a/.github/.golangci.yml b/.github/.golangci.yml index d61dc529..390d8e19 100644 --- a/.github/.golangci.yml +++ b/.github/.golangci.yml @@ -60,7 +60,7 @@ linters: - dogsled # - dupl - durationcheck - - errcheck + # - errcheck - errorlint - exhaustive - exportloopref @@ -98,7 +98,7 @@ linters: # - revive - rowserrcheck - sqlclosecheck - - staticcheck + # - staticcheck # - structcheck # - stylecheck - tparallel diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3a50522..dcb6f365 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,13 @@ jobs: name: runner / formatting runs-on: ubuntu-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + with: + egress-policy: audit + - name: Check out code into the Go module directory - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - name: Format run: if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then exit 1; fi - name: Run go vet @@ -22,10 +27,15 @@ jobs: name: runner / golangci-lint runs-on: ubuntu-latest steps: + - name: Harden Runner + uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + with: + egress-policy: audit + - name: Check out code into the Go module directory - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - name: golangci-lint - uses: reviewdog/action-golangci-lint@v2 + uses: reviewdog/action-golangci-lint@00311c26a97213f93f2fd3a3524d66762e956ae0 # v2.6.1 with: fail_on_error: true golangci_lint_flags: "--config=.github/.golangci.yml ./..." @@ -34,8 +44,13 @@ jobs: name: runner / yamllint runs-on: ubuntu-latest steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - - uses: reviewdog/action-yamllint@v1 + - name: Harden Runner + uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + with: + egress-policy: audit + + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + - uses: reviewdog/action-yamllint@8d79c3d034667db2792e328936811ed44953d691 # v1.14.0 with: fail_on_error: true reporter: github-pr-review @@ -45,8 +60,13 @@ jobs: name: runner / dotenv-linter runs-on: ubuntu-latest steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b - - uses: dotenv-linter/action-dotenv-linter@v2 + - name: Harden Runner + uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + with: + egress-policy: audit + + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + - uses: dotenv-linter/action-dotenv-linter@d92c8e455691d7a4d4e1d830081b0a39e4c34b88 # v2.21.0 with: reporter: github-pr-review tests: @@ -57,12 +77,17 @@ jobs: go-version: [1.20.x, 1.21.x, 1.22.x] os: [windows-2019, windows-2022, ubuntu-22.04, ubuntu-20.04] steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b + - name: Harden Runner + uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + with: + egress-policy: audit + + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 with: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Install Test Converter and run tests run: | export GOPATH="$HOME/go/" @@ -73,7 +98,7 @@ jobs: cat test_output.txt | go-junit-report -set-exit-code > junit-${{matrix.os}}-${{matrix.go-version}}-${{github.run_attempt}}.xml if grep -q "FAIL" test_output.txt; then exit 1; fi - name: Upload Coverage Results - uses: codecov/codecov-action@6d798873df2b1b8e5846dba6fb86631229fbcb17 # v4.4.0 + uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1 with: token: ${{ secrets.CODECOV_TOKEN }} - name: GitHub Upload Release Artifacts diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7629fd06..b760a154 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -46,16 +46,16 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 + uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 with: egress-policy: audit - name: Checkout repository - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5 + uses: github/codeql-action/init@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5 + uses: github/codeql-action/autobuild@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,7 +80,7 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5 + uses: github/codeql-action/analyze@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7 - name: Generate Security Report uses: rsdmike/github-security-report-action@a149b24539044c92786ec39af8ba38c93496495d # v3.0.4 continue-on-error: true diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index eea3e827..7141d093 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 + uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 with: egress-policy: audit - name: 'Checkout Repository' - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: 'Dependency Review' uses: actions/dependency-review-action@0c155c5e8556a497adf53f2c18edabf945ed8e70 # v4.3.2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 039b92ce..f5c9397d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,9 @@ on: branches: [ main ] pull_request: branches: [ main ] +permissions: + contents: read + jobs: release: runs-on: ubuntu-latest @@ -22,12 +25,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 + uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 with: egress-policy: audit - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: persist-credentials: false diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index b76202a9..148708e8 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,12 +32,12 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 + uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 with: egress-policy: audit - name: "Checkout code" - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: persist-credentials: false @@ -72,6 +72,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@b7cec7526559c32f1616476ff32d17ba4c59b2d6 # v3.25.5 + uses: github/codeql-action/upload-sarif@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7 with: sarif_file: results.sarif diff --git a/.github/workflows/semantic.yml b/.github/workflows/semantic.yml index 38f9a277..b253c25f 100644 --- a/.github/workflows/semantic.yml +++ b/.github/workflows/semantic.yml @@ -9,11 +9,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 + uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 with: egress-policy: audit - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 0 - uses: wagoid/commitlint-github-action@7f0a61df502599e1f1f50880aaa7ec1e2c0592f2 # v6.0.1 diff --git a/.golangci.yml b/.golangci.yml index d61dc529..390d8e19 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -60,7 +60,7 @@ linters: - dogsled # - dupl - durationcheck - - errcheck + # - errcheck - errorlint - exhaustive - exportloopref @@ -98,7 +98,7 @@ linters: # - revive - rowserrcheck - sqlclosecheck - - staticcheck + # - staticcheck # - structcheck # - stylecheck - tparallel diff --git a/README.md b/README.md index 5a1cd217..72780e48 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,9 @@ if err != nil { # Dev tips for passing CI Checks -- Ensure code is formatted correctly with `gofmt -s -w ./` +- Install gofumpt `go install mvdan.cc/gofumpt@latest` (replaces gofmt) +- Install gci `go install github.com/daixiang0/gci@latest` (organizes imports) +- Ensure code is formatted correctly with `gofumpt -l -w -extra ./` +- Ensure code is gci'd with `gci.exe write --skip-generated -s standard -s default .` - Ensure all unit tests pass with `go test ./...` -- Ensure code has been gci'd with `gci.exe write --skip-generated -s standard -s default .` - Ensure code has been linted with `docker run --rm -v ${pwd}:/app -w /app golangci/golangci-lint:latest golangci-lint run -v` diff --git a/pkg/wsman/amt/messagelog/decoder.go b/pkg/wsman/amt/messagelog/decoder.go index 7b79491b..debc3c42 100644 --- a/pkg/wsman/amt/messagelog/decoder.go +++ b/pkg/wsman/amt/messagelog/decoder.go @@ -5,6 +5,14 @@ package messagelog +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "fmt" + "time" +) + const ( AMTMessageLog string = "AMT_MessageLog" GetRecords string = "GetRecords" @@ -409,3 +417,226 @@ func (p PositionToFirstRecordReturnValue) String() string { return ValueNotFound } + +func parseEventLogResult(eventlogdata []string) (records []RawEventData, err error) { + records = make([]RawEventData, len(eventlogdata)) + + for idx, eventRecord := range eventlogdata { + decodedEventRecord, err := base64.StdEncoding.DecodeString(eventRecord) + if err != nil { + return records, err + } + + eventData := RawEventData{} + + buf := bytes.NewReader(decodedEventRecord) + + _ = binary.Read(buf, binary.LittleEndian, &eventData.TimeStamp) + _ = binary.Read(buf, binary.LittleEndian, &eventData.DeviceAddress) + _ = binary.Read(buf, binary.LittleEndian, &eventData.EventSensorType) + _ = binary.Read(buf, binary.LittleEndian, &eventData.EventType) + _ = binary.Read(buf, binary.LittleEndian, &eventData.EventOffset) + _ = binary.Read(buf, binary.LittleEndian, &eventData.EventSourceType) + _ = binary.Read(buf, binary.LittleEndian, &eventData.EventSeverity) + _ = binary.Read(buf, binary.LittleEndian, &eventData.SensorNumber) + _ = binary.Read(buf, binary.LittleEndian, &eventData.Entity) + _ = binary.Read(buf, binary.LittleEndian, &eventData.EntityInstance) + + for i := 13; i < 21; i++ { + var b uint8 + + _ = binary.Read(buf, binary.LittleEndian, &b) + + eventData.EventData = append(eventData.EventData, b) + } + + records[idx] = eventData + } + + return records, err +} + +func decodeEventRecord(eventLog []RawEventData) []RefinedEventData { + refinedEventData := make([]RefinedEventData, len(eventLog)) + + for idx, event := range eventLog { + decodedEvent := RefinedEventData{ + TimeStamp: time.Unix(int64(event.TimeStamp), 0), + DeviceAddress: event.DeviceAddress, + Description: decodeEventDetailString(event.EventSensorType, event.EventOffset, event.EventData), + Entity: SystemEntityTypes[int(event.Entity)], + EntityInstance: event.EntityInstance, + EventData: event.EventData, + EventSensorType: event.EventSensorType, + EventType: event.EventType, + EventOffset: event.EventOffset, + EventSourceType: event.EventSourceType, + EventSeverity: EventSeverity[int(event.EventSeverity)], + SensorNumber: event.SensorNumber, + } + refinedEventData[idx] = decodedEvent + } + + return refinedEventData +} + +func decodeEventDetailString(eventSensorType, eventOffset uint8, eventDataField []uint8) string { + switch eventSensorType { + case 6: + value := int(eventDataField[1]) + (int(eventDataField[2]) << 8) + + return fmt.Sprintf("Authentication failed %d times. The system may be under attack.", value) + case 15: + { + if eventDataField[0] == 235 { + return "Invalid Data" + } + + if eventOffset == 0 { + return SystemFirmwareError[int(eventDataField[1])] + } + + return SystemFirmwareProgress[int(eventDataField[1])] + } + case 18: + // System watchdog event + if eventDataField[0] == 170 { + watchdog := fmt.Sprintf("%x%x%x%x-%x%x", eventDataField[4], eventDataField[3], eventDataField[2], eventDataField[1], eventDataField[6], eventDataField[5]) + watchdogCurrentState := WatchdogCurrentStates[int(eventDataField[7])] + + return fmt.Sprintf("Agent watchdog %s-... changed to %s", watchdog, watchdogCurrentState) + } + + return "Unknown event data field" + case 30: + return "No bootable media" + case 32: + return "Operating system lockup or power interrupt" + case 35: + return "System boot failure" + case 37: + return "System firmware started (at least one CPU is properly executing)." + default: + return fmt.Sprintf("Unknown Sensor Type #%d", eventSensorType) + } +} + +var EventSeverity = map[int]string{ + 0: "Unspecified", + 1: "Monitor", + 2: "Information", + 4: "OK", + 8: "Non-critical condition", + 16: "Critical condition", + 32: "Non-recoverable condition", +} + +var SystemEntityTypes = map[int]string{ + 0: "Unspecified", + 1: "Other", + 2: "Unknown", + 3: "Processor", + 4: "Disk", + 5: "Peripheral", + 6: "System management module", + 7: "System board", + 8: "Memory module", + 9: "Processor module", + 10: "Power supply", + 11: "Add in card", + 12: "Front panel board", + 13: "Back panel board", + 14: "Power system board", + 15: "Drive backplane", + 16: "System internal expansion board", + 17: "Other system board", + 18: "Processor board", + 19: "Power unit", + 20: "Power module", + 21: "Power management board", + 22: "Chassis back panel board", + 23: "System chassis", + 24: "Sub chassis", + 25: "Other chassis board", + 26: "Disk drive bay", + 27: "Peripheral bay", + 28: "Device bay", + 29: "Fan cooling", + 30: "Cooling unit", + 31: "Cable interconnect", + 32: "Memory device", + 33: "System management software", + 34: "BIOS", + 35: "Intel(r) ME", + 36: "System bus", + 37: "Group", + 38: "Intel(r) ME", + 39: "External environment", + 40: "Battery", + 41: "Processing blade", + 42: "Connectivity switch", + 43: "Processor/memory module", + 44: "I/O module", + 45: "Processor I/O module", + 46: "Management controller firmware", + 47: "IPMI channel", + 48: "PCI bus", + 49: "PCI express bus", + 50: "SCSI bus", + 51: "SATA/SAS bus", + 52: "Processor front side bus", +} + +var SystemFirmwareError = map[int]string{ + 0: "Unspecified.", + 1: "No system memory is physically installed in the system.", + 2: "No usable system memory, all installed memory has experienced an unrecoverable failure.", + 3: "Unrecoverable hard-disk/ATAPI/IDE device failure.", + 4: "Unrecoverable system-board failure.", + 5: "Unrecoverable diskette subsystem failure.", + 6: "Unrecoverable hard-disk controller failure.", + 7: "Unrecoverable PS/2 or USB keyboard failure.", + 8: "Removable boot media not found.", + 9: "Unrecoverable video controller failure.", + 10: "No video device detected.", + 11: "Firmware (BIOS) ROM corruption detected.", + 12: "CPU voltage mismatch (processors that share same supply have mismatched voltage requirements)", + 13: "CPU speed matching failure", +} + +var SystemFirmwareProgress = map[int]string{ + 0: "Unspecified.", + 1: "Memory initialization.", + 2: "Starting hard-disk initialization and test", + 3: "Secondary processor(s) initialization", + 4: "User authentication", + 5: "User-initiated system setup", + 6: "USB resource configuration", + 7: "PCI resource configuration", + 8: "Option ROM initialization", + 9: "Video initialization", + 10: "Cache initialization", + 11: "SM Bus initialization", + 12: "Keyboard controller initialization", + 13: "Embedded controller/management controller initialization", + 14: "Docking station attachment", + 15: "Enabling docking station", + 16: "Docking station ejection", + 17: "Disabling docking station", + 18: "Calling operating system wake-up vector", + 19: "Starting operating system boot process", + 20: "Baseboard or motherboard initialization", + 21: "reserved", + 22: "Floppy initialization", + 23: "Keyboard test", + 24: "Pointing device test", + 25: "Primary processor initialization", +} + +var WatchdogCurrentStates = map[int]string{ + 1: "Not Started", + 2: "Stopped", + 4: "Running", + 8: "Expired", + 16: "Suspended", +} diff --git a/pkg/wsman/amt/messagelog/decoder_test.go b/pkg/wsman/amt/messagelog/decoder_test.go index 13484d22..851f5ac4 100644 --- a/pkg/wsman/amt/messagelog/decoder_test.go +++ b/pkg/wsman/amt/messagelog/decoder_test.go @@ -5,7 +5,13 @@ package messagelog -import "testing" +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) func TestCapabilities_String(t *testing.T) { tests := []struct { @@ -301,3 +307,49 @@ func TestPositionToFirstRecordReturnValue_String(t *testing.T) { } } } + +func TestConvertToEventLogResult(t *testing.T) { + records := []string{"Y8iYZf8GbwVoEP8mYaoKAAAAAAAA", "IgYBZf8PbwJoAf8iAEAHAAAAAAAA", "IgYBZf8PbwJoAf8iAEAHAAAAAAAA"} + expectedResult := []RawEventData{{TimeStamp: 0x6598c863, DeviceAddress: 0xff, EventSensorType: 0x6, EventType: 0x6f, EventOffset: 0x5, EventSourceType: 0x68, EventSeverity: 0x10, SensorNumber: 0xff, Entity: 0x26, EntityInstance: 0x61, EventData: []uint8{0xaa, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, {TimeStamp: 0x65010622, DeviceAddress: 0xff, EventSensorType: 0xf, EventType: 0x6f, EventOffset: 0x2, EventSourceType: 0x68, EventSeverity: 0x1, SensorNumber: 0xff, Entity: 0x22, EntityInstance: 0x0, EventData: []uint8{0x40, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}, {TimeStamp: 0x65010622, DeviceAddress: 0xff, EventSensorType: 0xf, EventType: 0x6f, EventOffset: 0x2, EventSourceType: 0x68, EventSeverity: 0x1, SensorNumber: 0xff, Entity: 0x22, EntityInstance: 0x0, EventData: []uint8{0x40, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}} + expectedDecodedResult := []RefinedEventData{{TimeStamp: time.Unix(int64(0x6598c863), 0), DeviceAddress: 0xff, Description: "Authentication failed 10 times. The system may be under attack.", Entity: "Intel(r) ME", EntityInstance: 0x61, EventData: []uint8{0xaa, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, EventSensorType: 0x6, EventType: 0x6f, EventOffset: 0x5, EventSourceType: 0x68, EventSeverity: "Critical condition", SensorNumber: 0xff}, {TimeStamp: time.Unix(int64(0x65010622), 0), DeviceAddress: 0xff, Description: "PCI resource configuration", Entity: "BIOS", EntityInstance: 0x0, EventData: []uint8{0x40, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, EventSensorType: 0xf, EventType: 0x6f, EventOffset: 0x2, EventSourceType: 0x68, EventSeverity: "Monitor", SensorNumber: 0xff}, {TimeStamp: time.Unix(int64(0x65010622), 0), DeviceAddress: 0xff, Description: "PCI resource configuration", Entity: "BIOS", EntityInstance: 0x0, EventData: []uint8{0x40, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, EventSensorType: 0xf, EventType: 0x6f, EventOffset: 0x2, EventSourceType: 0x68, EventSeverity: "Monitor", SensorNumber: 0xff}} + + result, err := parseEventLogResult(records) + if err != nil { + t.Errorf("Error: %v", err) + } + + decodedResult := decodeEventRecord(result) + + assert.Equal(t, expectedResult, result) + assert.Equal(t, expectedDecodedResult, decodedResult) +} + +func TestDecodeEventDetailString(t *testing.T) { + tests := []struct { + eventSensorType uint8 + eventOffset uint8 + eventDataField []uint8 + expected string + }{ + {6, 0, []uint8{0, 5, 0}, "Authentication failed 5 times. The system may be under attack."}, + {6, 0, []uint8{0, 1, 1}, "Authentication failed 257 times. The system may be under attack."}, + {15, 0, []uint8{235}, "Invalid Data"}, + {15, 0, []uint8{0, 1}, "No system memory is physically installed in the system."}, + {15, 1, []uint8{0, 2}, "Starting hard-disk initialization and test"}, + {18, 0, []uint8{170, 1, 2, 3, 4, 5, 6, 1}, "Agent watchdog 4321-65-... changed to Not Started"}, + {30, 0, nil, "No bootable media"}, + {32, 0, nil, "Operating system lockup or power interrupt"}, + {35, 0, nil, "System boot failure"}, + {37, 0, nil, "System firmware started (at least one CPU is properly executing)."}, + {0, 0, nil, "Unknown Sensor Type #0"}, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("eventSensorType=%d/eventOffset=%d", test.eventSensorType, test.eventOffset), func(t *testing.T) { + result := decodeEventDetailString(test.eventSensorType, test.eventOffset, test.eventDataField) + if result != test.expected { + t.Errorf("Expected %q but got %q", test.expected, result) + } + }) + } +} diff --git a/pkg/wsman/amt/messagelog/log.go b/pkg/wsman/amt/messagelog/log.go index 8221db00..0e5adb97 100644 --- a/pkg/wsman/amt/messagelog/log.go +++ b/pkg/wsman/amt/messagelog/log.go @@ -124,15 +124,22 @@ func (messageLog Service) GetRecords(identifier int) (response Response, err err // send the message to AMT err = messageLog.base.Execute(response.Message) if err != nil { - return + return response, err } // put the xml response into the go struct err = xml.Unmarshal([]byte(response.XMLOutput), &response) if err != nil { - return + return response, err } - return + response.Body.GetRecordsResponse.RawEventData, err = parseEventLogResult(response.Body.GetRecordsResponse.RecordArray) + if err != nil { + return response, err + } + + response.Body.GetRecordsResponse.RefinedEventData = decodeEventRecord(response.Body.GetRecordsResponse.RawEventData) + + return response, err } // Requests that an iteration of the MessageLog be established and that the iterator be set to the first entry in the Log. An identifier for the iterator is returned as an output parameter of the method. Regarding iteration, you have 2 choices: 1) Embed iteration data in the method call, and allow implementations to track/ store this data manually; or, 2) Iterate using a separate object (for example, class ActiveIterator) as an iteration agent. The first approach is used here for interoperability. The second requires an instance of the Iterator object for EACH iteration in progress. 2's functionality could be implemented underneath 1. diff --git a/pkg/wsman/amt/messagelog/log_test.go b/pkg/wsman/amt/messagelog/log_test.go index 00208ed0..3ea97c8e 100644 --- a/pkg/wsman/amt/messagelog/log_test.go +++ b/pkg/wsman/amt/messagelog/log_test.go @@ -8,6 +8,7 @@ package messagelog import ( "encoding/xml" "testing" + "time" "github.com/stretchr/testify/assert" @@ -22,7 +23,7 @@ func TestJson(t *testing.T) { GetResponse: MessageLogResponse{}, }, } - expectedResult := "{\"XMLName\":{\"Space\":\"\",\"Local\":\"\"},\"GetResponse\":{\"XMLName\":{\"Space\":\"\",\"Local\":\"\"},\"Capabilities\":null,\"CharacterSet\":0,\"CreationClassName\":\"\",\"CurrentNumberOfRecords\":0,\"ElementName\":\"\",\"EnabledDefault\":0,\"EnabledState\":0,\"HealthState\":0,\"IsFrozen\":false,\"LastChange\":0,\"LogState\":0,\"MaxLogSize\":0,\"MaxNumberOfRecords\":0,\"MaxRecordSize\":0,\"Name\":\"\",\"OperationalStatus\":null,\"OverwritePolicy\":0,\"PercentageNearFull\":0,\"RequestedState\":0,\"SizeOfHeader\":0,\"SizeOfRecordHeader\":0,\"Status\":\"\"},\"EnumerateResponse\":{\"EnumerationContext\":\"\"},\"PullResponse\":{\"XMLName\":{\"Space\":\"\",\"Local\":\"\"},\"MessageLogItems\":null},\"GetRecordsResponse\":{\"XMLName\":{\"Space\":\"\",\"Local\":\"\"},\"IterationIdentifier\":0,\"NoMoreRecords\":false,\"RecordArray\":null,\"ReturnValue\":0},\"PositionToFirstRecordResponse\":{\"XMLName\":{\"Space\":\"\",\"Local\":\"\"},\"IterationIdentifier\":0,\"ReturnValue\":0}}" + expectedResult := "{\"XMLName\":{\"Space\":\"\",\"Local\":\"\"},\"GetResponse\":{\"XMLName\":{\"Space\":\"\",\"Local\":\"\"},\"Capabilities\":null,\"CharacterSet\":0,\"CreationClassName\":\"\",\"CurrentNumberOfRecords\":0,\"ElementName\":\"\",\"EnabledDefault\":0,\"EnabledState\":0,\"HealthState\":0,\"IsFrozen\":false,\"LastChange\":0,\"LogState\":0,\"MaxLogSize\":0,\"MaxNumberOfRecords\":0,\"MaxRecordSize\":0,\"Name\":\"\",\"OperationalStatus\":null,\"OverwritePolicy\":0,\"PercentageNearFull\":0,\"RequestedState\":0,\"SizeOfHeader\":0,\"SizeOfRecordHeader\":0,\"Status\":\"\"},\"EnumerateResponse\":{\"EnumerationContext\":\"\"},\"PullResponse\":{\"XMLName\":{\"Space\":\"\",\"Local\":\"\"},\"MessageLogItems\":null},\"GetRecordsResponse\":{\"XMLName\":{\"Space\":\"\",\"Local\":\"\"},\"IterationIdentifier\":0,\"NoMoreRecords\":false,\"RecordArray\":null,\"RawEventData\":null,\"RefinedEventData\":null,\"ReturnValue\":0},\"PositionToFirstRecordResponse\":{\"XMLName\":{\"Space\":\"\",\"Local\":\"\"},\"IterationIdentifier\":0,\"ReturnValue\":0}}" result := response.JSON() assert.Equal(t, expectedResult, result) } @@ -33,7 +34,7 @@ func TestYaml(t *testing.T) { GetResponse: MessageLogResponse{}, }, } - expectedResult := "xmlname:\n space: \"\"\n local: \"\"\ngetresponse:\n xmlname:\n space: \"\"\n local: \"\"\n capabilities: []\n characterset: 0\n creationclassname: \"\"\n currentnumberofrecords: 0\n elementname: \"\"\n enableddefault: 0\n enabledstate: 0\n healthstate: 0\n isfrozen: false\n lastchange: 0\n logstate: 0\n maxlogsize: 0\n maxnumberofrecords: 0\n maxrecordsize: 0\n name: \"\"\n operationalstatus: []\n overwritepolicy: 0\n percentagenearfull: 0\n requestedstate: 0\n sizeofheader: 0\n sizeofrecordheader: 0\n status: \"\"\nenumerateresponse:\n enumerationcontext: \"\"\npullresponse:\n xmlname:\n space: \"\"\n local: \"\"\n messagelogitems: []\ngetrecordsresponse:\n xmlname:\n space: \"\"\n local: \"\"\n iterationidentifier: 0\n nomorerecords: false\n recordarray: []\n returnvalue: 0\npositiontofirstrecordresponse:\n xmlname:\n space: \"\"\n local: \"\"\n iterationidentifier: 0\n returnvalue: 0\n" + expectedResult := "xmlname:\n space: \"\"\n local: \"\"\ngetresponse:\n xmlname:\n space: \"\"\n local: \"\"\n capabilities: []\n characterset: 0\n creationclassname: \"\"\n currentnumberofrecords: 0\n elementname: \"\"\n enableddefault: 0\n enabledstate: 0\n healthstate: 0\n isfrozen: false\n lastchange: 0\n logstate: 0\n maxlogsize: 0\n maxnumberofrecords: 0\n maxrecordsize: 0\n name: \"\"\n operationalstatus: []\n overwritepolicy: 0\n percentagenearfull: 0\n requestedstate: 0\n sizeofheader: 0\n sizeofrecordheader: 0\n status: \"\"\nenumerateresponse:\n enumerationcontext: \"\"\npullresponse:\n xmlname:\n space: \"\"\n local: \"\"\n messagelogitems: []\ngetrecordsresponse:\n xmlname:\n space: \"\"\n local: \"\"\n iterationidentifier: 0\n nomorerecords: false\n recordarray: []\n raweventdata: []\n refinedeventdata: []\n returnvalue: 0\npositiontofirstrecordresponse:\n xmlname:\n space: \"\"\n local: \"\"\n iterationidentifier: 0\n returnvalue: 0\n" result := response.YAML() assert.Equal(t, expectedResult, result) } @@ -196,7 +197,92 @@ func TestPositiveAMT_MessageLog(t *testing.T) { IterationIdentifier: 3, NoMoreRecords: true, RecordArray: []string{"Y8iYZf8GbwVoEP8mYaoKAAAAAAAA", "IgYBZf8PbwJoAf8iAEAHAAAAAAAA", "IgYBZf8PbwJoAf8iAEAHAAAAAAAA"}, - ReturnValue: 0, + RawEventData: []RawEventData{ + { + TimeStamp: 0x6598c863, + DeviceAddress: 0xff, + EventSensorType: 0x6, + EventType: 0x6f, + EventOffset: 0x5, + EventSourceType: 0x68, + EventSeverity: 0x10, + SensorNumber: 0xff, + Entity: 0x26, + EntityInstance: 0x61, + EventData: []uint8{0xaa, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + }, + { + TimeStamp: 0x65010622, + DeviceAddress: 0xff, + EventSensorType: 0xf, + EventType: 0x6f, + EventOffset: 0x2, + EventSourceType: 0x68, + EventSeverity: 0x1, + SensorNumber: 0xff, + Entity: 0x22, + EntityInstance: 0x0, + EventData: []uint8{0x40, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + }, + { + TimeStamp: 0x65010622, + DeviceAddress: 0xff, + EventSensorType: 0xf, + EventType: 0x6f, + EventOffset: 0x2, + EventSourceType: 0x68, + EventSeverity: 0x1, + SensorNumber: 0xff, + Entity: 0x22, + EntityInstance: 0x0, + EventData: []uint8{0x40, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + }, + }, + RefinedEventData: []RefinedEventData{ + { + TimeStamp: time.Unix(int64(0x6598c863), 0), + Description: "Authentication failed 10 times. The system may be under attack.", + DeviceAddress: 255, + Entity: "Intel(r) ME", + EntityInstance: 97, + EventData: []uint8{0xaa, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + EventOffset: 5, + EventSensorType: 6, + EventSeverity: "Critical condition", + EventSourceType: 104, + EventType: 111, + SensorNumber: 255, + }, + { + TimeStamp: time.Unix(int64(0x65010622), 0), + Description: "PCI resource configuration", + DeviceAddress: 255, + Entity: "BIOS", + EntityInstance: 0, + EventData: []uint8{0x40, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + EventOffset: 2, + EventSensorType: 15, + EventSeverity: "Monitor", + EventSourceType: 104, + EventType: 111, + SensorNumber: 255, + }, + { + TimeStamp: time.Unix(int64(0x65010622), 0), + Description: "PCI resource configuration", + DeviceAddress: 255, + Entity: "BIOS", + EntityInstance: 0, + EventData: []uint8{0x40, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + EventOffset: 2, + EventSensorType: 15, + EventSeverity: "Monitor", + EventSourceType: 104, + EventType: 111, + SensorNumber: 255, + }, + }, + ReturnValue: 0, }, }, }, diff --git a/pkg/wsman/amt/messagelog/types.go b/pkg/wsman/amt/messagelog/types.go index 8b9a4f47..6ff74c5d 100644 --- a/pkg/wsman/amt/messagelog/types.go +++ b/pkg/wsman/amt/messagelog/types.go @@ -7,6 +7,7 @@ package messagelog import ( "encoding/xml" + "time" "github.com/open-amt-cloud-toolkit/go-wsman-messages/v2/internal/message" "github.com/open-amt-cloud-toolkit/go-wsman-messages/v2/pkg/wsman/client" @@ -72,6 +73,8 @@ type ( IterationIdentifier int `xml:"IterationIdentifier"` // An identifier for the iterator. NoMoreRecords bool `xml:"NoMoreRecords"` // Indicates that there are no more records to read RecordArray []string `xml:"RecordArray"` // Array of records encoded as Base64 + RawEventData []RawEventData `xml:"RawEventData"` // Slice of raw event data + RefinedEventData []RefinedEventData `xml:"RefinedEventData"` // Slice of refined event data ReturnValue GetRecordsReturnValue `xml:"ReturnValue"` // ValueMap={0, 1, 2, 3} Values={Completed with No Error, Not Supported, Invalid record pointed, No record exists in log} } @@ -81,6 +84,35 @@ type ( ReturnValue PositionToFirstRecordReturnValue `xml:"ReturnValue"` // ValueMap={0, 1, 2} Values={Completed with No Error, Not Supported, No record exists} } + RawEventData struct { + TimeStamp uint32 + DeviceAddress uint8 + EventSensorType uint8 + EventType uint8 + EventOffset uint8 + EventSourceType uint8 + EventSeverity uint8 + SensorNumber uint8 + Entity uint8 + EntityInstance uint8 + EventData []uint8 + } + + RefinedEventData struct { + TimeStamp time.Time + DeviceAddress uint8 + Description string + Entity string + EntityInstance uint8 + EventData []uint8 + EventSensorType uint8 + EventType uint8 + EventOffset uint8 + EventSourceType uint8 + EventSeverity string + SensorNumber uint8 + } + // Capabilities is an array of integers indicating the Log capabilities. Capabilities int diff --git a/pkg/wsman/amt/publickey/certificate.go b/pkg/wsman/amt/publickey/certificate.go index 4a18b711..49f6b357 100644 --- a/pkg/wsman/amt/publickey/certificate.go +++ b/pkg/wsman/amt/publickey/certificate.go @@ -76,20 +76,23 @@ func (certificate Certificate) Enumerate() (response Response, err error) { // Pull returns the instances of this class. An enumeration context provided by the Enumerate call is used as input. func (certificate Certificate) Pull(enumerationContext string) (response Response, err error) { var refinedOutput []RefinedPublicKeyCertificateResponse + response = Response{ Message: &client.Message{ XMLInput: certificate.base.Pull(enumerationContext), }, } + // send the message to AMT err = certificate.base.Execute(response.Message) if err != nil { - return + return response, err } + // put the xml response into the go struct err = xml.Unmarshal([]byte(response.XMLOutput), &response) if err != nil { - return + return response, err } for _, item := range response.Body.PullResponse.PublicKeyCertificateItems { @@ -108,7 +111,7 @@ func (certificate Certificate) Pull(enumerationContext string) (response Respons response.Body.RefinedPullResponse.PublicKeyCertificateItems = refinedOutput - return + return response, err } // Put will change properties of the selected instance. diff --git a/pkg/wsman/amt/publicprivate/keypair.go b/pkg/wsman/amt/publicprivate/keypair.go index 7174657f..7b2e8a9c 100644 --- a/pkg/wsman/amt/publicprivate/keypair.go +++ b/pkg/wsman/amt/publicprivate/keypair.go @@ -71,21 +71,25 @@ func (keyPair KeyPair) Enumerate() (response Response, err error) { // Pull returns the instances of this class. An enumeration context provided by the Enumerate call is used as input. func (keyPair KeyPair) Pull(enumerationContext string) (response Response, err error) { var refinedOutput []RefinedPublicPrivateKeyPair + response = Response{ Message: &client.Message{ XMLInput: keyPair.base.Pull(enumerationContext), }, } + // send the message to AMT err = keyPair.base.Execute(response.Message) if err != nil { - return + return response, err } + // put the xml response into the go struct err = xml.Unmarshal([]byte(response.XMLOutput), &response) if err != nil { - return + return response, err } + for _, item := range response.Body.PullResponse.PublicPrivateKeyPairItems { output := RefinedPublicPrivateKeyPair{ InstanceID: item.InstanceID, @@ -98,7 +102,7 @@ func (keyPair KeyPair) Pull(enumerationContext string) (response Response, err e response.Body.RefinedPullResponse.PublicPrivateKeyPairItems = refinedOutput - return + return response, err } // Deletes an instance of a key pair. diff --git a/pkg/wsman/client/wsman_tcp.go b/pkg/wsman/client/wsman_tcp.go index 3f6a6a4e..73f30b29 100644 --- a/pkg/wsman/client/wsman_tcp.go +++ b/pkg/wsman/client/wsman_tcp.go @@ -70,14 +70,8 @@ func (t *Target) Receive() ([]byte, error) { return nil, fmt.Errorf("no active connection") } - item := t.bufferPool.Get() - - tmp, ok := item.([]byte) - if !ok { - return nil, fmt.Errorf("failed to get buffer from pool") - } - - defer t.bufferPool.Put(&tmp) + tmp := t.bufferPool.Get().([]byte) + defer t.bufferPool.Put(tmp) n, err := t.conn.Read(tmp) if err != nil { diff --git a/pkg/wsman/ips/alarmclock/occurrence.go b/pkg/wsman/ips/alarmclock/occurrence.go index 9d4e3e66..7088f2ea 100644 --- a/pkg/wsman/ips/alarmclock/occurrence.go +++ b/pkg/wsman/ips/alarmclock/occurrence.go @@ -48,7 +48,7 @@ func (occurrence Occurrence) Get(alarmName string) (response Response, err error // Delete removes a the specified instance. func (occurrence Occurrence) Delete(handle string) (response Response, err error) { - selector := message.Selector{Name: "Name", Value: handle} + selector := message.Selector{Name: "InstanceID", Value: handle} response = Response{ Message: &client.Message{ XMLInput: occurrence.base.Delete(selector), diff --git a/pkg/wsman/ips/alarmclock/occurrence_test.go b/pkg/wsman/ips/alarmclock/occurrence_test.go index ed10e326..f6dcd614 100644 --- a/pkg/wsman/ips/alarmclock/occurrence_test.go +++ b/pkg/wsman/ips/alarmclock/occurrence_test.go @@ -136,7 +136,7 @@ func TestPositiveIPS_AlarmClockOccurrence(t *testing.T) { "IPS_AlarmClockOccurrence", wsmantesting.Delete, "", - "testalarm", + "testalarm", func() (Response, error) { client.CurrentMessage = wsmantesting.CurrentMessageDelete @@ -256,7 +256,7 @@ func TestNegativeIPS_AlarmClockOccurrence(t *testing.T) { "IPS_AlarmClockOccurrence", wsmantesting.Delete, "", - "testalarm", + "testalarm", func() (Response, error) { client.CurrentMessage = wsmantesting.CurrentMessageError