github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/aagent/watchers/machineswatcher/machines_test.go (about)

     1  // Copyright (c) 2021-2022, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package machines
     6  
     7  import (
     8  	"crypto/ed25519"
     9  	"crypto/rand"
    10  	"encoding/base64"
    11  	"encoding/hex"
    12  	"os"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/choria-io/go-choria/aagent/model"
    17  	"github.com/golang/mock/gomock"
    18  	. "github.com/onsi/ginkgo/v2"
    19  	. "github.com/onsi/gomega"
    20  )
    21  
    22  func TestMachine(t *testing.T) {
    23  	RegisterFailHandler(Fail)
    24  	RunSpecs(t, "AAgent/Watchers/MachinesWatcher")
    25  }
    26  
    27  var _ = Describe("AAgent/Watchers/MachinesWatcher", func() {
    28  	var (
    29  		w       *Watcher
    30  		machine *model.MockMachine
    31  		mockctl *gomock.Controller
    32  		td      string
    33  		err     error
    34  	)
    35  
    36  	BeforeEach(func() {
    37  		td, err = os.MkdirTemp("", "")
    38  		Expect(err).ToNot(HaveOccurred())
    39  		mockctl = gomock.NewController(GinkgoT())
    40  
    41  		machine = model.NewMockMachine(mockctl)
    42  		machine.EXPECT().Directory().Return(td).AnyTimes()
    43  		machine.EXPECT().SignerKey().Return("").AnyTimes()
    44  
    45  		wi, err := New(machine, "machines", nil, "", "", "1m", time.Hour, map[string]any{
    46  			"data_item": "spec",
    47  		})
    48  		Expect(err).ToNot(HaveOccurred())
    49  		w = wi.(*Watcher)
    50  	})
    51  
    52  	AfterEach(func() {
    53  		mockctl.Finish()
    54  		os.RemoveAll(td)
    55  	})
    56  
    57  	Describe("loadAndValidateData", func() {
    58  		var (
    59  			data *Specification
    60  			pri  ed25519.PrivateKey
    61  			pub  ed25519.PublicKey
    62  			spec []byte
    63  		)
    64  
    65  		BeforeEach(func() {
    66  			pub, pri, err = ed25519.GenerateKey(rand.Reader)
    67  			Expect(err).ToNot(HaveOccurred())
    68  			spec = []byte("[]")
    69  			data = &Specification{
    70  				Machines: []byte(base64.StdEncoding.EncodeToString(spec)),
    71  			}
    72  			data.Signature = hex.EncodeToString(ed25519.Sign(pri, spec))
    73  			machine.EXPECT().DataGet(gomock.Eq("spec")).Return(data, true).AnyTimes()
    74  		})
    75  
    76  		It("Should function without a signature", func() {
    77  			data.Signature = ""
    78  			machine.EXPECT().SignerKey().Return("").AnyTimes()
    79  			spec, err := w.loadAndValidateData()
    80  			Expect(err).ToNot(HaveOccurred())
    81  			Expect(spec).ToNot(BeNil())
    82  		})
    83  
    84  		It("Should handle data with no signatures when signature is required", func() {
    85  			err = w.setProperties(map[string]any{
    86  				"data_item":  "spec",
    87  				"public_key": "x",
    88  			})
    89  			Expect(err).ToNot(HaveOccurred())
    90  			data.Signature = ""
    91  			machine.EXPECT().DataDelete(gomock.Eq("spec"))
    92  			machine.EXPECT().Errorf(gomock.Any(), gomock.Eq("No signature found in specification, removing data"))
    93  			spec, err := w.loadAndValidateData()
    94  			Expect(err).To(MatchError("invalid data_item"))
    95  			Expect(spec).To(BeNil())
    96  		})
    97  
    98  		It("Should handle data with corrupt signatures", func() {
    99  			err = w.setProperties(map[string]any{
   100  				"data_item":  "spec",
   101  				"public_key": hex.EncodeToString(pub),
   102  			})
   103  			Expect(err).ToNot(HaveOccurred())
   104  			data.Signature = "x"
   105  
   106  			machine.EXPECT().DataDelete(gomock.Eq("spec"))
   107  			machine.EXPECT().Errorf(gomock.Any(), gomock.Eq("invalid signature string, removing data %s: %s"), gomock.Eq("spec"), gomock.Any())
   108  			spec, err := w.loadAndValidateData()
   109  			Expect(err).To(MatchError("invalid data_item"))
   110  			Expect(spec).To(BeNil())
   111  		})
   112  
   113  		It("Should handle data with invalid signatures", func() {
   114  			err = w.setProperties(map[string]any{
   115  				"data_item":  "spec",
   116  				"public_key": hex.EncodeToString(pub),
   117  			})
   118  			Expect(err).ToNot(HaveOccurred())
   119  			data.Signature = hex.EncodeToString(ed25519.Sign(pri, []byte("wrong")))
   120  
   121  			machine.EXPECT().DataDelete(gomock.Eq("spec"))
   122  			machine.EXPECT().Errorf(gomock.Any(), gomock.Eq("Signature in data_item %s did not verify using configured public key '%s', removing data"), gomock.Eq("spec"), gomock.Eq(hex.EncodeToString(pub)))
   123  			spec, err := w.loadAndValidateData()
   124  			Expect(err).To(MatchError("invalid data_item"))
   125  			Expect(spec).To(BeNil())
   126  		})
   127  
   128  		It("Should allow overrides from config", func() {
   129  			machine := model.NewMockMachine(mockctl)
   130  			machine.EXPECT().Directory().Return(td).AnyTimes()
   131  			machine.EXPECT().SignerKey().Return(hex.EncodeToString(pub)).AnyTimes()
   132  			machine.EXPECT().DataGet(gomock.Eq("spec")).Return(data, true).AnyTimes()
   133  
   134  			wi, err := New(machine, "machines", nil, "", "", "1m", time.Hour, map[string]any{
   135  				"data_item":  "spec",
   136  				"public_key": "other",
   137  			})
   138  			Expect(err).ToNot(HaveOccurred())
   139  			w = wi.(*Watcher)
   140  
   141  			Expect(w.properties.PublicKey).To(Equal(hex.EncodeToString(pub)))
   142  
   143  			spec, err := w.loadAndValidateData()
   144  			Expect(err).ToNot(HaveOccurred())
   145  			Expect(spec).To(Equal([]byte("[]")))
   146  		})
   147  
   148  		It("Should handle valid signatures", func() {
   149  			err = w.setProperties(map[string]any{
   150  				"data_item":  "spec",
   151  				"public_key": hex.EncodeToString(pub),
   152  			})
   153  			Expect(err).ToNot(HaveOccurred())
   154  
   155  			spec, err := w.loadAndValidateData()
   156  			Expect(err).ToNot(HaveOccurred())
   157  			Expect(spec).To(Equal([]byte("[]")))
   158  		})
   159  	})
   160  })