-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for encoding/decoding cert payloads
OVERVIEW Add support for generating a certificate metadata payload in JSON format from a specified metadata payload format version. Add support for support for decoding a given (valid) certificate metadata payload. The format is automatically detected from a list of valid format versions. The intent is to support all stable format versions indefinitely. As of this commit / PR, format 0 is still under active development. This format version is an "unstable" metadata format and is not covered by this goal; format version 0 is subject to change often as development continues. Format version 1 is implemented at this time as a stub version for testing purposes; once stable the plan is to promote version 0 content as the initial version 1. CHANGES Primary changes: - add support for generating a JSON payload from a specified metadata payload format version - this can be generated by calling the `Encode` function from a specific format version or by calling the top-level `Encode` function and specifying a valid format version number (e.g., `0` or `1`) - add support for decoding a given (valid) certificate metadata payload - the intent is to support decoding any given payload matching the set of supported format versions (e.g., `0`, `1`) - the caller provides an instance of a specific format version of the certificate metadata payload and the `Decode` function for that format version is used - once a format version is stable, the intent is to support creating and decoding it using this library indefinitely - this should allow the sysadmin using the `check_cert` plugin to specify what version of the payload format they wish to create - this should allow the sysadmin using a reporting tool to consume a certificate metadata payload generated by the `check_cert` plugin in the same fixed version as the one they asked the `check_cert` plugin to create - this process should continue to work as-is until the sysadmin decides to explicitly change the certificate metadata payload format version they're working with; updating this dependency should not break payload generation or consumption Other changes: - add identification of misordered certificate chains - add example "test" to illustrate library usage - initial example uses format 0; the plan is to update the example once format 1 is released/stable - documentation updates - general refactoring work REFERENCES - #19 - #31 - #46 - atc0005/check-cert#1004
- Loading branch information
Showing
30 changed files
with
3,288 additions
and
256 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
// Copyright 2024 Adam Chalkley | ||
// | ||
// https://github.com/atc0005/cert-payload | ||
// | ||
// Licensed under the MIT License. See LICENSE file in the project root for | ||
// full license information. | ||
|
||
package payload_test | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
payload "github.com/atc0005/cert-payload" | ||
format0 "github.com/atc0005/cert-payload/format/v0" | ||
) | ||
|
||
// Example of parsing a previously retrieved Nagios XI API response (saved to | ||
// a JSON file) from the /nagiosxi/api/v1/objects/servicestatus endpoint, | ||
// extracting and decoding an embedded certificate metadata payload from each | ||
// status entry and then unmarshalling the result into a specific format | ||
// version (in this case format 0). | ||
// | ||
// TODO: Update this example once format version 1 is released. | ||
func Example_extractandDecodePayloadsFromNagiosXIAPI() { | ||
if len(os.Args) < 2 { | ||
fmt.Println("Missing input file") | ||
os.Exit(1) | ||
} | ||
|
||
sampleInputFile := os.Args[1] | ||
|
||
jsonInput, readErr := os.ReadFile(filepath.Clean(sampleInputFile)) | ||
if readErr != nil { | ||
fmt.Println("Failed to read sample input file:", readErr) | ||
os.Exit(1) | ||
} | ||
|
||
var serviceStatusResponse ServiceStatusResponse | ||
decodeErr := json.Unmarshal(jsonInput, &serviceStatusResponse) | ||
if decodeErr != nil { | ||
fmt.Println("Failed to decode JSON input:", decodeErr) | ||
os.Exit(1) | ||
} | ||
|
||
for i, serviceStatus := range serviceStatusResponse.ServiceStatuses { | ||
fmt.Printf("\n\nProcess service check result %d ...", i) | ||
|
||
longServiceOutput := serviceStatus.LongServiceOutput | ||
|
||
// We use a pretend nagios.ExtractAndDecodePayload implementation. | ||
unencodedPayload, payloadDecodeErr := ExtractAndDecodePayload( | ||
longServiceOutput, | ||
"", | ||
DefaultASCII85EncodingDelimiterLeft, | ||
DefaultASCII85EncodingDelimiterRight, | ||
) | ||
|
||
if payloadDecodeErr != nil { | ||
fmt.Println(" WARNING: Failed to extract and decode payload from original plugin output:", payloadDecodeErr) | ||
// os.Exit(1) | ||
continue // we have some known cases of explicitly excluding payload generation | ||
} | ||
|
||
format0Payload := format0.CertChainPayload{} | ||
jsonDecodeErr := payload.Decode(unencodedPayload, &format0Payload) | ||
if jsonDecodeErr != nil { | ||
fmt.Println("Failed to decode JSON payload from original plugin output:", jsonDecodeErr) | ||
os.Exit(1) | ||
} | ||
|
||
if !format0Payload.Issues.Confirmed() { | ||
fmt.Print(" Skipping (no cert chain issues detected)") | ||
continue | ||
} | ||
|
||
fmt.Printf( | ||
"\nJSON payload for %s (flagged as problematic):\n", | ||
format0Payload.Server, | ||
) | ||
|
||
var prettyJSON bytes.Buffer | ||
err := json.Indent(&prettyJSON, []byte(unencodedPayload), "", " ") | ||
if err == nil { | ||
_, _ = fmt.Fprintln(os.Stdout, prettyJSON.String()) | ||
} | ||
|
||
} | ||
|
||
} | ||
|
||
// Pulled from go-nagios repo to remove external dependency. | ||
const ( | ||
// DefaultASCII85EncodingDelimiterLeft is the left delimiter often used | ||
// with ascii85-encoded data. | ||
DefaultASCII85EncodingDelimiterLeft string = "<~" | ||
|
||
// DefaultASCII85EncodingDelimiterRight is the right delimiter often used | ||
// with ascii85-encoded data. | ||
DefaultASCII85EncodingDelimiterRight string = "~>" | ||
) | ||
|
||
// ExtractAndDecodePayload is a mockup for nagios.ExtractAndDecodePayload. | ||
func ExtractAndDecodePayload(text string, customRegex string, leftDelimiter string, rightDelimiter string) (string, error) { | ||
_ = text | ||
_ = customRegex | ||
_ = leftDelimiter | ||
_ = rightDelimiter | ||
|
||
return "placeholder", nil | ||
} | ||
|
||
// BoolString is a boolean value that is represented in JSON API input as a | ||
// string value ("1" or "0"). | ||
type BoolString bool | ||
|
||
// MarshalJSON implements the json.Marshaler interface. This compliments the | ||
// custom Unmarshaler implementation to handle conversion of Go boolean field | ||
// to JSON API expectations of a "1" or "0" string value. | ||
func (bs BoolString) MarshalJSON() ([]byte, error) { | ||
switch bs { | ||
case true: | ||
return json.Marshal("1") | ||
case false: | ||
return json.Marshal("0") | ||
|
||
} | ||
|
||
return nil, nil | ||
} | ||
|
||
// UnmarshalJSON implements the json.Unmarshaler interface to handle | ||
// converting a string value of "1" or "0" to a native boolean value. | ||
func (bs *BoolString) UnmarshalJSON(data []byte) error { | ||
|
||
// Per json.Unmarshaler convention we treat "null" value as a no-op. | ||
str := string(data) | ||
if str == "null" { | ||
return nil | ||
} | ||
|
||
// The 1 or 0 value is double-quoted, so we remove those before attempting | ||
// to parse as a boolean value. | ||
str = strings.Trim(str, `"`) | ||
|
||
boolValue, err := strconv.ParseBool(str) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
*bs = BoolString(boolValue) | ||
|
||
return nil | ||
} | ||
|
||
// credit: https://romangaranin.net/posts/2021-02-19-json-time-and-golang/ | ||
|
||
// DateTimeLayout is the time layout format as used by the JSON API. | ||
const DateTimeLayout string = "2006-01-02 15:04:05" | ||
|
||
// DateTime is time value as represented in the JSON API input. It uses the | ||
// DateTimeLayout format. | ||
type DateTime time.Time | ||
|
||
// String implements the fmt.Stringer interface as a convenience method. | ||
func (dt DateTime) String() string { | ||
return dt.Format(DateTimeLayout) | ||
} | ||
|
||
// Format calls (time.Time).Format as a convenience for the caller. | ||
func (dt DateTime) Format(layout string) string { | ||
return time.Time(dt).Format(layout) | ||
} | ||
|
||
// MarshalJSON implements the json.Marshaler interface. This compliments the | ||
// custom Unmarshaler implementation to handle conversion of a native Go | ||
// time.Time format to the JSON API expectations of a time value in the | ||
// DateTimeLayout format. | ||
func (dt DateTime) MarshalJSON() ([]byte, error) { | ||
return json.Marshal(time.Time(dt).Format(DateTimeLayout)) | ||
} | ||
|
||
// UnmarshalJSON implements the json.Unmarshaler interface to handle | ||
// converting a time string from the JSON API to a native Go time.Time value | ||
// using the DateTimeLayout format. | ||
func (dt *DateTime) UnmarshalJSON(data []byte) error { | ||
value := strings.Trim(string(data), `"`) // get rid of " | ||
if value == "" || value == "null" { | ||
|
||
// Per json.Unmarshaler convention we treat "null" value as a no-op. | ||
return nil | ||
} | ||
|
||
t, err := time.Parse(DateTimeLayout, value) // parse time | ||
if err != nil { | ||
return err | ||
} | ||
|
||
*dt = DateTime(t) // set result using the pointer | ||
|
||
return nil | ||
} | ||
|
||
type ServiceStatus struct { | ||
HostAddress string `json:"host_address"` | ||
HostAlias string `json:"host_alias"` | ||
HostName string `json:"host_name"` | ||
ServiceDescription string `json:"service_description"` | ||
ActiveChecksEnabled BoolString `json:"active_checks_enabled"` | ||
NotificationsEnabled BoolString `json:"notifications_enabled"` | ||
LongServiceOutput string `json:"long_output"` | ||
Notes string `json:"notes"` | ||
StatusUpdateTime DateTime `json:"status_update_time"` | ||
LastCheck DateTime `json:"last_check"` | ||
NextCheck DateTime `json:"next_check"` | ||
LastNotification DateTime `json:"last_notification"` | ||
NextNotification DateTime `json:"next_notification"` | ||
RawPerfData string `json:"perfdata"` | ||
} | ||
|
||
type ServiceStatusResponse struct { | ||
RecordCount int `json:"recordcount"` | ||
ServiceStatuses []ServiceStatus `json:"servicestatus"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.