github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/protocol/v2/secure_request_test.go (about) 1 // Copyright (c) 2022, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package v2 6 7 import ( 8 "context" 9 "fmt" 10 11 "github.com/choria-io/go-choria/inter" 12 imock "github.com/choria-io/go-choria/inter/imocks" 13 "github.com/choria-io/go-choria/protocol" 14 "github.com/golang/mock/gomock" 15 . "github.com/onsi/ginkgo/v2" 16 . "github.com/onsi/gomega" 17 "github.com/sirupsen/logrus" 18 "github.com/tidwall/gjson" 19 ) 20 21 var _ = Describe("SecureRequest", func() { 22 var mockctl *gomock.Controller 23 var security *imock.MockSecurityProvider 24 var tech inter.SecurityTechnology 25 var req protocol.Request 26 var err error 27 28 BeforeEach(func() { 29 logrus.SetLevel(logrus.FatalLevel) 30 mockctl = gomock.NewController(GinkgoT()) 31 security = imock.NewMockSecurityProvider(mockctl) 32 33 security.EXPECT().BackingTechnology().DoAndReturn(func() inter.SecurityTechnology { 34 return tech 35 }).AnyTimes() 36 37 req, err = NewRequest("ginkgo", "ginkgo.example.net", "up=ginkgo", 60, "1234", "choria") 38 Expect(err).ToNot(HaveOccurred()) 39 req.SetMessage([]byte("hello")) 40 41 tech = inter.SecurityTechnologyED25519JWT 42 protocol.Secure = "true" 43 }) 44 45 AfterEach(func() { 46 mockctl.Finish() 47 }) 48 49 Describe("NewSecureRequest", func() { 50 It("Should require the correct security technology", func() { 51 tech = inter.SecurityTechnologyX509 52 _, err := NewSecureRequest(nil, security) 53 Expect(err).To(MatchError(ErrIncorrectProtocol)) 54 }) 55 56 It("Should support insecure operation", func() { 57 protocol.Secure = "false" 58 sreq, err := NewSecureRequest(req, security) 59 Expect(err).ToNot(HaveOccurred()) 60 Expect(sreq.(*SecureRequest).CallerJWT).To(Equal("")) 61 }) 62 63 It("Should handle token lookup failures", func() { 64 security.EXPECT().TokenBytes().Return(nil, fmt.Errorf("ginkgo")) 65 66 sreq, err := NewSecureRequest(req, security) 67 Expect(err).To(MatchError("ginkgo")) 68 Expect(sreq).To(BeNil()) 69 }) 70 71 It("Should handle signing failures", func() { 72 security.EXPECT().TokenBytes().Return([]byte("token"), nil) 73 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return(nil, fmt.Errorf("stub failure")).AnyTimes() 74 75 sreq, err := NewSecureRequest(req, security) 76 Expect(err).To(MatchError("stub failure")) 77 Expect(sreq).To(BeNil()) 78 }) 79 80 It("Should produce a correct secure request", func() { 81 security.EXPECT().TokenBytes().Return([]byte("token"), nil) 82 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return([]byte("stub sig"), nil).AnyTimes() 83 84 sreq, err := NewSecureRequest(req, security) 85 Expect(err).ToNot(HaveOccurred()) 86 87 r := sreq.(*SecureRequest) 88 Expect(r.CallerJWT).To(Equal("token")) 89 Expect(r.Signature).To(Equal([]byte("stub sig"))) 90 Expect(r.SignerJWT).To(Equal("")) 91 Expect(r.MessageBody).To(ContainSubstring("io.choria.protocol.v2.request")) 92 }) 93 }) 94 95 Describe("NewRemoteSignedSecureRequest", func() { 96 It("Should require the correct security technology", func() { 97 tech = inter.SecurityTechnologyX509 98 _, err := NewRemoteSignedSecureRequest(context.Background(), nil, security) 99 Expect(err).To(MatchError(ErrIncorrectProtocol)) 100 }) 101 102 Describe("Should support insecure or remote signing operation", func() { 103 It("Should handle signing failures", func() { 104 security.EXPECT().RemoteSignRequest(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("simulated failure")) 105 _, err := NewRemoteSignedSecureRequest(context.Background(), req, security) 106 Expect(err).To(MatchError("simulated failure")) 107 }) 108 109 It("Should not call remote sign for the signing agent", func() { 110 security.EXPECT().RemoteSignRequest(gomock.Any(), gomock.Any()).Times(0) 111 112 // will call NewSecureRequest() and we have no expect on RemoteSignRequest() 113 security.EXPECT().TokenBytes().Return([]byte("token"), nil) 114 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return([]byte("stub sig"), nil).AnyTimes() 115 116 req, err = NewRequest(protocol.RemoteSigningAgent, "ginkgo.example.net", "up=ginkgo", 60, "1234", "choria") 117 Expect(err).ToNot(HaveOccurred()) 118 req.SetMessage([]byte("hello")) 119 120 _, err := NewRemoteSignedSecureRequest(context.Background(), req, security) 121 Expect(err).ToNot(HaveOccurred()) 122 }) 123 }) 124 125 It("Should check the secure request is signed by a signer", func() { 126 security.EXPECT().RemoteSignRequest(gomock.Any(), gomock.AssignableToTypeOf([]byte{})).DoAndReturn(func(_ context.Context, reqj []byte) ([]byte, error) { 127 Expect(gjson.GetBytes(reqj, "agent").String()).To(Equal("ginkgo")) 128 Expect(gjson.GetBytes(reqj, "protocol").String()).To(Equal(string(protocol.RequestV2))) 129 130 security.EXPECT().TokenBytes().Return([]byte("token"), nil) 131 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return([]byte("stub sig"), nil).AnyTimes() 132 133 signed, err := NewSecureRequest(req, security) 134 Expect(err).ToNot(HaveOccurred()) 135 136 signedj, err := signed.JSON() 137 Expect(err).ToNot(HaveOccurred()) 138 139 return signedj, nil 140 }) 141 142 sreq, err := NewRemoteSignedSecureRequest(context.Background(), req, security) 143 Expect(err).To(MatchError("remote signer did not set a signer JWT")) 144 Expect(sreq).To(BeNil()) 145 }) 146 147 It("Should produce a correct secure request", func() { 148 security.EXPECT().RemoteSignRequest(gomock.Any(), gomock.AssignableToTypeOf([]byte{})).DoAndReturn(func(_ context.Context, reqj []byte) ([]byte, error) { 149 Expect(gjson.GetBytes(reqj, "agent").String()).To(Equal("ginkgo")) 150 Expect(gjson.GetBytes(reqj, "protocol").String()).To(Equal(string(protocol.RequestV2))) 151 152 security.EXPECT().TokenBytes().Return([]byte("token"), nil).Times(2) 153 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return([]byte("stub sig"), nil).AnyTimes() 154 155 signed, err := NewSecureRequest(req, security) 156 Expect(err).ToNot(HaveOccurred()) 157 158 signed.(*SecureRequest).SignerJWT = "signer jwt" 159 signedj, err := signed.JSON() 160 Expect(err).ToNot(HaveOccurred()) 161 162 return signedj, nil 163 }) 164 165 sreq, err := NewRemoteSignedSecureRequest(context.Background(), req, security) 166 Expect(err).ToNot(HaveOccurred()) 167 Expect(sreq.(*SecureRequest).SignerJWT).To(Equal("signer jwt")) 168 }) 169 }) 170 171 Describe("NewSecureRequestFromTransport", func() { 172 It("Should require the correct security technology", func() { 173 tech = inter.SecurityTechnologyX509 174 _, err := NewSecureRequestFromTransport(nil, security, false) 175 Expect(err).To(MatchError(ErrIncorrectProtocol)) 176 }) 177 178 It("Should detect invalid payloads", func() { 179 sr, err := NewSecureRequestFromTransport(&TransportMessage{Data: []byte("{}")}, security, false) 180 Expect(err).To(MatchError(ErrInvalidJSON)) 181 Expect(sr).To(BeNil()) 182 }) 183 184 It("Should support skipping validation", func() { 185 security.EXPECT().TokenBytes().Return([]byte("token"), nil) 186 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return([]byte("stub sig"), nil).AnyTimes() 187 security.EXPECT().VerifySignatureBytes(gomock.Any(), gomock.Any(), gomock.Any()).Return(false, "").Times(0) 188 189 tsreq, err := NewSecureRequest(req, security) 190 Expect(err).ToNot(HaveOccurred()) 191 t, err := NewTransportMessage("ginkgo.example.net") 192 Expect(err).ToNot(HaveOccurred()) 193 Expect(t.SetRequestData(tsreq)).To(Succeed()) 194 195 sreq, err := NewSecureRequestFromTransport(t, security, true) 196 Expect(err).ToNot(HaveOccurred()) 197 r := sreq.(*SecureRequest) 198 Expect(r.CallerJWT).To(Equal("token")) 199 Expect(r.Signature).To(Equal([]byte("stub sig"))) 200 Expect(r.SignerJWT).To(Equal("")) 201 Expect(r.MessageBody).To(ContainSubstring("io.choria.protocol.v2.request")) 202 }) 203 204 It("Should handle validation failures", func() { 205 security.EXPECT().TokenBytes().Return([]byte("token"), nil) 206 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return([]byte("stub sig"), nil).AnyTimes() 207 security.EXPECT().VerifySignatureBytes(gomock.Any(), gomock.Any(), gomock.Any()).Return(false, "").Times(1) 208 209 tsreq, err := NewSecureRequest(req, security) 210 Expect(err).ToNot(HaveOccurred()) 211 t, err := NewTransportMessage("ginkgo.example.net") 212 Expect(err).ToNot(HaveOccurred()) 213 Expect(t.SetRequestData(tsreq)).To(Succeed()) 214 215 sreq, err := NewSecureRequestFromTransport(t, security, false) 216 Expect(err).To(MatchError("secure request messages created from Transport Message did not pass security validation")) 217 Expect(sreq).To(BeNil()) 218 }) 219 220 It("Should validate and produce a correct secure request", func() { 221 security.EXPECT().TokenBytes().Return([]byte("token"), nil) 222 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return([]byte("stub sig"), nil).AnyTimes() 223 security.EXPECT().VerifySignatureBytes(gomock.Any(), gomock.Any(), gomock.Any()).Return(true, "signer.example.net").Times(1) 224 security.EXPECT().ShouldAllowCaller(gomock.Any(), gomock.Any()).DoAndReturn(func(caller string, jwts ...[]byte) (bool, error) { 225 Expect(caller).To(Equal("up=ginkgo")) 226 return true, nil 227 }) 228 229 tsreq, err := NewSecureRequest(req, security) 230 Expect(err).ToNot(HaveOccurred()) 231 t, err := NewTransportMessage("ginkgo.example.net") 232 Expect(err).ToNot(HaveOccurred()) 233 Expect(t.SetRequestData(tsreq)).To(Succeed()) 234 235 sreq, err := NewSecureRequestFromTransport(t, security, false) 236 Expect(err).ToNot(HaveOccurred()) 237 r := sreq.(*SecureRequest) 238 Expect(r.CallerJWT).To(Equal("token")) 239 Expect(r.Signature).To(Equal([]byte("stub sig"))) 240 Expect(r.SignerJWT).To(Equal("")) 241 Expect(r.MessageBody).To(ContainSubstring("io.choria.protocol.v2.request")) 242 }) 243 }) 244 245 Describe("SetMessage", func() { 246 It("Should handle invalid requests", func() { 247 sreq := &SecureRequest{security: security} 248 err := sreq.SetMessage(&Request{}) 249 Expect(err).To(MatchError(ErrInvalidJSON)) 250 Expect(err.Error()).To(HavePrefix("could not JSON encode reply message")) 251 }) 252 253 It("Should support insecure operation", func() { 254 protocol.Secure = "false" 255 sreq := &SecureRequest{security: security} 256 err := sreq.SetMessage(req) 257 Expect(err).ToNot(HaveOccurred()) 258 Expect(sreq.Signature).To(Equal([]byte("insecure"))) 259 }) 260 261 It("Should sign the body and store it", func() { 262 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return([]byte("stub sig"), nil).AnyTimes() 263 sreq := &SecureRequest{security: security} 264 err := sreq.SetMessage(req) 265 Expect(err).ToNot(HaveOccurred()) 266 Expect(sreq.Signature).To(Equal([]byte("stub sig"))) 267 }) 268 }) 269 270 Describe("Valid", func() { 271 It("Should support insecure operation", func() { 272 protocol.Secure = "false" 273 sreq := &SecureRequest{} 274 Expect(sreq.Valid()).To(BeTrue()) 275 }) 276 277 It("Should detect signature validation failures", func() { 278 security.EXPECT().TokenBytes().Return([]byte("caller jwt"), nil).AnyTimes() 279 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return([]byte("stub sig"), nil).AnyTimes() 280 security.EXPECT().VerifySignatureBytes(gomock.Any(), gomock.Any(), gomock.Any()).Return(false, "").Times(1) 281 282 sreq, err := NewSecureRequest(req, security) 283 Expect(err).ToNot(HaveOccurred()) 284 Expect(sreq.Valid()).To(BeFalse()) 285 }) 286 287 It("Should handle disallowed callers", func() { 288 security.EXPECT().TokenBytes().Return([]byte("caller jwt"), nil).AnyTimes() 289 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return([]byte("stub sig"), nil).AnyTimes() 290 security.EXPECT().VerifySignatureBytes(gomock.Any(), gomock.Any(), gomock.Any()).Return(true, "ginkgo").Times(1) 291 security.EXPECT().ShouldAllowCaller(gomock.Any(), gomock.Any()).Return(false, fmt.Errorf("simulated failure")) 292 293 sreq, err := NewSecureRequest(req, security) 294 Expect(err).ToNot(HaveOccurred()) 295 Expect(sreq.Valid()).To(BeFalse()) 296 }) 297 298 It("Should do correct validations", func() { 299 security.EXPECT().TokenBytes().Return([]byte("caller jwt"), nil).AnyTimes() 300 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return([]byte("stub sig"), nil).AnyTimes() 301 302 security.EXPECT().VerifySignatureBytes(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(body []byte, sig []byte, public ...[]byte) (bool, string) { 303 Expect(body).To(ContainSubstring("io.choria.protocol.v2.request")) 304 Expect(body).To(ContainSubstring(`"message":"aGVsbG8="`)) 305 Expect(sig).To(Equal([]byte("stub sig"))) 306 Expect(public).To(HaveLen(2)) 307 Expect(public[0]).To(Equal([]byte("caller jwt"))) 308 Expect(public[1]).To(Equal([]byte("signer jwt"))) 309 310 return true, "ginkgo" 311 }).Times(1) 312 313 security.EXPECT().RemoteSignRequest(gomock.Any(), gomock.AssignableToTypeOf([]byte{})).DoAndReturn(func(_ context.Context, reqj []byte) ([]byte, error) { 314 signed, err := NewSecureRequest(req, security) 315 Expect(err).ToNot(HaveOccurred()) 316 317 signed.(*SecureRequest).SignerJWT = "signer jwt" 318 signedj, err := signed.JSON() 319 Expect(err).ToNot(HaveOccurred()) 320 321 return signedj, nil 322 }).AnyTimes() 323 324 security.EXPECT().ShouldAllowCaller(gomock.Any(), gomock.Any()).DoAndReturn(func(caller string, public ...[]byte) (bool, error) { 325 Expect(caller).To(Equal("up=ginkgo")) 326 Expect(public).To(HaveLen(2)) 327 Expect(public[0]).To(Equal([]byte("caller jwt"))) 328 Expect(public[1]).To(Equal([]byte("signer jwt"))) 329 330 return false, nil 331 }).Times(1) 332 333 sreq, err := NewRemoteSignedSecureRequest(context.Background(), req, security) 334 Expect(err).ToNot(HaveOccurred()) 335 Expect(sreq.Valid()).To(BeTrue()) 336 }) 337 }) 338 339 Describe("IsValidJSON", func() { 340 It("Should detect invalid JSON data", func() { 341 sr := &SecureRequest{} 342 err := sr.IsValidJSON([]byte("{}")) 343 Expect(err).To(MatchError("supplied JSON document does not pass schema validation: missing properties: 'protocol', 'request', 'signature', 'caller'")) 344 }) 345 346 It("Should accept valid JSON data", func() { 347 security.EXPECT().TokenBytes().Return([]byte("token"), nil) 348 security.EXPECT().SignBytes(gomock.AssignableToTypeOf([]byte{})).Return([]byte("stub sig"), nil).AnyTimes() 349 350 sreq, err := NewSecureRequest(req, security) 351 Expect(err).ToNot(HaveOccurred()) 352 353 j, err := sreq.JSON() 354 Expect(err).ToNot(HaveOccurred()) 355 356 Expect(sreq.IsValidJSON(j)).ToNot(HaveOccurred()) 357 }) 358 }) 359 })