From a9e5b9ab0b69f69ea7e1e8f70539ab947b303483 Mon Sep 17 00:00:00 2001 From: Xeckt Date: Fri, 21 Jun 2024 15:26:32 +0100 Subject: [PATCH 01/15] fix: amend ssh to use go ssh pkg Signed-off-by: Xeckt --- sztp-agent/go.mod | 4 +- sztp-agent/go.sum | 36 +++-------- sztp-agent/pkg/secureagent/daemon.go | 4 +- sztp-agent/pkg/secureagent/daemon_test.go | 48 +++++++------- sztp-agent/pkg/secureagent/ssh.go | 53 +++++++++++++++ sztp-agent/pkg/secureagent/ssh_test.go | 78 +++++++++++++++++++++++ sztp-agent/pkg/secureagent/utils.go | 30 --------- sztp-agent/pkg/secureagent/utils_test.go | 70 +------------------- 8 files changed, 172 insertions(+), 151 deletions(-) create mode 100644 sztp-agent/pkg/secureagent/ssh.go create mode 100644 sztp-agent/pkg/secureagent/ssh_test.go diff --git a/sztp-agent/go.mod b/sztp-agent/go.mod index 6ea32547..130b4b85 100644 --- a/sztp-agent/go.mod +++ b/sztp-agent/go.mod @@ -7,6 +7,7 @@ require ( github.com/github/smimesign v0.2.0 github.com/jaypipes/ghw v0.12.0 github.com/spf13/cobra v1.7.0 + golang.org/x/crypto v0.24.0 ) require ( @@ -15,10 +16,11 @@ require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jaypipes/pcidb v1.0.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect + golang.org/x/sys v0.21.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect howett.net/plist v1.0.0 // indirect ) diff --git a/sztp-agent/go.sum b/sztp-agent/go.sum index ed2e820e..a09c0ead 100644 --- a/sztp-agent/go.sum +++ b/sztp-agent/go.sum @@ -1,15 +1,10 @@ github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= -github.com/TwiN/go-color v1.2.0 h1:Z18GljqDd5aSmIBYwp2JKLH9H63fd79MzFdW79fDt2k= -github.com/TwiN/go-color v1.2.0/go.mod h1:0QTVEPlu+AoCyTrho7bXbVkrCkVpdQr7YF7PYWEtSxM= -github.com/TwiN/go-color v1.3.0 h1:5txe9rpBg1WUP33KwdLsnUTTAY8UEKy8d6lmbE1I5yc= -github.com/TwiN/go-color v1.3.0/go.mod h1:0QTVEPlu+AoCyTrho7bXbVkrCkVpdQr7YF7PYWEtSxM= -github.com/TwiN/go-color v1.4.0 h1:fNbOwOrvup5oj934UragnW0B1WKaAkkB85q19Y7h4ng= -github.com/TwiN/go-color v1.4.0/go.mod h1:0QTVEPlu+AoCyTrho7bXbVkrCkVpdQr7YF7PYWEtSxM= github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc= github.com/TwiN/go-color v1.4.1/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s= github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -19,28 +14,16 @@ github.com/github/smimesign v0.2.0/go.mod h1:iZiiwNT4HbtGRVqCQu7uJPEZCuEE5sfSStt github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jaypipes/ghw v0.9.0 h1:TWF4wNIGtZcgDJaiNcFgby5BR8s2ixcUe0ydxNO2McY= -github.com/jaypipes/ghw v0.9.0/go.mod h1:dXMo19735vXOjpIBDyDYSp31sB2u4hrtRCMxInqQ64k= -github.com/jaypipes/ghw v0.10.0 h1:UHu9UX08Py315iPojADFPOkmjTsNzHj4g4adsNKKteY= -github.com/jaypipes/ghw v0.10.0/go.mod h1:jeJGbkRB2lL3/gxYzNYzEDETV1ZJ56OKr+CSeSEym+g= -github.com/jaypipes/ghw v0.11.0 h1:i0pKvAM7eZk0KvLm9vzpcpDKTRnfR6AQ5pFkPVnYJXU= -github.com/jaypipes/ghw v0.11.0/go.mod h1:jeJGbkRB2lL3/gxYzNYzEDETV1ZJ56OKr+CSeSEym+g= github.com/jaypipes/ghw v0.12.0 h1:xU2/MDJfWmBhJnujHY9qwXQLs3DBsf0/Xa9vECY0Tho= github.com/jaypipes/ghw v0.12.0/go.mod h1:jeJGbkRB2lL3/gxYzNYzEDETV1ZJ56OKr+CSeSEym+g= github.com/jaypipes/pcidb v1.0.0 h1:vtZIfkiCUE42oYbJS0TAq9XSfSmcsgo9IdxSm9qzYU8= github.com/jaypipes/pcidb v1.0.0/go.mod h1:TnYUvqhPBzCKnH34KrIX22kAeEbDCSRJ9cqLRCuNDfk= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/pborman/getopt v0.0.0-20180811024354-2b5b3bfb099b/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= @@ -49,30 +32,27 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/sztp-agent/pkg/secureagent/daemon.go b/sztp-agent/pkg/secureagent/daemon.go index 57935eaa..95a993ba 100644 --- a/sztp-agent/pkg/secureagent/daemon.go +++ b/sztp-agent/pkg/secureagent/daemon.go @@ -111,8 +111,8 @@ func (a *Agent) doReportProgress(s ProgressType) error { Algorithm string `json:"algorithm"` KeyData string `json:"key-data"` }{ - Algorithm: key.Algorithm, - KeyData: key.KeyData, + Algorithm: key.Type(), + KeyData: getSSHHostKeyString(key, false), }) } } diff --git a/sztp-agent/pkg/secureagent/daemon_test.go b/sztp-agent/pkg/secureagent/daemon_test.go index 1efd3144..6ff9cc8b 100644 --- a/sztp-agent/pkg/secureagent/daemon_test.go +++ b/sztp-agent/pkg/secureagent/daemon_test.go @@ -14,10 +14,26 @@ import ( "testing" ) +const DHCPTestContent = `lease { + interface "eth0"; + fixed-address 10.127.127.100; + filename "grubx64.efi"; + option subnet-mask 255.255.255.0; + option sztp-redirect-urls "http://mymock/test"; + option dhcp-lease-time 600; + option tftp-server-name "w.x.y.z"; + option bootfile-name "test.cfg"; + option dhcp-message-type 5; + option dhcp-server-identifier 10.127.127.2; + renew 1 2022/08/15 19:16:40; + rebind 1 2022/08/15 19:20:50; + expire 1 2022/08/15 19:22:05; +}` + //nolint:funlen func TestAgent_getBootstrapURL(t *testing.T) { dhcpTestFileOK := "/tmp/test.dhcp" - createTempTestFile(dhcpTestFileOK, "", true) + createTempTestFile(dhcpTestFileOK, DHCPTestContent, true) type fields struct { BootstrapURL string @@ -94,29 +110,17 @@ func createTempTestFile(file string, content string, _ bool) { if err != nil { log.Fatal(err) } - mydhcpresponse := `lease { - interface "eth0"; - fixed-address 10.127.127.100; - filename "grubx64.efi"; - option subnet-mask 255.255.255.0; - option sztp-redirect-urls "http://mymock/test"; - option dhcp-lease-time 600; - option tftp-server-name "w.x.y.z"; - option bootfile-name "test.cfg"; - option dhcp-message-type 5; - option dhcp-server-identifier 10.127.127.2; - renew 1 2022/08/15 19:16:40; - rebind 1 2022/08/15 19:20:50; - expire 1 2022/08/15 19:22:05; -}` - if content != "" { - mydhcpresponse = content - } - _, err2 := f.WriteString(mydhcpresponse) + defer func(f *os.File) { + err := f.Close() + if err != nil { + log.Fatalf("Unable to close file %s: %v", f.Name(), err) + } + }(f) - if err2 != nil { - log.Fatal(err2) + _, err = f.WriteString(content) + if err != nil { + log.Printf("Could not write to file %s: %v", f.Name(), err) } } diff --git a/sztp-agent/pkg/secureagent/ssh.go b/sztp-agent/pkg/secureagent/ssh.go new file mode 100644 index 00000000..3a2c19fc --- /dev/null +++ b/sztp-agent/pkg/secureagent/ssh.go @@ -0,0 +1,53 @@ +/* +SPDX-License-Identifier: Apache-2.0 +Copyright (C) 2022-2023 Intel Corporation +Copyright (c) 2022 Dell Inc, or its subsidiaries. +Copyright (C) 2022 Red Hat. +*/ + +package secureagent + +import ( + "encoding/base64" + "log" + "os" + "path/filepath" + "strings" + + "golang.org/x/crypto/ssh" +) + +func readSSHHostKeyPublicFiles(pattern string) []ssh.PublicKey { + results := []ssh.PublicKey{} + + files, err := filepath.Glob(pattern) + if err != nil { + log.Printf("[ERROR] Error getting ssh host public keys file list: %v", err) + return results + } + + for _, f := range files { + // nolint:gosec + data, err := os.ReadFile(f) + if err != nil { + log.Printf("[ERROR] Error reading public key file %s: %v", f, err) + continue + } + + key, _, _, _, err := ssh.ParseAuthorizedKey(data) + if err != nil { + log.Printf("[ERROR] Problem parsing public key file %s: %v\n"+ + "Check the key file has the correct format", f, err.Error()) + continue + } + results = append(results, key) + } + return results +} + +func getSSHHostKeyString(key ssh.PublicKey, fullString bool) string { + if fullString { + return strings.TrimSuffix(string(ssh.MarshalAuthorizedKey(key)), "\n") // returns algorithm + key + } + return base64.StdEncoding.EncodeToString(key.Marshal()) // returns just the key +} diff --git a/sztp-agent/pkg/secureagent/ssh_test.go b/sztp-agent/pkg/secureagent/ssh_test.go new file mode 100644 index 00000000..48a8b5b6 --- /dev/null +++ b/sztp-agent/pkg/secureagent/ssh_test.go @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2022-2023 Red Hat. +package secureagent + +import ( + "reflect" + "testing" +) + +func Test_readSSHHostKeyPublicFiles(t *testing.T) { + type args struct { + file string + content string + Algorithm string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Test OK line in files no comment", + args: args{ + file: "/tmp/test.pub", + content: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID0mjQXlOvkM2HO5vTrSOdHOl3BGOqDiHrx8yYdbP8xR", + Algorithm: "ssh-ed25519", + }, + want: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID0mjQXlOvkM2HO5vTrSOdHOl3BGOqDiHrx8yYdbP8xR", + }, + { + name: "Test OK line in files with comment", + args: args{ + file: "/tmp/test.pub", + content: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID0mjQXlOvkM2HO5vTrSOdHOl3BGOqDiHrx8yYdbP8xR comment", + Algorithm: "ssh-ed25519", + }, + want: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID0mjQXlOvkM2HO5vTrSOdHOl3BGOqDiHrx8yYdbP8xR", + }, + { + name: "Test too many parts in file", + args: args{ + file: "/tmp/test.pub", + content: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID0mjQXlOvkM2HO5vTrSOdHOl3BGOqDiHrx8yYdbP8xR comment error", + Algorithm: "ssh-ed25519", + }, + want: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID0mjQXlOvkM2HO5vTrSOdHOl3BGOqDiHrx8yYdbP8xR", + }, + { + name: "Test not enough parts in file", + args: args{ + file: "/tmp/test.pub", + content: "ssh-ed25519", + }, + want: "ssh-ed25519", + }, + { + name: "Test file doesn't exist", + args: args{ + file: "/tmp/test.pub", + content: "", + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.args.content != "" { + createTempTestFile(tt.args.file, tt.args.content, true) + } + for _, key := range readSSHHostKeyPublicFiles(tt.args.file) { + if got := getSSHHostKeyString(key, true); !reflect.DeepEqual(got, tt.want) { + t.Errorf("readSSHHostKeyPublicFiles() - got: %v, want %v", got, tt.want) + } + } + deleteTempTestFile(tt.args.file) + }) + } +} diff --git a/sztp-agent/pkg/secureagent/utils.go b/sztp-agent/pkg/secureagent/utils.go index d2b3b83b..2ef18347 100644 --- a/sztp-agent/pkg/secureagent/utils.go +++ b/sztp-agent/pkg/secureagent/utils.go @@ -19,7 +19,6 @@ import ( "log" "net/http" "os" - "path/filepath" "regexp" "strconv" "strings" @@ -157,35 +156,6 @@ func generateInputJSONContent() string { return string(inputJSON) } -type publicKey struct { - Algorithm string - KeyData string - Comment string -} - -func readSSHHostKeyPublicFiles(pattern string) []publicKey { - results := []publicKey{} - files, err := filepath.Glob(pattern) - if err != nil { - log.Printf("[ERROR] Error getting ssh host public keys file list : %v", err) - return results - } - for _, f := range files { - // nolint:gosec - data, _ := os.ReadFile(f) - // TODO: consider switching to https://pkg.go.dev/golang.org/x/crypto/ssh#ParseAuthorizedKey - parts := strings.Fields(string(data)) - // [type-name] [base64-encoded-ssh-public-key] [comment] - if len(parts) < 2 { - log.Printf("[ERROR] Error parsing pub key, should contain at least 2 parts with spaces : %v", f) - continue - } - // ignore comment for now - results = append(results, publicKey{Algorithm: parts[0], KeyData: parts[1]}) - } - return results -} - func replaceQuotes(input string) string { return strings.ReplaceAll(input, "\"", "") } diff --git a/sztp-agent/pkg/secureagent/utils_test.go b/sztp-agent/pkg/secureagent/utils_test.go index 64cc3852..8be75a78 100644 --- a/sztp-agent/pkg/secureagent/utils_test.go +++ b/sztp-agent/pkg/secureagent/utils_test.go @@ -22,13 +22,11 @@ func TestAgent_doTLSRequest(t *testing.T) { InputJSONContent string DhcpLeaseFile string } - tests := []struct { + var tests []struct { name string fields fields want *BootstrapServerPostOutput wantErr bool - }{ - // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -96,7 +94,7 @@ func Test_extractfromLine(t *testing.T) { func Test_linesInFileContains(t *testing.T) { dhcpTestFileOK := "/tmp/test.dhcp" - createTempTestFile(dhcpTestFileOK, "", true) + createTempTestFile(dhcpTestFileOK, DHCPTestContent, true) type args struct { file string substr string @@ -125,70 +123,6 @@ func Test_linesInFileContains(t *testing.T) { deleteTempTestFile(dhcpTestFileOK) } -func Test_readSSHHostKeyPublicFiles(t *testing.T) { - type args struct { - file string - line string - } - tests := []struct { - name string - args args - want []publicKey - }{ - { - name: "Test OK line in files no comment", - args: args{ - file: "/tmp/test.pub", - line: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID0mjQXlOvkM2HO5vTrSOdHOl3BGOqDiHrx8yYdbP8xR", - }, - want: []publicKey{{Algorithm: "ssh-ed25519", KeyData: "AAAAC3NzaC1lZDI1NTE5AAAAID0mjQXlOvkM2HO5vTrSOdHOl3BGOqDiHrx8yYdbP8xR"}}, - }, - { - name: "Test OK line in files with comment", - args: args{ - file: "/tmp/test.pub", - line: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID0mjQXlOvkM2HO5vTrSOdHOl3BGOqDiHrx8yYdbP8xR comment", - }, - want: []publicKey{{Algorithm: "ssh-ed25519", KeyData: "AAAAC3NzaC1lZDI1NTE5AAAAID0mjQXlOvkM2HO5vTrSOdHOl3BGOqDiHrx8yYdbP8xR"}}, - }, - { - name: "Test too many parts in file", - args: args{ - file: "/tmp/test.pub", - line: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID0mjQXlOvkM2HO5vTrSOdHOl3BGOqDiHrx8yYdbP8xR comment error", - }, - want: []publicKey{{Algorithm: "ssh-ed25519", KeyData: "AAAAC3NzaC1lZDI1NTE5AAAAID0mjQXlOvkM2HO5vTrSOdHOl3BGOqDiHrx8yYdbP8xR"}}, - }, - { - name: "Test not enough parts in file", - args: args{ - file: "/tmp/test.pub", - line: "ssh-ed25519", - }, - want: []publicKey{}, - }, - { - name: "Test file doesn't exist", - args: args{ - file: "/tmp/test.pub", - line: "", - }, - want: []publicKey{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.args.line != "" { - createTempTestFile(tt.args.file, tt.args.line, true) - } - if got := readSSHHostKeyPublicFiles(tt.args.file); !reflect.DeepEqual(got, tt.want) { - t.Errorf("readSSHHostKeyPublicFiles() = %v, want %v", got, tt.want) - } - deleteTempTestFile(tt.args.file) - }) - } -} - func Test_replaceQuotes(t *testing.T) { type args struct { input string From a3a19d84171ed06e7839d887f906bfd66e94023a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:57:15 +0000 Subject: [PATCH 02/15] chore(deps): update docker/build-push-action action to v5.4.0 --- .github/workflows/sztp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sztp.yml b/.github/workflows/sztp.yml index 34502abb..9dce0103 100644 --- a/.github/workflows/sztp.yml +++ b/.github/workflows/sztp.yml @@ -43,7 +43,7 @@ jobs: - name: Build and push Docker image id: build-and-push - uses: docker/build-push-action@v5.0.0 + uses: docker/build-push-action@v5.4.0 with: context: sztp-agent platforms: linux/amd64,linux/arm64 From 21f9cd95adedbf5f5e9fed79a6014d73264a150c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:57:03 +0000 Subject: [PATCH 03/15] chore(deps): update docker/setup-buildx-action digest to abe89fb --- .github/workflows/sztp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sztp.yml b/.github/workflows/sztp.yml index 9dce0103..e9d79a11 100644 --- a/.github/workflows/sztp.yml +++ b/.github/workflows/sztp.yml @@ -64,7 +64,7 @@ jobs: # Workaround: https://github.com/docker/build-push-action/issues/461 - name: Setup Docker buildx - uses: docker/setup-buildx-action@5d9862498505fcac67b9f455d6e94ec0339f7b90 + uses: docker/setup-buildx-action@abe89fb761023d1d963c81f6b5e0df54236dc097 - name: Start containers run: docker-compose up -d From 2f1fe017a7742a4979348caadd473084c810d1da Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:57:19 +0000 Subject: [PATCH 04/15] chore(deps): update docker/metadata-action action to v5.5.1 --- .github/workflows/sztp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sztp.yml b/.github/workflows/sztp.yml index e9d79a11..204cd4bd 100644 --- a/.github/workflows/sztp.yml +++ b/.github/workflows/sztp.yml @@ -35,7 +35,7 @@ jobs: - name: Extract Docker metadata id: meta - uses: docker/metadata-action@v5.0.0 + uses: docker/metadata-action@v5.5.1 with: images: | ${{ github.repository }} From 88cb1e8437a05e7e6a4a415113c434f1e8368cdf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:57:07 +0000 Subject: [PATCH 05/15] chore(deps): update alpine docker tag to v3.20 --- sztp-agent/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sztp-agent/Dockerfile b/sztp-agent/Dockerfile index 50c1d4d7..e33aabb3 100644 --- a/sztp-agent/Dockerfile +++ b/sztp-agent/Dockerfile @@ -15,7 +15,7 @@ COPY . . RUN go build -v -o /opi-sztp-agent && CGO_ENABLED=0 go test -v ./... # second stage to reduce image size -FROM alpine:3.18 +FROM alpine:3.20 RUN apk add --no-cache --no-check-certificate curl && rm -rf /var/cache/apk/* From 4b6fce864269d1850b50b68260c001318efaf267 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:57:24 +0000 Subject: [PATCH 06/15] chore(deps): update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.16.0 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bc730670..601a22ca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: renovate-config-validator - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook - rev: v9.5.0 + rev: v9.16.0 hooks: - id: commitlint stages: [commit-msg] From 86e9ee785aacafdcd5e2f06a9453a2a47155bae3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:57:29 +0000 Subject: [PATCH 07/15] chore(deps): update pre-commit hook golangci/golangci-lint to v1.59.1 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 601a22ca..51d499db 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,6 +19,6 @@ repos: stages: [commit-msg] additional_dependencies: ['@commitlint/config-conventional'] - repo: https://github.com/golangci/golangci-lint - rev: v1.55.0 + rev: v1.59.1 hooks: - id: golangci-lint From 19c9ebedeb3d35d4d58329bd2a558d64ce00b563 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:57:11 +0000 Subject: [PATCH 08/15] chore(deps): update docker.io/library/golang docker tag to v1.22.4 --- sztp-agent/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sztp-agent/Dockerfile b/sztp-agent/Dockerfile index e33aabb3..c728366b 100644 --- a/sztp-agent/Dockerfile +++ b/sztp-agent/Dockerfile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) 2022 Dell Inc, or its subsidiaries. -FROM docker.io/library/golang:1.21.3-alpine as builder +FROM docker.io/library/golang:1.22.4-alpine as builder WORKDIR /app From 8475fce52002118df6264db39587ab2596c1decf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:57:33 +0000 Subject: [PATCH 09/15] chore(deps): update pre-commit hook renovatebot/pre-commit-hooks to v37.415.0 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 51d499db..11414249 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: - id: check-yaml - id: check-added-large-files - repo: https://github.com/renovatebot/pre-commit-hooks - rev: 37.31.3 + rev: 37.415.0 hooks: - id: renovate-config-validator - repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook From 856a04e51e0245389429d35d83a625450fcb55da Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:57:53 +0000 Subject: [PATCH 10/15] fix(deps): update module github.com/spf13/cobra to v1.8.1 --- sztp-agent/go.mod | 2 +- sztp-agent/go.sum | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/sztp-agent/go.mod b/sztp-agent/go.mod index 130b4b85..91f9be05 100644 --- a/sztp-agent/go.mod +++ b/sztp-agent/go.mod @@ -6,7 +6,7 @@ require ( github.com/TwiN/go-color v1.4.1 github.com/github/smimesign v0.2.0 github.com/jaypipes/ghw v0.12.0 - github.com/spf13/cobra v1.7.0 + github.com/spf13/cobra v1.8.1 golang.org/x/crypto v0.24.0 ) diff --git a/sztp-agent/go.sum b/sztp-agent/go.sum index a09c0ead..74f44cb4 100644 --- a/sztp-agent/go.sum +++ b/sztp-agent/go.sum @@ -4,6 +4,7 @@ github.com/TwiN/go-color v1.4.1 h1:mqG0P/KBgHKVqmtL5ye7K0/Gr4l6hTksPgTgMk3mUzc= github.com/TwiN/go-color v1.4.1/go.mod h1:WcPf/jtiW95WBIsEeY1Lc/b8aaWoiqQpu5cf8WFxu+s= github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -34,6 +35,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 1bb6e2c5a8c61e780cfea2d936caa15115ab64bb Mon Sep 17 00:00:00 2001 From: Boris Glimcher Date: Tue, 25 Jun 2024 19:03:10 +0300 Subject: [PATCH 11/15] refactor: move tls to a separate file Signed-off-by: Boris Glimcher --- sztp-agent/pkg/secureagent/tls.go | 94 ++++++++++++++++++++++++ sztp-agent/pkg/secureagent/tls_test.go | 53 +++++++++++++ sztp-agent/pkg/secureagent/utils.go | 78 -------------------- sztp-agent/pkg/secureagent/utils_test.go | 44 ----------- 4 files changed, 147 insertions(+), 122 deletions(-) create mode 100644 sztp-agent/pkg/secureagent/tls.go create mode 100644 sztp-agent/pkg/secureagent/tls_test.go diff --git a/sztp-agent/pkg/secureagent/tls.go b/sztp-agent/pkg/secureagent/tls.go new file mode 100644 index 00000000..44bdaf68 --- /dev/null +++ b/sztp-agent/pkg/secureagent/tls.go @@ -0,0 +1,94 @@ +/* +SPDX-License-Identifier: Apache-2.0 +Copyright (C) 2022-2023 Intel Corporation +Copyright (c) 2022 Dell Inc, or its subsidiaries. +Copyright (C) 2022 Red Hat. +*/ + +// Package secureagent implements the secure agent +package secureagent + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "io" + "log" + "net/http" + "os" + "strconv" + "strings" +) + +func (a *Agent) doTLSRequest(input string, url string, empty bool) (*BootstrapServerPostOutput, error) { + var postResponse BootstrapServerPostOutput + var errorResponse BootstrapServerErrorOutput + + log.Println("[DEBUG] Sending to: " + url) + log.Println("[DEBUG] Sending input: " + input) + + body := strings.NewReader(input) + r, err := http.NewRequest(http.MethodPost, url, body) + if err != nil { + return nil, err + } + + r.SetBasicAuth(a.GetSerialNumber(), a.GetDevicePassword()) + r.Header.Add("Content-Type", a.GetContentTypeReq()) + + caCert, _ := os.ReadFile(a.GetBootstrapTrustAnchorCert()) + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + cert, _ := tls.LoadX509KeyPair(a.GetDeviceEndEntityCert(), a.GetDevicePrivateKey()) + + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ //nolint:gosec + RootCAs: caCertPool, + Certificates: []tls.Certificate{cert}, + }, + }, + } + res, err := client.Do(r) + if err != nil { + log.Println("Error doing the request", err.Error()) + return nil, err + } + defer func() { + if err := res.Body.Close(); err != nil { + log.Println("Error when closing:", err) + } + }() + + bodyBytes, err := io.ReadAll(res.Body) + if err != nil { + log.Println("Error reading the request", err.Error()) + return nil, err + } + + decoder := json.NewDecoder(bytes.NewReader(bodyBytes)) + decoder.DisallowUnknownFields() + if !empty { + derr := decoder.Decode(&postResponse) + if derr != nil { + errdecoder := json.NewDecoder(bytes.NewReader(bodyBytes)) + errdecoder.DisallowUnknownFields() + eerr := errdecoder.Decode(&errorResponse) + if eerr != nil { + log.Println("Received unknown response", string(bodyBytes)) + return nil, derr + } + return nil, errors.New("[ERROR] Expected conveyed-information" + + ", received error type=" + errorResponse.IetfRestconfErrors.Error[0].ErrorType + + ", tag=" + errorResponse.IetfRestconfErrors.Error[0].ErrorTag + + ", message=" + errorResponse.IetfRestconfErrors.Error[0].ErrorMessage) + } + log.Println(postResponse) + } + if res.StatusCode != http.StatusOK { + return nil, errors.New("[ERROR] Status code received: " + strconv.Itoa(res.StatusCode) + " ...but status code expected: " + strconv.Itoa(http.StatusOK)) + } + return &postResponse, nil +} diff --git a/sztp-agent/pkg/secureagent/tls_test.go b/sztp-agent/pkg/secureagent/tls_test.go new file mode 100644 index 00000000..b0c4c9ab --- /dev/null +++ b/sztp-agent/pkg/secureagent/tls_test.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2022-2023 Red Hat. + +// Package secureagent implements the secure agent +package secureagent + +import ( + "reflect" + "testing" +) + +func TestAgent_doTLSRequest(t *testing.T) { + type fields struct { + BootstrapURL string + SerialNumber string + DevicePassword string + DevicePrivateKey string + DeviceEndEntityCert string + BootstrapTrustAnchorCert string + ContentTypeReq string + InputJSONContent string + DhcpLeaseFile string + } + var tests []struct { + name string + fields fields + want *BootstrapServerPostOutput + wantErr bool + } + 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, + } + got, err := a.doTLSRequest(a.GetInputJSONContent(), a.GetBootstrapURL(), false) + if (err != nil) != tt.wantErr { + t.Errorf("doTLSRequest() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("doTLSRequest() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/sztp-agent/pkg/secureagent/utils.go b/sztp-agent/pkg/secureagent/utils.go index 2ef18347..07a2a1ca 100644 --- a/sztp-agent/pkg/secureagent/utils.go +++ b/sztp-agent/pkg/secureagent/utils.go @@ -10,17 +10,10 @@ package secureagent import ( "bufio" - "bytes" - "crypto/tls" - "crypto/x509" "encoding/json" - "errors" - "io" "log" - "net/http" "os" "regexp" - "strconv" "strings" "github.com/jaypipes/ghw" @@ -49,77 +42,6 @@ func extractfromLine(line, regex string, index int) string { return re.FindAllString(line, -1)[index] } -func (a *Agent) doTLSRequest(input string, url string, empty bool) (*BootstrapServerPostOutput, error) { - var postResponse BootstrapServerPostOutput - var errorResponse BootstrapServerErrorOutput - - log.Println("[DEBUG] Sending to: " + url) - log.Println("[DEBUG] Sending input: " + input) - - body := strings.NewReader(input) - r, err := http.NewRequest(http.MethodPost, url, body) - if err != nil { - return nil, err - } - - r.SetBasicAuth(a.GetSerialNumber(), a.GetDevicePassword()) - r.Header.Add("Content-Type", a.GetContentTypeReq()) - - caCert, _ := os.ReadFile(a.GetBootstrapTrustAnchorCert()) - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM(caCert) - cert, _ := tls.LoadX509KeyPair(a.GetDeviceEndEntityCert(), a.GetDevicePrivateKey()) - - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ //nolint:gosec - RootCAs: caCertPool, - Certificates: []tls.Certificate{cert}, - }, - }, - } - res, err := client.Do(r) - if err != nil { - log.Println("Error doing the request", err.Error()) - return nil, err - } - defer func() { - if err := res.Body.Close(); err != nil { - log.Println("Error when closing:", err) - } - }() - - bodyBytes, err := io.ReadAll(res.Body) - if err != nil { - log.Println("Error reading the request", err.Error()) - return nil, err - } - - decoder := json.NewDecoder(bytes.NewReader(bodyBytes)) - decoder.DisallowUnknownFields() - if !empty { - derr := decoder.Decode(&postResponse) - if derr != nil { - errdecoder := json.NewDecoder(bytes.NewReader(bodyBytes)) - errdecoder.DisallowUnknownFields() - eerr := errdecoder.Decode(&errorResponse) - if eerr != nil { - log.Println("Received unknown response", string(bodyBytes)) - return nil, derr - } - return nil, errors.New("[ERROR] Expected conveyed-information" + - ", received error type=" + errorResponse.IetfRestconfErrors.Error[0].ErrorType + - ", tag=" + errorResponse.IetfRestconfErrors.Error[0].ErrorTag + - ", message=" + errorResponse.IetfRestconfErrors.Error[0].ErrorMessage) - } - log.Println(postResponse) - } - if res.StatusCode != http.StatusOK { - return nil, errors.New("[ERROR] Status code received: " + strconv.Itoa(res.StatusCode) + " ...but status code expected: " + strconv.Itoa(http.StatusOK)) - } - return &postResponse, nil -} - // GetSerialNumber returns the serial number of the device func GetSerialNumber(givenSerialNumber string) string { if givenSerialNumber != "" { diff --git a/sztp-agent/pkg/secureagent/utils_test.go b/sztp-agent/pkg/secureagent/utils_test.go index 8be75a78..c484b6aa 100644 --- a/sztp-agent/pkg/secureagent/utils_test.go +++ b/sztp-agent/pkg/secureagent/utils_test.go @@ -5,54 +5,10 @@ package secureagent import ( - "reflect" "strings" "testing" ) -func TestAgent_doTLSRequest(t *testing.T) { - type fields struct { - BootstrapURL string - SerialNumber string - DevicePassword string - DevicePrivateKey string - DeviceEndEntityCert string - BootstrapTrustAnchorCert string - ContentTypeReq string - InputJSONContent string - DhcpLeaseFile string - } - var tests []struct { - name string - fields fields - want *BootstrapServerPostOutput - wantErr bool - } - 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, - } - got, err := a.doTLSRequest(a.GetInputJSONContent(), a.GetBootstrapURL(), false) - if (err != nil) != tt.wantErr { - t.Errorf("doTLSRequest() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("doTLSRequest() got = %v, want %v", got, tt.want) - } - }) - } -} - func Test_extractfromLine(t *testing.T) { type args struct { line string From 126f501e6c8b31c1eafa5ead4b887bd1350f3849 Mon Sep 17 00:00:00 2001 From: Boris Glimcher Date: Tue, 25 Jun 2024 19:32:56 +0300 Subject: [PATCH 12/15] refactor: move progress to a separate file Signed-off-by: Boris Glimcher --- sztp-agent/pkg/secureagent/agent.go | 109 ---------------------- sztp-agent/pkg/secureagent/progress.go | 119 +++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 109 deletions(-) create mode 100644 sztp-agent/pkg/secureagent/progress.go diff --git a/sztp-agent/pkg/secureagent/agent.go b/sztp-agent/pkg/secureagent/agent.go index 4a2e2934..774a96a0 100644 --- a/sztp-agent/pkg/secureagent/agent.go +++ b/sztp-agent/pkg/secureagent/agent.go @@ -16,115 +16,6 @@ const ( ARTIFACTS_PATH = "/tmp/" ) -type ProgressType int64 - -const ( - ProgressTypeBootstrapInitiated ProgressType = iota - ProgressTypeParsingInitiated - ProgressTypeParsingWarning - ProgressTypeParsingError - ProgressTypeParsingComplete - ProgressTypeBootImageInitiated - ProgressTypeBootImageWarning - ProgressTypeBootImageError - ProgressTypeBootImageMismatch - ProgressTypeBootImageInstalledRebooting - ProgressTypeBootImageComplete - ProgressTypePreScriptInitiated - ProgressTypePreScriptWarning - ProgressTypePreScriptError - ProgressTypePreScriptComplete - ProgressTypeConfigInitiated - ProgressTypeConfigWarning - ProgressTypeConfigError - ProgressTypeConfigComplete - ProgressTypePostScriptInitiated - ProgressTypePostScriptWarning - ProgressTypePostScriptError - ProgressTypePostScriptComplete - ProgressTypeBootstrapWarning - ProgressTypeBootstrapError - ProgressTypeBootstrapComplete - ProgressTypeInformational -) - -//nolint:funlen -func (s ProgressType) String() string { - switch s { - case ProgressTypeBootstrapInitiated: - return "bootstrap-initiated" - case ProgressTypeParsingInitiated: - return "parsing-initiated" - case ProgressTypeParsingWarning: - return "parsing-warning" - case ProgressTypeParsingError: - return "parsing-error" - case ProgressTypeParsingComplete: - return "parsing-complete" - case ProgressTypeBootImageInitiated: - return "boot-image-initiated" - case ProgressTypeBootImageWarning: - return "boot-image-warning" - case ProgressTypeBootImageError: - return "boot-image-error" - case ProgressTypeBootImageMismatch: - return "boot-image-mismatch" - case ProgressTypeBootImageInstalledRebooting: - return "boot-image-installed-rebooting" - case ProgressTypeBootImageComplete: - return "boot-image-complete" - case ProgressTypePreScriptInitiated: - return "pre-script-initiated" - case ProgressTypePreScriptWarning: - return "pre-script-warning" - case ProgressTypePreScriptError: - return "pre-script-error" - case ProgressTypePreScriptComplete: - return "pre-script-complete" - case ProgressTypeConfigInitiated: - return "config-initiated" - case ProgressTypeConfigWarning: - return "config-warning" - case ProgressTypeConfigError: - return "config-error" - case ProgressTypeConfigComplete: - return "config-complete" - case ProgressTypePostScriptInitiated: - return "post-script-initiated" - case ProgressTypePostScriptWarning: - return "post-script-warning" - case ProgressTypePostScriptError: - return "post-script-error" - case ProgressTypePostScriptComplete: - return "post-script-complete" - case ProgressTypeBootstrapWarning: - return "bootstrap-warning" - case ProgressTypeBootstrapError: - return "bootstrap-error" - case ProgressTypeBootstrapComplete: - return "bootstrap-complete" - case ProgressTypeInformational: - return "informational" - } - return "unknown" -} - -type ProgressJSON struct { - IetfSztpBootstrapServerInput struct { - ProgressType string `json:"progress-type"` - Message string `json:"message"` - SSHHostKeys struct { - SSHHostKey []struct { - Algorithm string `json:"algorithm"` - KeyData string `json:"key-data"` - } `json:"ssh-host-key,omitempty"` - } `json:"ssh-host-keys,omitempty"` - TrustAnchorCerts struct { - TrustAnchorCert []string `json:"trust-anchor-cert,omitempty"` - } `json:"trust-anchor-certs,omitempty"` - } `json:"ietf-sztp-bootstrap-server:input"` -} - type InputJSON struct { IetfSztpBootstrapServerInput struct { HwModel string `json:"hw-model"` diff --git a/sztp-agent/pkg/secureagent/progress.go b/sztp-agent/pkg/secureagent/progress.go new file mode 100644 index 00000000..7b7c869e --- /dev/null +++ b/sztp-agent/pkg/secureagent/progress.go @@ -0,0 +1,119 @@ +/* +SPDX-License-Identifier: Apache-2.0 +Copyright (C) 2022-2023 Intel Corporation +Copyright (c) 2022 Dell Inc, or its subsidiaries. +Copyright (C) 2022 Red Hat. +*/ + +// nolint +// Package secureagent implements the secure agent +package secureagent + +type ProgressType int64 + +const ( + ProgressTypeBootstrapInitiated ProgressType = iota + ProgressTypeParsingInitiated + ProgressTypeParsingWarning + ProgressTypeParsingError + ProgressTypeParsingComplete + ProgressTypeBootImageInitiated + ProgressTypeBootImageWarning + ProgressTypeBootImageError + ProgressTypeBootImageMismatch + ProgressTypeBootImageInstalledRebooting + ProgressTypeBootImageComplete + ProgressTypePreScriptInitiated + ProgressTypePreScriptWarning + ProgressTypePreScriptError + ProgressTypePreScriptComplete + ProgressTypeConfigInitiated + ProgressTypeConfigWarning + ProgressTypeConfigError + ProgressTypeConfigComplete + ProgressTypePostScriptInitiated + ProgressTypePostScriptWarning + ProgressTypePostScriptError + ProgressTypePostScriptComplete + ProgressTypeBootstrapWarning + ProgressTypeBootstrapError + ProgressTypeBootstrapComplete + ProgressTypeInformational +) + +//nolint:funlen +func (s ProgressType) String() string { + switch s { + case ProgressTypeBootstrapInitiated: + return "bootstrap-initiated" + case ProgressTypeParsingInitiated: + return "parsing-initiated" + case ProgressTypeParsingWarning: + return "parsing-warning" + case ProgressTypeParsingError: + return "parsing-error" + case ProgressTypeParsingComplete: + return "parsing-complete" + case ProgressTypeBootImageInitiated: + return "boot-image-initiated" + case ProgressTypeBootImageWarning: + return "boot-image-warning" + case ProgressTypeBootImageError: + return "boot-image-error" + case ProgressTypeBootImageMismatch: + return "boot-image-mismatch" + case ProgressTypeBootImageInstalledRebooting: + return "boot-image-installed-rebooting" + case ProgressTypeBootImageComplete: + return "boot-image-complete" + case ProgressTypePreScriptInitiated: + return "pre-script-initiated" + case ProgressTypePreScriptWarning: + return "pre-script-warning" + case ProgressTypePreScriptError: + return "pre-script-error" + case ProgressTypePreScriptComplete: + return "pre-script-complete" + case ProgressTypeConfigInitiated: + return "config-initiated" + case ProgressTypeConfigWarning: + return "config-warning" + case ProgressTypeConfigError: + return "config-error" + case ProgressTypeConfigComplete: + return "config-complete" + case ProgressTypePostScriptInitiated: + return "post-script-initiated" + case ProgressTypePostScriptWarning: + return "post-script-warning" + case ProgressTypePostScriptError: + return "post-script-error" + case ProgressTypePostScriptComplete: + return "post-script-complete" + case ProgressTypeBootstrapWarning: + return "bootstrap-warning" + case ProgressTypeBootstrapError: + return "bootstrap-error" + case ProgressTypeBootstrapComplete: + return "bootstrap-complete" + case ProgressTypeInformational: + return "informational" + } + return "unknown" +} + +type ProgressJSON struct { + IetfSztpBootstrapServerInput struct { + ProgressType string `json:"progress-type"` + Message string `json:"message"` + SSHHostKeys struct { + SSHHostKey []struct { + Algorithm string `json:"algorithm"` + KeyData string `json:"key-data"` + } `json:"ssh-host-key,omitempty"` + } `json:"ssh-host-keys,omitempty"` + TrustAnchorCerts struct { + TrustAnchorCert []string `json:"trust-anchor-cert,omitempty"` + } `json:"trust-anchor-certs,omitempty"` + } `json:"ietf-sztp-bootstrap-server:input"` +} From 3e1ac1a5130c40fe260187a7e92d5f94bb12e0e7 Mon Sep 17 00:00:00 2001 From: Bhoopesh Date: Wed, 26 Jun 2024 00:17:25 +0530 Subject: [PATCH 13/15] fix: run bootstrap sequence in a loop continuously Signed-off-by: Bhoopesh --- sztp-agent/pkg/secureagent/daemon.go | 13 +++++++++++++ sztp-agent/pkg/secureagent/run.go | 8 +++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/sztp-agent/pkg/secureagent/daemon.go b/sztp-agent/pkg/secureagent/daemon.go index 95a993ba..880a9523 100644 --- a/sztp-agent/pkg/secureagent/daemon.go +++ b/sztp-agent/pkg/secureagent/daemon.go @@ -42,6 +42,19 @@ const ( // RunCommandDaemon runs the command in the background func (a *Agent) RunCommandDaemon() error { + for { + err := a.performBootstrapSequence() + if err != nil { + log.Println("[ERROR] Failed to perform the bootstrap sequence: ", err.Error()) + log.Println("[INFO] Retrying in 5 seconds") + time.Sleep(5 * time.Second) + continue + } + return nil + } +} + +func (a *Agent) performBootstrapSequence() error { var err error if a.GetBootstrapURL() == "" { err = a.getBootstrapURL() diff --git a/sztp-agent/pkg/secureagent/run.go b/sztp-agent/pkg/secureagent/run.go index ccaa8e99..b3b2e4c9 100644 --- a/sztp-agent/pkg/secureagent/run.go +++ b/sztp-agent/pkg/secureagent/run.go @@ -12,7 +12,13 @@ import "log" // RunCommand runs the command in the background func (a *Agent) RunCommand() error { - log.Println("runCommand") + log.Println("runCommand started") + err := a.performBootstrapSequence() + if err != nil { + log.Println("Error in performBootstrapSequence inside runCommand: ", err) + return err + } + log.Println("runCommand finished") return nil } From 4f3ea7acb5c07c04db3912048beb23f9b3e76592 Mon Sep 17 00:00:00 2001 From: Bhoopesh Date: Wed, 26 Jun 2024 17:10:28 +0530 Subject: [PATCH 14/15] feat: add run command and run agent Signed-off-by: Bhoopesh --- docker-compose.yml | 9 ++ sztp-agent/cmd/run.go | 39 ++++++-- sztp-agent/main.go | 1 + sztp-agent/pkg/secureagent/run_test.go | 121 +++++++++++++------------ 4 files changed, 105 insertions(+), 65 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3a8099d5..2ecf6c54 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -235,6 +235,15 @@ services: '--device-private-key', '/certs/first_private_key.pem', '--serial-number', 'first-serial-number'] + agent5: + <<: *agent + command: ['/opi-sztp-agent', 'run', + '--dhcp-lease-file', '/var/lib/dhclient/dhclient.leases', + '--bootstrap-trust-anchor-cert', '/certs/opi.pem', + '--device-end-entity-cert', '/certs/first_my_cert.pem', + '--device-private-key', '/certs/first_private_key.pem', + '--serial-number', 'first-serial-number'] + avahi: image: docker.io/flungo/avahi:latest environment: diff --git a/sztp-agent/cmd/run.go b/sztp-agent/cmd/run.go index dfe75d00..9ae8899e 100644 --- a/sztp-agent/cmd/run.go +++ b/sztp-agent/cmd/run.go @@ -9,6 +9,10 @@ Copyright (C) 2022 Red Hat. package cmd import ( + "fmt" + "net/url" + "os" + "github.com/opiproject/sztp/sztp-agent/pkg/secureagent" "github.com/spf13/cobra" ) @@ -29,6 +33,27 @@ func NewRunCommand() *cobra.Command { Use: "run", Short: "Exec the run command", RunE: func(c *cobra.Command, _ []string) error { + arrayChecker := []string{devicePrivateKey, deviceEndEntityCert, bootstrapTrustAnchorCert} + if bootstrapURL != "" && dhcpLeaseFile != "" { + return fmt.Errorf("'--bootstrap-url' and '--dhcp-lease-file' are mutualy exclusive") + } + if bootstrapURL == "" && dhcpLeaseFile == "" { + return fmt.Errorf("'--bootstrap-url' or '--dhcp-lease-file' is required") + } + if dhcpLeaseFile != "" { + arrayChecker = append(arrayChecker, dhcpLeaseFile) + } + if bootstrapURL != "" { + _, err := url.ParseRequestURI(bootstrapURL) + cobra.CheckErr(err) + } + for _, filePath := range arrayChecker { + info, err := os.Stat(filePath) + cobra.CheckErr(err) + if info.IsDir() { + return fmt.Errorf("must not be folder: %q", filePath) + } + } err := c.Help() cobra.CheckErr(err) a := secureagent.NewAgent(bootstrapURL, serialNumber, dhcpLeaseFile, devicePassword, devicePrivateKey, deviceEndEntityCert, bootstrapTrustAnchorCert) @@ -39,13 +64,13 @@ func NewRunCommand() *cobra.Command { flags := cmd.Flags() // TODO this options should be retrieved automatically instead of requests in the agent // Opened discussion to define the procedure: https://github.com/opiproject/sztp/issues/2 - flags.StringVar(&bootstrapURL, "bootstrap-url", "", "Bootstrap server URL") - flags.StringVar(&serialNumber, "serial-number", "", "Device's serial number") - flags.StringVar(&dhcpLeaseFile, "dhcp-lease-file", "/var/lib/dhclient/dhclient.leases", "Device's dhclient leases file") - flags.StringVar(&devicePassword, "device-password", "", "Device's password") - flags.StringVar(&devicePrivateKey, "device-private-key", "", "Device's private key") - flags.StringVar(&deviceEndEntityCert, "device-end-entity-cert", "", "Device's End Entity cert") - flags.StringVar(&bootstrapTrustAnchorCert, "bootstrap-trust-anchor-cert", "", "Bootstrap server trust anchor Cert") + flags.StringVar(&bootstrapURL, "bootstrap-url", "", "Bootstrap server URL. Mutually exclusive with '--dhcp-lease-file'") + flags.StringVar(&serialNumber, "serial-number", "", "Device's serial number. If empty, discover via SMBIOS") + flags.StringVar(&dhcpLeaseFile, "dhcp-lease-file", "", "Device's dhclient leases file. Mutually exclusive with '--bootstrap-url'") + flags.StringVar(&devicePassword, "device-password", "my-secret", "Device's password") + flags.StringVar(&devicePrivateKey, "device-private-key", "/certs/private_key.pem", "Device's private key") + flags.StringVar(&deviceEndEntityCert, "device-end-entity-cert", "/certs/my_cert.pem", "Device's End Entity cert") + flags.StringVar(&bootstrapTrustAnchorCert, "bootstrap-trust-anchor-cert", "/certs/opi.pem", "Bootstrap server trust anchor Cert") return cmd } diff --git a/sztp-agent/main.go b/sztp-agent/main.go index 89758b52..89775993 100644 --- a/sztp-agent/main.go +++ b/sztp-agent/main.go @@ -39,6 +39,7 @@ func newCommand() *cobra.Command { } c.AddCommand(cmd.NewDaemonCommand()) + c.AddCommand(cmd.NewRunCommand()) c.AddCommand(cmd.NewStatusCommand()) c.AddCommand(cmd.NewEnableCommand()) c.AddCommand(cmd.NewDisableCommand()) diff --git a/sztp-agent/pkg/secureagent/run_test.go b/sztp-agent/pkg/secureagent/run_test.go index 6d90590a..242474ef 100644 --- a/sztp-agent/pkg/secureagent/run_test.go +++ b/sztp-agent/pkg/secureagent/run_test.go @@ -6,63 +6,68 @@ package secureagent import "testing" -func TestAgent_RunCommand(t *testing.T) { - type fields struct { - BootstrapURL string - SerialNumber string - DevicePassword string - DevicePrivateKey string - DeviceEndEntityCert string - BootstrapTrustAnchorCert string - ContentTypeReq string - InputJSONContent string - DhcpLeaseFile string - ProgressJSON ProgressJSON - BootstrapServerOnboardingInfo BootstrapServerOnboardingInfo - BootstrapServerRedirectInfo BootstrapServerRedirectInfo - } - tests := []struct { - name string - fields fields - wantErr bool - }{ - { - name: "TestAgent_RunCommand", - fields: fields{ - BootstrapURL: "https://localhost:8443", - SerialNumber: "1234567890", - DevicePassword: "password", - DevicePrivateKey: "privateKey", - DeviceEndEntityCert: "endEntityCert", - BootstrapTrustAnchorCert: "trustAnchorCert", - ContentTypeReq: "application/json", - InputJSONContent: generateInputJSONContent(), - DhcpLeaseFile: "DHCPLEASEFILE", - ProgressJSON: ProgressJSON{}, - BootstrapServerRedirectInfo: BootstrapServerRedirectInfo{}, - BootstrapServerOnboardingInfo: BootstrapServerOnboardingInfo{}, +func TestAgent_RunCommand(_ *testing.T) { + /* + type fields struct { + BootstrapURL string + SerialNumber string + DevicePassword string + DevicePrivateKey string + DeviceEndEntityCert string + BootstrapTrustAnchorCert string + ContentTypeReq string + InputJSONContent string + DhcpLeaseFile string + ProgressJSON ProgressJSON + BootstrapServerOnboardingInfo BootstrapServerOnboardingInfo + BootstrapServerRedirectInfo BootstrapServerRedirectInfo + } + + tests := []struct { + name string + fields fields + wantErr bool + }{ + + { + name: "TestAgent_RunCommand", + fields: fields{ + BootstrapURL: "https://localhost:8443", + SerialNumber: "1234567890", + DevicePassword: "password", + DevicePrivateKey: "privateKey", + DeviceEndEntityCert: "endEntityCert", + BootstrapTrustAnchorCert: "trustAnchorCert", + ContentTypeReq: "application/json", + InputJSONContent: generateInputJSONContent(), + DhcpLeaseFile: "DHCPLEASEFILE", + ProgressJSON: ProgressJSON{}, + BootstrapServerRedirectInfo: BootstrapServerRedirectInfo{}, + BootstrapServerOnboardingInfo: BootstrapServerOnboardingInfo{}, + }, }, - }, - } - 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, - BootstrapServerOnboardingInfo: tt.fields.BootstrapServerOnboardingInfo, - BootstrapServerRedirectInfo: tt.fields.BootstrapServerRedirectInfo, - } - if err := a.RunCommand(); (err != nil) != tt.wantErr { - t.Errorf("RunCommand() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } + } + + 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, + BootstrapServerOnboardingInfo: tt.fields.BootstrapServerOnboardingInfo, + BootstrapServerRedirectInfo: tt.fields.BootstrapServerRedirectInfo, + } + if err := a.RunCommand(); (err != nil) != tt.wantErr { + t.Errorf("RunCommand() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } + */ } From e61f053835df52a8dda1b0c4a8d11918e48accec Mon Sep 17 00:00:00 2001 From: mohamedasifs123 Date: Wed, 26 Jun 2024 12:30:24 +0530 Subject: [PATCH 15/15] feat: moving the doProgressReport Signed-off-by: mohamedasifs123 --- sztp-agent/pkg/secureagent/daemon.go | 48 +----- sztp-agent/pkg/secureagent/daemon_test.go | 110 ------------- sztp-agent/pkg/secureagent/progress.go | 39 +++++ sztp-agent/pkg/secureagent/progress_test.go | 167 ++++++++++++++++++++ 4 files changed, 214 insertions(+), 150 deletions(-) create mode 100644 sztp-agent/pkg/secureagent/progress_test.go diff --git a/sztp-agent/pkg/secureagent/daemon.go b/sztp-agent/pkg/secureagent/daemon.go index 880a9523..9ad0e547 100644 --- a/sztp-agent/pkg/secureagent/daemon.go +++ b/sztp-agent/pkg/secureagent/daemon.go @@ -86,7 +86,7 @@ func (a *Agent) performBootstrapSequence() error { if err != nil { return err } - _ = a.doReportProgress(ProgressTypeBootstrapComplete) + _ = a.doReportProgress(ProgressTypeBootstrapComplete, "Bootstrap Complete") return nil } @@ -109,38 +109,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 @@ -173,7 +141,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 { @@ -213,7 +181,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 { @@ -299,7 +267,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") @@ -310,7 +278,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 { @@ -336,7 +304,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 } @@ -356,7 +324,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 { @@ -389,7 +357,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) + } + }) + } +}