diff --git a/sztp-agent/pkg/secureagent/daemon.go b/sztp-agent/pkg/secureagent/daemon.go index 95a993ba..65e592c4 100644 --- a/sztp-agent/pkg/secureagent/daemon.go +++ b/sztp-agent/pkg/secureagent/daemon.go @@ -73,7 +73,7 @@ func (a *Agent) RunCommandDaemon() error { if err != nil { return err } - _ = a.doReportProgress(ProgressTypeBootstrapComplete) + _ = a.doReportProgress(ProgressTypeBootstrapComplete, "Bootstrap Complete") return nil } @@ -96,38 +96,6 @@ func (a *Agent) getBootstrapURL() error { return nil } -func (a *Agent) doReportProgress(s ProgressType) error { - log.Println("[INFO] Starting the Report Progress request.") - url := strings.ReplaceAll(a.GetBootstrapURL(), "get-bootstrapping-data", "report-progress") - var p ProgressJSON - p.IetfSztpBootstrapServerInput.ProgressType = s.String() - p.IetfSztpBootstrapServerInput.Message = "message sent via JSON" - if s == ProgressTypeBootstrapComplete { - // TODO: use/generate real TA cert here - encodedKey := base64.StdEncoding.EncodeToString([]byte("mysshpass")) - p.IetfSztpBootstrapServerInput.TrustAnchorCerts.TrustAnchorCert = []string{encodedKey} - for _, key := range readSSHHostKeyPublicFiles("/etc/ssh/ssh_host_*key.pub") { - p.IetfSztpBootstrapServerInput.SSHHostKeys.SSHHostKey = append(p.IetfSztpBootstrapServerInput.SSHHostKeys.SSHHostKey, struct { - Algorithm string `json:"algorithm"` - KeyData string `json:"key-data"` - }{ - Algorithm: key.Type(), - KeyData: getSSHHostKeyString(key, false), - }) - } - } - a.SetProgressJSON(p) - inputJSON, _ := json.Marshal(a.GetProgressJSON()) - res, err := a.doTLSRequest(string(inputJSON), url, true) - if err != nil { - log.Println("[ERROR] ", err.Error()) - return err - } - log.Println(res) - log.Println("[INFO] Response retrieved successfully") - return nil -} - func (a *Agent) doHandleBootstrapRedirect() error { if reflect.ValueOf(a.BootstrapServerRedirectInfo).IsZero() { return nil @@ -160,7 +128,7 @@ func (a *Agent) doRequestBootstrapServerOnboardingInfo() error { return err } log.Println("[INFO] Response retrieved successfully") - _ = a.doReportProgress(ProgressTypeBootstrapInitiated) + _ = a.doReportProgress(ProgressTypeBootstrapInitiated, "Bootstrap Initiated") crypto := res.IetfSztpBootstrapServerOutput.ConveyedInformation newVal, err := base64.StdEncoding.DecodeString(crypto) if err != nil { @@ -200,7 +168,7 @@ func (a *Agent) doRequestBootstrapServerOnboardingInfo() error { //nolint:funlen func (a *Agent) downloadAndValidateImage() error { log.Printf("[INFO] Starting the Download Image: %v", a.BootstrapServerOnboardingInfo.IetfSztpConveyedInfoOnboardingInformation.BootImage.DownloadURI) - _ = a.doReportProgress(ProgressTypeBootImageInitiated) + _ = a.doReportProgress(ProgressTypeBootImageInitiated, "BootImage Initiated") // Download the image from DownloadURI and save it to a file a.BootstrapServerOnboardingInfo.IetfSztpConveyedInfoOnboardingInformation.InfoTimestampReference = fmt.Sprintf("%8d", time.Now().Unix()) for i, item := range a.BootstrapServerOnboardingInfo.IetfSztpConveyedInfoOnboardingInformation.BootImage.DownloadURI { @@ -286,7 +254,7 @@ func (a *Agent) downloadAndValidateImage() error { return errors.New("checksum mismatch") } log.Println("[INFO] Checksum verified successfully") - _ = a.doReportProgress(ProgressTypeBootImageComplete) + _ = a.doReportProgress(ProgressTypeBootImageComplete, "BootImage Complete") return nil default: return errors.New("unsupported hash algorithm") @@ -297,7 +265,7 @@ func (a *Agent) downloadAndValidateImage() error { func (a *Agent) copyConfigurationFile() error { log.Println("[INFO] Starting the Copy Configuration.") - _ = a.doReportProgress(ProgressTypeConfigInitiated) + _ = a.doReportProgress(ProgressTypeConfigInitiated, "Configuration Initiated") // Copy the configuration file to the device file, err := os.Create(ARTIFACTS_PATH + a.BootstrapServerOnboardingInfo.IetfSztpConveyedInfoOnboardingInformation.InfoTimestampReference + "-config") if err != nil { @@ -323,7 +291,7 @@ func (a *Agent) copyConfigurationFile() error { return err } log.Println("[INFO] Configuration file copied successfully") - _ = a.doReportProgress(ProgressTypeConfigComplete) + _ = a.doReportProgress(ProgressTypeConfigComplete, "Configuration Complete") return nil } @@ -343,7 +311,7 @@ func (a *Agent) launchScriptsConfiguration(typeOf string) error { reportEnd = ProgressTypePreScriptComplete } log.Println("[INFO] Starting the " + scriptName + "-configuration.") - _ = a.doReportProgress(reportStart) + _ = a.doReportProgress(reportStart, "Report starting") // nolint:gosec file, err := os.Create(ARTIFACTS_PATH + a.BootstrapServerOnboardingInfo.IetfSztpConveyedInfoOnboardingInformation.InfoTimestampReference + scriptName + "configuration.sh") if err != nil { @@ -376,7 +344,7 @@ func (a *Agent) launchScriptsConfiguration(typeOf string) error { return err } log.Println(string(out)) // remove it - _ = a.doReportProgress(reportEnd) + _ = a.doReportProgress(reportEnd, "Report end") log.Println("[INFO] " + scriptName + "-Configuration script executed successfully") return nil } diff --git a/sztp-agent/pkg/secureagent/daemon_test.go b/sztp-agent/pkg/secureagent/daemon_test.go index 6ff9cc8b..dc9284e7 100644 --- a/sztp-agent/pkg/secureagent/daemon_test.go +++ b/sztp-agent/pkg/secureagent/daemon_test.go @@ -319,116 +319,6 @@ func TestAgent_doReqBootstrap(t *testing.T) { } } -//nolint:funlen -func TestAgent_doReportProgress(t *testing.T) { - var output []byte - expected := BootstrapServerPostOutput{ - IetfSztpBootstrapServerOutput: struct { - ConveyedInformation string `json:"conveyed-information"` - }{ - ConveyedInformation: "MIIDfwYLKoZIhvcNAQkQASugggNuBIIDansKICAiaWV0Zi1zenRwLWNvbnZleWVkLWluZm86b25ib2FyZGluZy1pbmZvcm1hdGlvbiI6IHsKICAgICJib290LWltYWdlIjogewogICAgICAiZG93bmxvYWQtdXJpIjogWwogICAgICAgICJodHRwOi8vd2ViOjgwODIvdmFyL2xpYi9taXNjL215LWJvb3QtaW1hZ2UuaW1nIiwKICAgICAgICAiZnRwOi8vd2ViOjMwODIvdmFyL2xpYi9taXNjL215LWJvb3QtaW1hZ2UuaW1nIgogICAgICBdLAogICAgICAiaW1hZ2UtdmVyaWZpY2F0aW9uIjogWwogICAgICAgIHsKICAgICAgICAgICJoYXNoLWFsZ29yaXRobSI6ICJpZXRmLXN6dHAtY29udmV5ZWQtaW5mbzpzaGEtMjU2IiwKICAgICAgICAgICJoYXNoLXZhbHVlIjogIjdiOmNhOmU2OmFjOjIzOjA2OmQ4Ojc5OjA2OjhjOmFjOjAzOjgwOmUyOjE2OjQ0OjdlOjQwOjZhOjY1OmZhOmQ0OjY5OjYxOjZlOjA1OmNlOmY1Ojg3OmRjOjJiOjk3IgogICAgICAgIH0KICAgICAgXQogICAgfSwKICAgICJwcmUtY29uZmlndXJhdGlvbi1zY3JpcHQiOiAiSXlFdlltbHVMMkpoYzJnS1pXTm9ieUFpYVc1emFXUmxJSFJvWlNCd2NtVXRZMjl1Wm1sbmRYSmhkR2x2YmkxelkzSnBjSFF1TGk0aUNnPT0iLAogICAgImNvbmZpZ3VyYXRpb24taGFuZGxpbmciOiAibWVyZ2UiLAogICAgImNvbmZpZ3VyYXRpb24iOiAiUEhSdmNDQjRiV3h1Y3owaWFIUjBjSE02TDJWNFlXMXdiR1V1WTI5dEwyTnZibVpwWnlJK0NpQWdQR0Z1ZVMxNGJXd3RZMjl1ZEdWdWRDMXZhMkY1THo0S1BDOTBiM0ErQ2c9PSIsCiAgICAicG9zdC1jb25maWd1cmF0aW9uLXNjcmlwdCI6ICJJeUV2WW1sdUwySmhjMmdLWldOb2J5QWlhVzV6YVdSbElIUm9aU0J3YjNOMExXTnZibVpwWjNWeVlYUnBiMjR0YzJOeWFYQjBMaTR1SWdvPSIKICB9Cn0=", - }, - } - expectedFailedBase64 := BootstrapServerPostOutput{ - IetfSztpBootstrapServerOutput: struct { - ConveyedInformation string `json:"conveyed-information"` - }{ - ConveyedInformation: "{wrongBASE64}", - }, - } - svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - user, pass, _ := r.BasicAuth() - log.Println(user, pass) - - switch { - case (user + ":" + pass) == "USER:PASS": - w.WriteHeader(200) - output, _ = json.Marshal(expected) - case (user + ":" + pass) == "KOBASE64:KO": - w.WriteHeader(200) - output, _ = json.Marshal(expectedFailedBase64) - default: - w.WriteHeader(400) - output, _ = json.Marshal(expected) - } - - _, err := fmt.Fprint(w, string(output)) - if err != nil { - return - } - })) - defer svr.Close() - type fields struct { - BootstrapURL string - SerialNumber string - DevicePassword string - DevicePrivateKey string - DeviceEndEntityCert string - BootstrapTrustAnchorCert string - ContentTypeReq string - InputJSONContent string - DhcpLeaseFile string - ProgressJSON ProgressJSON - } - tests := []struct { - name string - fields fields - wantErr bool - }{ - { - name: "OK", - fields: fields{ - BootstrapURL: svr.URL, - SerialNumber: "USER", - DevicePassword: "PASS", - DevicePrivateKey: "PRIVATEKEY", - DeviceEndEntityCert: "ENDENTITYCERT", - BootstrapTrustAnchorCert: "TRUSTANCHORCERT", - ContentTypeReq: "application/vnd.ietf.sztp.bootstrap-server+json", - InputJSONContent: "INPUTJSON", - DhcpLeaseFile: "DHCPLEASEFILE", - ProgressJSON: ProgressJSON{}, - }, - wantErr: false, - }, - { - name: "KO", - fields: fields{ - BootstrapURL: svr.URL, - SerialNumber: "USER", - DevicePassword: "PASSWORDWRONG", - DevicePrivateKey: "PRIVATEKEY", - DeviceEndEntityCert: "ENDENTITYCERT", - BootstrapTrustAnchorCert: "TRUSTANCHORCERT", - ContentTypeReq: "application/vnd.ietf.sztp.bootstrap-server+json", - InputJSONContent: "INPUTJSON", - DhcpLeaseFile: "DHCPLEASEFILE", - ProgressJSON: ProgressJSON{}, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - a := &Agent{ - BootstrapURL: tt.fields.BootstrapURL, - SerialNumber: tt.fields.SerialNumber, - DevicePassword: tt.fields.DevicePassword, - DevicePrivateKey: tt.fields.DevicePrivateKey, - DeviceEndEntityCert: tt.fields.DeviceEndEntityCert, - BootstrapTrustAnchorCert: tt.fields.BootstrapTrustAnchorCert, - ContentTypeReq: tt.fields.ContentTypeReq, - InputJSONContent: tt.fields.InputJSONContent, - DhcpLeaseFile: tt.fields.DhcpLeaseFile, - ProgressJSON: tt.fields.ProgressJSON, - } - if err := a.doReportProgress(ProgressTypeBootstrapInitiated); (err != nil) != tt.wantErr { - t.Errorf("doReportProgress() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - //nolint:funlen func TestAgent_downloadAndValidateImage(t *testing.T) { svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/sztp-agent/pkg/secureagent/progress.go b/sztp-agent/pkg/secureagent/progress.go index 7b7c869e..0b46fefa 100644 --- a/sztp-agent/pkg/secureagent/progress.go +++ b/sztp-agent/pkg/secureagent/progress.go @@ -9,6 +9,13 @@ Copyright (C) 2022 Red Hat. // Package secureagent implements the secure agent package secureagent +import ( + "encoding/base64" + "encoding/json" + "log" + "strings" +) + type ProgressType int64 const ( @@ -102,6 +109,38 @@ func (s ProgressType) String() string { return "unknown" } +func (a *Agent) doReportProgress(s ProgressType, message string) error { + log.Println("[INFO] Starting the Report Progress request.") + url := strings.ReplaceAll(a.GetBootstrapURL(), "get-bootstrapping-data", "report-progress") + var p ProgressJSON + p.IetfSztpBootstrapServerInput.ProgressType = s.String() + p.IetfSztpBootstrapServerInput.Message = message + if s == ProgressTypeBootstrapComplete { + // TODO: use/generate real TA cert here + encodedKey := base64.StdEncoding.EncodeToString([]byte("mysshpass")) + p.IetfSztpBootstrapServerInput.TrustAnchorCerts.TrustAnchorCert = []string{encodedKey} + for _, key := range readSSHHostKeyPublicFiles("/etc/ssh/ssh_host_*key.pub") { + p.IetfSztpBootstrapServerInput.SSHHostKeys.SSHHostKey = append(p.IetfSztpBootstrapServerInput.SSHHostKeys.SSHHostKey, struct { + Algorithm string `json:"algorithm"` + KeyData string `json:"key-data"` + }{ + Algorithm: key.Type(), + KeyData: getSSHHostKeyString(key, false), + }) + } + } + a.SetProgressJSON(p) + inputJSON, _ := json.Marshal(a.GetProgressJSON()) + res, err := a.doTLSRequest(string(inputJSON), url, true) + if err != nil { + log.Println("[ERROR] ", err.Error()) + return err + } + log.Println(res) + log.Println("[INFO] Response retrieved successfully") + return nil +} + type ProgressJSON struct { IetfSztpBootstrapServerInput struct { ProgressType string `json:"progress-type"` diff --git a/sztp-agent/pkg/secureagent/progress_test.go b/sztp-agent/pkg/secureagent/progress_test.go new file mode 100644 index 00000000..7a55c0cb --- /dev/null +++ b/sztp-agent/pkg/secureagent/progress_test.go @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2022-2023 Red Hat. + +// Package secureagent implements the secure agent +package secureagent + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "net/http/httptest" + "testing" +) + +func TestProgressTypeString(t *testing.T) { + tests := []struct { + input ProgressType + expected string + }{ + {ProgressTypeBootstrapInitiated, "bootstrap-initiated"}, + {ProgressTypeParsingInitiated, "parsing-initiated"}, + {ProgressTypeParsingWarning, "parsing-warning"}, + {ProgressTypeParsingError, "parsing-error"}, + {ProgressTypeParsingComplete, "parsing-complete"}, + {ProgressTypeBootImageInitiated, "boot-image-initiated"}, + {ProgressTypeBootImageWarning, "boot-image-warning"}, + {ProgressTypeBootImageError, "boot-image-error"}, + {ProgressTypeBootImageMismatch, "boot-image-mismatch"}, + {ProgressTypeBootImageInstalledRebooting, "boot-image-installed-rebooting"}, + {ProgressTypeBootImageComplete, "boot-image-complete"}, + {ProgressTypePreScriptInitiated, "pre-script-initiated"}, + {ProgressTypePreScriptWarning, "pre-script-warning"}, + {ProgressTypePreScriptError, "pre-script-error"}, + {ProgressTypePreScriptComplete, "pre-script-complete"}, + {ProgressTypeConfigInitiated, "config-initiated"}, + {ProgressTypeConfigWarning, "config-warning"}, + {ProgressTypeConfigError, "config-error"}, + {ProgressTypeConfigComplete, "config-complete"}, + {ProgressTypePostScriptInitiated, "post-script-initiated"}, + {ProgressTypePostScriptWarning, "post-script-warning"}, + {ProgressTypePostScriptError, "post-script-error"}, + {ProgressTypePostScriptComplete, "post-script-complete"}, + {ProgressTypeBootstrapWarning, "bootstrap-warning"}, + {ProgressTypeBootstrapError, "bootstrap-error"}, + {ProgressTypeBootstrapComplete, "bootstrap-complete"}, + {ProgressTypeInformational, "informational"}, + {ProgressType(999), "unknown"}, // Test for an unknown value + } + + for _, test := range tests { + result := test.input.String() + if result != test.expected { + t.Errorf("For %v expected %v, but got %v", test.input, test.expected, result) + } + } +} + +//nolint:funlen +func TestAgent_doReportProgress(t *testing.T) { + var output []byte + expected := BootstrapServerPostOutput{ + IetfSztpBootstrapServerOutput: struct { + ConveyedInformation string `json:"conveyed-information"` + }{ + ConveyedInformation: "MIIDfwYLKoZIhvcNAQkQASugggNuBIIDansKICAiaWV0Zi1zenRwLWNvbnZleWVkLWluZm86b25ib2FyZGluZy1pbmZvcm1hdGlvbiI6IHsKICAgICJib290LWltYWdlIjogewogICAgICAiZG93bmxvYWQtdXJpIjogWwogICAgICAgICJodHRwOi8vd2ViOjgwODIvdmFyL2xpYi9taXNjL215LWJvb3QtaW1hZ2UuaW1nIiwKICAgICAgICAiZnRwOi8vd2ViOjMwODIvdmFyL2xpYi9taXNjL215LWJvb3QtaW1hZ2UuaW1nIgogICAgICBdLAogICAgICAiaW1hZ2UtdmVyaWZpY2F0aW9uIjogWwogICAgICAgIHsKICAgICAgICAgICJoYXNoLWFsZ29yaXRobSI6ICJpZXRmLXN6dHAtY29udmV5ZWQtaW5mbzpzaGEtMjU2IiwKICAgICAgICAgICJoYXNoLXZhbHVlIjogIjdiOmNhOmU2OmFjOjIzOjA2OmQ4Ojc5OjA2OjhjOmFjOjAzOjgwOmUyOjE2OjQ0OjdlOjQwOjZhOjY1OmZhOmQ0OjY5OjYxOjZlOjA1OmNlOmY1Ojg3OmRjOjJiOjk3IgogICAgICAgIH0KICAgICAgXQogICAgfSwKICAgICJwcmUtY29uZmlndXJhdGlvbi1zY3JpcHQiOiAiSXlFdlltbHVMMkpoYzJnS1pXTm9ieUFpYVc1emFXUmxJSFJvWlNCd2NtVXRZMjl1Wm1sbmRYSmhkR2x2YmkxelkzSnBjSFF1TGk0aUNnPT0iLAogICAgImNvbmZpZ3VyYXRpb24taGFuZGxpbmciOiAibWVyZ2UiLAogICAgImNvbmZpZ3VyYXRpb24iOiAiUEhSdmNDQjRiV3h1Y3owaWFIUjBjSE02TDJWNFlXMXdiR1V1WTI5dEwyTnZibVpwWnlJK0NpQWdQR0Z1ZVMxNGJXd3RZMjl1ZEdWdWRDMXZhMkY1THo0S1BDOTBiM0ErQ2c9PSIsCiAgICAicG9zdC1jb25maWd1cmF0aW9uLXNjcmlwdCI6ICJJeUV2WW1sdUwySmhjMmdLWldOb2J5QWlhVzV6YVdSbElIUm9aU0J3YjNOMExXTnZibVpwWjNWeVlYUnBiMjR0YzJOeWFYQjBMaTR1SWdvPSIKICB9Cn0=", + }, + } + expectedFailedBase64 := BootstrapServerPostOutput{ + IetfSztpBootstrapServerOutput: struct { + ConveyedInformation string `json:"conveyed-information"` + }{ + ConveyedInformation: "{wrongBASE64}", + }, + } + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + user, pass, _ := r.BasicAuth() + log.Println(user, pass) + + switch { + case (user + ":" + pass) == "USER:PASS": + w.WriteHeader(200) + output, _ = json.Marshal(expected) + case (user + ":" + pass) == "KOBASE64:KO": + w.WriteHeader(200) + output, _ = json.Marshal(expectedFailedBase64) + default: + w.WriteHeader(400) + output, _ = json.Marshal(expected) + } + + _, err := fmt.Fprint(w, string(output)) + if err != nil { + return + } + })) + defer svr.Close() + type fields struct { + BootstrapURL string + SerialNumber string + DevicePassword string + DevicePrivateKey string + DeviceEndEntityCert string + BootstrapTrustAnchorCert string + ContentTypeReq string + InputJSONContent string + DhcpLeaseFile string + ProgressJSON ProgressJSON + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + { + name: "OK", + fields: fields{ + BootstrapURL: svr.URL, + SerialNumber: "USER", + DevicePassword: "PASS", + DevicePrivateKey: "PRIVATEKEY", + DeviceEndEntityCert: "ENDENTITYCERT", + BootstrapTrustAnchorCert: "TRUSTANCHORCERT", + ContentTypeReq: "application/vnd.ietf.sztp.bootstrap-server+json", + InputJSONContent: "INPUTJSON", + DhcpLeaseFile: "DHCPLEASEFILE", + ProgressJSON: ProgressJSON{}, + }, + wantErr: false, + }, + { + name: "KO", + fields: fields{ + BootstrapURL: svr.URL, + SerialNumber: "USER", + DevicePassword: "PASSWORDWRONG", + DevicePrivateKey: "PRIVATEKEY", + DeviceEndEntityCert: "ENDENTITYCERT", + BootstrapTrustAnchorCert: "TRUSTANCHORCERT", + ContentTypeReq: "application/vnd.ietf.sztp.bootstrap-server+json", + InputJSONContent: "INPUTJSON", + DhcpLeaseFile: "DHCPLEASEFILE", + ProgressJSON: ProgressJSON{}, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &Agent{ + BootstrapURL: tt.fields.BootstrapURL, + SerialNumber: tt.fields.SerialNumber, + DevicePassword: tt.fields.DevicePassword, + DevicePrivateKey: tt.fields.DevicePrivateKey, + DeviceEndEntityCert: tt.fields.DeviceEndEntityCert, + BootstrapTrustAnchorCert: tt.fields.BootstrapTrustAnchorCert, + ContentTypeReq: tt.fields.ContentTypeReq, + InputJSONContent: tt.fields.InputJSONContent, + DhcpLeaseFile: tt.fields.DhcpLeaseFile, + ProgressJSON: tt.fields.ProgressJSON, + } + if err := a.doReportProgress(ProgressTypeBootstrapInitiated, "Bootstrap Initiated"); (err != nil) != tt.wantErr { + t.Errorf("doReportProgress() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +}