github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/broker/network/choria_auth_test.go (about) 1 // Copyright (c) 2020-2023, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package network 6 7 import ( 8 "crypto/ed25519" 9 "crypto/rsa" 10 "crypto/tls" 11 "crypto/x509" 12 "encoding/base64" 13 "encoding/hex" 14 "io" 15 "net" 16 "os" 17 "path/filepath" 18 "runtime" 19 "strings" 20 "time" 21 22 "github.com/choria-io/go-choria/choria" 23 "github.com/choria-io/go-choria/integration/testutil" 24 iu "github.com/choria-io/go-choria/internal/util" 25 "github.com/choria-io/tokens" 26 "github.com/golang-jwt/jwt/v4" 27 "github.com/golang/mock/gomock" 28 "github.com/nats-io/nats-server/v2/server" 29 . "github.com/onsi/ginkgo/v2" 30 . "github.com/onsi/gomega" 31 "github.com/sirupsen/logrus" 32 ) 33 34 var _ = Describe("Network Broker/ChoriaAuth", func() { 35 var ( 36 log *logrus.Entry 37 auth *ChoriaAuth 38 user *server.User 39 mockClient *MockClientAuthentication 40 mockctl *gomock.Controller 41 ) 42 43 BeforeEach(func() { 44 logger := logrus.New() 45 logger.Out = io.Discard 46 log = logrus.NewEntry(logger) 47 auth = &ChoriaAuth{ 48 clientAllowList: []string{}, 49 log: log, 50 choriaAccount: &server.Account{Name: "choria"}, 51 issuerTokens: map[string]string{}, 52 } 53 user = &server.User{ 54 Username: "bob", 55 Password: "secret", 56 Permissions: &server.Permissions{}, 57 } 58 59 mockctl = gomock.NewController(GinkgoT()) 60 mockClient = NewMockClientAuthentication(mockctl) 61 }) 62 63 AfterEach(func() { 64 mockctl.Finish() 65 }) 66 67 createKeyPair := func() (td string, pri *rsa.PrivateKey) { 68 td, err := os.MkdirTemp("", "") 69 Expect(err).ToNot(HaveOccurred()) 70 71 pri, err = testutil.CreateRSAKeyAndCert(td) 72 Expect(err).ToNot(HaveOccurred()) 73 74 return td, pri 75 } 76 77 createSignedServerJWT := func(pk any, pubK []byte, claims map[string]any) string { 78 signed, err := testutil.CreateSignedServerJWT(pk, pubK, claims) 79 Expect(err).ToNot(HaveOccurred()) 80 81 return signed 82 } 83 84 createSignedClientJWT := func(pk any, claims map[string]any) string { 85 signed, err := testutil.CreateSignedClientJWT(pk, claims) 86 Expect(err).ToNot(HaveOccurred()) 87 88 return signed 89 } 90 91 Describe("Check", func() { 92 Describe("Provisioning user", func() { 93 BeforeEach(func() { 94 auth.isTLS = true 95 auth.provPass = "s3cret" 96 auth.provisioningAccount = &server.Account{Name: provisioningUser} 97 copts := &server.ClientOpts{Username: "provisioner", Password: "s3cret"} 98 mockClient.EXPECT().GetOpts().Return(copts).AnyTimes() 99 }) 100 101 It("Should only prov auth when tls is enabled", func() { 102 auth.isTLS = false 103 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{}) 104 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}).AnyTimes() 105 106 Expect(auth.Check(mockClient)).To(BeFalse()) 107 108 auth.isTLS = true 109 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{nil}}).AnyTimes() 110 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 111 Expect(user.Account).To(Equal(auth.provisioningAccount)) 112 }) 113 114 Expect(auth.Check(mockClient)).To(BeTrue()) 115 }) 116 117 It("Should reject provision user on a plain connection", func() { 118 auth.provisioningTokenSigner = "testdata/ssl/certs/rip.mcollective.pem" 119 120 mockClient.EXPECT().GetTLSConnectionState().Return(nil) 121 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}) 122 123 Expect(auth.Check(mockClient)).To(BeFalse()) 124 }) 125 126 It("Should not do provision auth for unverified connections", func() { 127 auth.provisioningTokenSigner = "testdata/ssl/certs/rip.mcollective.pem" 128 129 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}).AnyTimes() 130 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{}) 131 Expect(auth.Check(mockClient)).To(BeFalse()) 132 }) 133 134 It("Should verify the password correctly", func() { 135 auth.provisioningTokenSigner = "testdata/ssl/certs/rip.mcollective.pem" 136 auth.provPass = "other" 137 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}).AnyTimes() 138 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{nil}}).AnyTimes() 139 140 Expect(auth.Check(mockClient)).To(BeFalse()) 141 }) 142 143 It("Should do provision auth for verified connections", func() { 144 auth.provisioningTokenSigner = "testdata/ssl/certs/rip.mcollective.pem" 145 146 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}).AnyTimes() 147 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{nil}}).AnyTimes() 148 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 149 Expect(user.Account).To(Equal(auth.provisioningAccount)) 150 }) 151 152 Expect(auth.Check(mockClient)).To(BeTrue()) 153 }) 154 }) 155 156 Describe("system user", func() { 157 var copts *server.ClientOpts 158 159 BeforeEach(func() { 160 auth.isTLS = true 161 auth.systemAccount = &server.Account{Name: "system"} 162 auth.systemUser = "system" 163 auth.systemPass = "sysTem" 164 165 copts = &server.ClientOpts{Username: "system", Password: "sysTem"} 166 mockClient.EXPECT().GetOpts().Return(copts).AnyTimes() 167 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}).AnyTimes() 168 }) 169 170 It("Should verify the password correctly", func() { 171 auth.systemPass = "other" 172 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{nil}}).AnyTimes() 173 174 Expect(auth.Check(mockClient)).To(BeFalse()) 175 }) 176 177 It("Should register mTLS system users", func() { 178 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{nil}}).AnyTimes() 179 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 180 Expect(user.Account).To(Equal(auth.systemAccount)) 181 }) 182 183 Expect(auth.Check(mockClient)).To(BeTrue()) 184 }) 185 186 It("Should reject non mTLS system users that has no JWT", func() { 187 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{}).AnyTimes() 188 mockClient.EXPECT().RegisterUser(gomock.Any()).Times(0) 189 Expect(auth.Check(mockClient)).To(BeFalse()) 190 }) 191 192 Describe("JWT based system access", func() { 193 var ( 194 td string 195 privateKey *rsa.PrivateKey 196 edPrivateKey ed25519.PrivateKey 197 edPublicKey ed25519.PublicKey 198 err error 199 ) 200 201 BeforeEach(func() { 202 td, privateKey = createKeyPair() 203 auth.clientJwtSigners = []string{filepath.Join(td, "public.pem")} 204 edPublicKey, edPrivateKey, err = choria.Ed25519KeyPair() 205 Expect(err).ToNot(HaveOccurred()) 206 sig, err := choria.Ed25519Sign(edPrivateKey, []byte("toomanysecrets")) 207 mockClient.EXPECT().GetNonce().Return([]byte("toomanysecrets")).AnyTimes() 208 Expect(err).ToNot(HaveOccurred()) 209 copts.Sig = base64.RawURLEncoding.EncodeToString(sig) 210 mockClient.EXPECT().Kind().Return(server.CLIENT).AnyTimes() 211 }) 212 213 AfterEach(func() { 214 os.RemoveAll(td) 215 }) 216 217 It("Should reject non mTLS system users with a JWT but without the needed permissions", func() { 218 mockClient.EXPECT().RegisterUser(gomock.Any()).Times(0) 219 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{}).AnyTimes() 220 copts.Token = createSignedClientJWT(privateKey, map[string]any{ 221 "purpose": tokens.ClientIDPurpose, 222 "public_key": hex.EncodeToString(edPublicKey), 223 }) 224 225 Expect(auth.Check(mockClient)).To(BeFalse()) 226 }) 227 228 It("Should reject non mTLS system users with a JWT that does not allow system access", func() { 229 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{}).AnyTimes() 230 mockClient.EXPECT().RegisterUser(gomock.Any()).Times(0) 231 232 copts.Token = createSignedClientJWT(privateKey, map[string]any{ 233 "purpose": tokens.ClientIDPurpose, 234 "public_key": hex.EncodeToString(edPublicKey), 235 "permissions": map[string]bool{}, 236 }) 237 238 Expect(auth.Check(mockClient)).To(BeFalse()) 239 }) 240 241 It("Should only accept system users with a JWT over TLS", func() { 242 mockClient.EXPECT().GetTLSConnectionState().Return(nil).AnyTimes() 243 mockClient.EXPECT().RegisterUser(gomock.Any()).Times(0) 244 245 copts.Token = createSignedClientJWT(privateKey, map[string]any{ 246 "purpose": tokens.ClientIDPurpose, 247 "public_key": hex.EncodeToString(edPublicKey), 248 "permissions": map[string]bool{ 249 "system_user": true, 250 }, 251 }) 252 253 Expect(auth.Check(mockClient)).To(BeFalse()) 254 }) 255 256 It("Should accept non mTLS system users with a correct JWT", func() { 257 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{}).AnyTimes() 258 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 259 Expect(user.Account).To(Equal(auth.systemAccount)) 260 }) 261 262 copts.Token = createSignedClientJWT(privateKey, map[string]any{ 263 "purpose": tokens.ClientIDPurpose, 264 "public_key": hex.EncodeToString(edPublicKey), 265 "permissions": map[string]bool{ 266 "system_user": true, 267 }, 268 }) 269 270 Expect(auth.Check(mockClient)).To(BeTrue()) 271 }) 272 }) 273 }) 274 }) 275 276 Describe("handleDefaultConnection", func() { 277 var ( 278 td string 279 privateKey *rsa.PrivateKey 280 edPrivateKey ed25519.PrivateKey 281 edPublicKey ed25519.PublicKey 282 copts *server.ClientOpts 283 verifiedConn *tls.ConnectionState 284 err error 285 ) 286 287 BeforeEach(func() { 288 td, privateKey = createKeyPair() 289 auth.serverJwtSigners = []string{filepath.Join(td, "public.pem")} 290 edPublicKey, edPrivateKey, err = choria.Ed25519KeyPair() 291 Expect(err).ToNot(HaveOccurred()) 292 }) 293 294 AfterEach(func() { 295 os.RemoveAll(td) 296 }) 297 298 Describe("Servers", func() { 299 BeforeEach(func() { 300 auth.serverJwtSigners = []string{filepath.Join(td, "public.pem")} 301 auth.clientAllowList = nil 302 auth.denyServers = false 303 304 copts = &server.ClientOpts{ 305 Token: createSignedServerJWT(privateKey, edPublicKey, map[string]any{ 306 "purpose": tokens.ServerPurpose, 307 "public_key": hex.EncodeToString(edPublicKey), 308 "collectives": []string{"c1", "c2"}, 309 }), 310 } 311 verifiedConn = &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{nil}} 312 mockClient.EXPECT().GetOpts().Return(copts).AnyTimes() 313 mockClient.EXPECT().Kind().Return(server.CLIENT).AnyTimes() 314 }) 315 316 It("Should require a remote", func() { 317 _, err := auth.verifyServerJWTBasedAuth(nil, "", nil, "", log) 318 Expect(err).To(MatchError("remote client information is required in anonymous TLS or JWT signing modes")) 319 }) 320 321 It("Should fail on invalid jwt", func() { 322 _, err := auth.verifyServerJWTBasedAuth(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}, "x", nil, "", log) 323 Expect(err).To(MatchError("invalid JWT token")) 324 }) 325 326 It("Should fail for invalid nonce", func() { 327 copts.Sig = "wrong" 328 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}) 329 mockClient.EXPECT().GetNonce().Return([]byte("toomanysecrets")) 330 331 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 332 Expect(err).To(MatchError("invalid nonce signature or jwt token")) 333 Expect(verified).To(BeFalse()) 334 }) 335 336 It("Should deny servers when allow list is set and servers are not allowed", func() { 337 auth.clientAllowList = []string{"10.0.0.0/24"} 338 auth.denyServers = true 339 mockClient.GetOpts().Token = "" 340 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}) 341 mockClient.EXPECT().GetNonce().Return(nil) 342 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 343 Expect(user.Username).To(BeEmpty()) 344 Expect(user.Account).To(Equal(auth.choriaAccount)) 345 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 346 Deny: []string{">"}, 347 })) 348 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 349 Deny: []string{">"}}, 350 )) 351 }) 352 353 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 354 Expect(err).ToNot(HaveOccurred()) 355 Expect(verified).To(BeTrue()) 356 }) 357 358 Describe("Server Permissions", func() { 359 BeforeEach(func() { 360 auth.denyServers = false 361 sig, err := choria.Ed25519Sign(edPrivateKey, []byte("toomanysecrets")) 362 Expect(err).ToNot(HaveOccurred()) 363 copts.Sig = base64.RawURLEncoding.EncodeToString(sig) 364 365 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}) 366 mockClient.EXPECT().GetNonce().Return([]byte("toomanysecrets")) 367 }) 368 369 It("Should set strict permissions for a server JWT user", func() { 370 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 371 Expect(user.Username).To(Equal("ginkgo.example.net")) 372 Expect(user.Account).To(Equal(auth.choriaAccount)) 373 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 374 Allow: []string{ 375 "c1.broadcast.agent.>", 376 "c1.node.ginkgo.example.net", 377 "c1.reply.3f7c3a791b0eb10da51dca4cdedb9418.>", 378 "c2.broadcast.agent.>", 379 "c2.node.ginkgo.example.net", 380 "c2.reply.3f7c3a791b0eb10da51dca4cdedb9418.>", 381 }, 382 })) 383 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 384 Allow: []string{ 385 "choria.lifecycle.>", 386 "choria.machine.transition", 387 "choria.machine.watcher.>", 388 "c1.reply.>", 389 "c1.broadcast.agent.registration", 390 "choria.federation.c1.collective", 391 "c2.reply.>", 392 "c2.broadcast.agent.registration", 393 "choria.federation.c2.collective", 394 }, 395 })) 396 }) 397 398 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 399 Expect(err).ToNot(HaveOccurred()) 400 Expect(verified).To(BeTrue()) 401 }) 402 403 It("Should support denying servers", func() { 404 auth.denyServers = true 405 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 406 Expect(user.Username).To(Equal("ginkgo.example.net")) 407 Expect(user.Account).To(Equal(auth.choriaAccount)) 408 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 409 Deny: []string{">"}, 410 })) 411 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 412 Deny: []string{">"}, 413 })) 414 }) 415 416 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 417 Expect(err).ToNot(HaveOccurred()) 418 Expect(verified).To(BeTrue()) 419 }) 420 421 It("Should handle no collectives being set", func() { 422 copts.Token = createSignedServerJWT(privateKey, edPublicKey, map[string]any{ 423 "purpose": tokens.ServerPurpose, 424 "public_key": hex.EncodeToString(edPublicKey), 425 }) 426 427 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 428 Expect(user.Username).To(Equal("ginkgo.example.net")) 429 Expect(user.Account).To(Equal(auth.choriaAccount)) 430 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 431 Deny: []string{">"}, 432 })) 433 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 434 Deny: []string{">"}, 435 })) 436 }) 437 438 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 439 Expect(err).ToNot(HaveOccurred()) 440 Expect(verified).To(BeTrue()) 441 }) 442 443 It("Should support service hosts", func() { 444 copts.Token = createSignedServerJWT(privateKey, edPublicKey, map[string]any{ 445 "purpose": tokens.ServerPurpose, 446 "public_key": hex.EncodeToString(edPublicKey), 447 "collectives": []string{"c1", "c2"}, 448 "permissions": &tokens.ServerPermissions{ServiceHost: true}, 449 }) 450 451 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 452 Expect(user.Username).To(Equal("ginkgo.example.net")) 453 Expect(user.Account).To(Equal(auth.choriaAccount)) 454 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 455 Allow: []string{ 456 "c1.broadcast.agent.>", 457 "c1.node.ginkgo.example.net", 458 "c1.reply.3f7c3a791b0eb10da51dca4cdedb9418.>", 459 "c1.broadcast.service.>", 460 "c2.broadcast.agent.>", 461 "c2.node.ginkgo.example.net", 462 "c2.reply.3f7c3a791b0eb10da51dca4cdedb9418.>", 463 "c2.broadcast.service.>", 464 }, 465 })) 466 }) 467 468 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 469 Expect(err).ToNot(HaveOccurred()) 470 Expect(verified).To(BeTrue()) 471 }) 472 473 It("Should support Governors", func() { 474 copts.Token = createSignedServerJWT(privateKey, edPublicKey, map[string]any{ 475 "purpose": tokens.ServerPurpose, 476 "public_key": hex.EncodeToString(edPublicKey), 477 "collectives": []string{"c1", "c2"}, 478 "permissions": &tokens.ServerPermissions{Governor: true, Streams: true}, 479 }) 480 481 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 482 Expect(user.Username).To(Equal("ginkgo.example.net")) 483 Expect(user.Account).To(Equal(auth.choriaAccount)) 484 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 485 Allow: []string{ 486 "choria.lifecycle.>", 487 "choria.machine.transition", 488 "choria.machine.watcher.>", 489 "c1.reply.>", 490 "c1.broadcast.agent.registration", 491 "choria.federation.c1.collective", 492 "c1.governor.*", 493 "c2.reply.>", 494 "c2.broadcast.agent.registration", 495 "choria.federation.c2.collective", 496 "c2.governor.*", 497 "$JS.API.STREAM.INFO.*", 498 "$JS.API.STREAM.MSG.GET.*", 499 "$JS.API.STREAM.MSG.DELETE.*", 500 "$JS.API.DIRECT.GET.*", 501 "$JS.API.DIRECT.GET.*.>", 502 "$JS.API.CONSUMER.CREATE.*", 503 "$JS.API.CONSUMER.CREATE.*.>", 504 "$JS.API.CONSUMER.DURABLE.CREATE.*.*", 505 "$JS.API.CONSUMER.INFO.*.*", 506 "$JS.API.CONSUMER.MSG.NEXT.*.*", 507 "$JS.ACK.>", 508 "$JS.FC.>", 509 }, 510 })) 511 }) 512 513 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 514 Expect(err).ToNot(HaveOccurred()) 515 Expect(verified).To(BeTrue()) 516 }) 517 518 It("Should support Submission", func() { 519 copts.Token = createSignedServerJWT(privateKey, edPublicKey, map[string]any{ 520 "purpose": tokens.ServerPurpose, 521 "public_key": hex.EncodeToString(edPublicKey), 522 "collectives": []string{"c1", "c2"}, 523 "permissions": &tokens.ServerPermissions{Submission: true}, 524 }) 525 526 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 527 Expect(user.Username).To(Equal("ginkgo.example.net")) 528 Expect(user.Account).To(Equal(auth.choriaAccount)) 529 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 530 Allow: []string{ 531 "choria.lifecycle.>", 532 "choria.machine.transition", 533 "choria.machine.watcher.>", 534 "c1.reply.>", 535 "c1.broadcast.agent.registration", 536 "choria.federation.c1.collective", 537 "c1.submission.in.>", 538 "c2.reply.>", 539 "c2.broadcast.agent.registration", 540 "choria.federation.c2.collective", 541 "c2.submission.in.>", 542 }, 543 })) 544 }) 545 546 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 547 Expect(err).ToNot(HaveOccurred()) 548 Expect(verified).To(BeTrue()) 549 }) 550 551 Describe("Should support Streams", func() { 552 It("Should support Streams in the choria org", func() { 553 copts.Token = createSignedServerJWT(privateKey, edPublicKey, map[string]any{ 554 "purpose": tokens.ServerPurpose, 555 "public_key": hex.EncodeToString(edPublicKey), 556 "collectives": []string{"c1", "c2"}, 557 "permissions": &tokens.ServerPermissions{Streams: true}, 558 }) 559 560 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 561 Expect(user.Username).To(Equal("ginkgo.example.net")) 562 Expect(user.Account).To(Equal(auth.choriaAccount)) 563 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 564 Allow: []string{ 565 "choria.lifecycle.>", 566 "choria.machine.transition", 567 "choria.machine.watcher.>", 568 "c1.reply.>", 569 "c1.broadcast.agent.registration", 570 "choria.federation.c1.collective", 571 "c2.reply.>", 572 "c2.broadcast.agent.registration", 573 "choria.federation.c2.collective", 574 "$JS.API.STREAM.INFO.*", 575 "$JS.API.STREAM.MSG.GET.*", 576 "$JS.API.STREAM.MSG.DELETE.*", 577 "$JS.API.DIRECT.GET.*", 578 "$JS.API.DIRECT.GET.*.>", 579 "$JS.API.CONSUMER.CREATE.*", 580 "$JS.API.CONSUMER.CREATE.*.>", 581 "$JS.API.CONSUMER.DURABLE.CREATE.*.*", 582 "$JS.API.CONSUMER.INFO.*.*", 583 "$JS.API.CONSUMER.MSG.NEXT.*.*", 584 "$JS.ACK.>", 585 "$JS.FC.>", 586 }, 587 })) 588 }) 589 590 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 591 Expect(err).ToNot(HaveOccurred()) 592 Expect(verified).To(BeTrue()) 593 }) 594 It("Should support Streams in other orgs", func() { 595 copts.Token = createSignedServerJWT(privateKey, edPublicKey, map[string]any{ 596 "purpose": tokens.ServerPurpose, 597 "public_key": hex.EncodeToString(edPublicKey), 598 "collectives": []string{"c1", "c2"}, 599 "ou": "other", 600 "permissions": &tokens.ServerPermissions{Streams: true}, 601 }) 602 603 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 604 Expect(user.Username).To(Equal("ginkgo.example.net")) 605 Expect(user.Account).To(Equal(auth.choriaAccount)) 606 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 607 Allow: []string{ 608 "choria.lifecycle.>", 609 "choria.machine.transition", 610 "choria.machine.watcher.>", 611 "c1.reply.>", 612 "c1.broadcast.agent.registration", 613 "choria.federation.c1.collective", 614 "c2.reply.>", 615 "c2.broadcast.agent.registration", 616 "choria.federation.c2.collective", 617 "choria.streams.STREAM.INFO.*", 618 "choria.streams.STREAM.MSG.GET.*", 619 "choria.streams.STREAM.MSG.DELETE.*", 620 "choria.streams.DIRECT.GET.*", 621 "choria.streams.DIRECT.GET.*.>", 622 "choria.streams.CONSUMER.CREATE.*", 623 "choria.streams.CONSUMER.CREATE.*.>", 624 "choria.streams.CONSUMER.DURABLE.CREATE.*.*", 625 "choria.streams.CONSUMER.INFO.*.*", 626 "choria.streams.CONSUMER.MSG.NEXT.*.*", 627 "$JS.ACK.>", 628 "$JS.FC.>", 629 }, 630 })) 631 }) 632 633 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 634 Expect(err).ToNot(HaveOccurred()) 635 Expect(verified).To(BeTrue()) 636 }) 637 }) 638 639 It("Should support additional subjects", func() { 640 copts.Token = createSignedServerJWT(privateKey, edPublicKey, map[string]any{ 641 "purpose": tokens.ServerPurpose, 642 "public_key": hex.EncodeToString(edPublicKey), 643 "collectives": []string{"c1", "c2"}, 644 "pub_subjects": []string{"other", "subject"}, 645 }) 646 647 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 648 Expect(user.Username).To(Equal("ginkgo.example.net")) 649 Expect(user.Account).To(Equal(auth.choriaAccount)) 650 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 651 Allow: []string{ 652 "choria.lifecycle.>", 653 "choria.machine.transition", 654 "choria.machine.watcher.>", 655 "other", 656 "subject", 657 "c1.reply.>", 658 "c1.broadcast.agent.registration", 659 "choria.federation.c1.collective", 660 "c2.reply.>", 661 "c2.broadcast.agent.registration", 662 "choria.federation.c2.collective", 663 }, 664 })) 665 }) 666 667 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 668 Expect(err).ToNot(HaveOccurred()) 669 Expect(verified).To(BeTrue()) 670 671 }) 672 }) 673 }) 674 675 Describe("Clients", func() { 676 BeforeEach(func() { 677 auth.clientJwtSigners = []string{filepath.Join(td, "public.pem")} 678 copts = &server.ClientOpts{ 679 Token: createSignedClientJWT(privateKey, map[string]any{ 680 "purpose": tokens.ClientIDPurpose, 681 "public_key": hex.EncodeToString(edPublicKey), 682 }), 683 } 684 verifiedConn = &tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{nil}} 685 mockClient.EXPECT().GetOpts().Return(copts).AnyTimes() 686 mockClient.EXPECT().Kind().Return(server.CLIENT).AnyTimes() 687 }) 688 689 It("Should require a remote", func() { 690 _, err := auth.verifyClientJWTBasedAuth(nil, "", nil, "", log) 691 Expect(err).To(MatchError("remote client information is required in anonymous TLS or JWT signing modes")) 692 }) 693 694 It("Should fail on invalid jwt", func() { 695 _, err := auth.verifyClientJWTBasedAuth(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}, "x", nil, "", log) 696 Expect(err).To(MatchError("invalid JWT token")) 697 }) 698 699 It("Should fail for invalid nonce", func() { 700 copts.Sig = "wrong" 701 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}) 702 mockClient.EXPECT().GetNonce().Return([]byte("toomanysecrets")) 703 704 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 705 Expect(err).To(MatchError("invalid nonce signature or jwt token")) 706 Expect(verified).To(BeFalse()) 707 }) 708 709 It("Should set strict permissions for a client JWT user", func() { 710 sig, err := choria.Ed25519Sign(edPrivateKey, []byte("toomanysecrets")) 711 Expect(err).ToNot(HaveOccurred()) 712 copts.Sig = base64.RawURLEncoding.EncodeToString(sig) 713 714 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}) 715 mockClient.EXPECT().GetNonce().Return([]byte("toomanysecrets")) 716 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 717 Expect(user.Username).To(Equal("up=ginkgo")) 718 Expect(user.Account).To(Equal(auth.choriaAccount)) 719 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 720 Allow: []string{"*.reply.e33bf0376d4accbb4a8fd24b2f840b2e.>"}, 721 })) 722 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 723 Allow: []string{"$SYS.REQ.USER.INFO"}, 724 })) 725 }) 726 727 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 728 Expect(err).ToNot(HaveOccurred()) 729 Expect(verified).To(BeTrue()) 730 }) 731 732 Context("Org Issuers", func() { 733 var td string 734 var err error 735 var issuerPubk ed25519.PublicKey 736 737 BeforeEach(func() { 738 td, err = os.MkdirTemp("", "") 739 Expect(err).ToNot(HaveOccurred()) 740 741 issuerPubk, _, err = iu.Ed25519KeyPair() 742 Expect(err).ToNot(HaveOccurred()) 743 744 auth.issuerTokens = map[string]string{"choria": hex.EncodeToString(issuerPubk)} 745 746 // make sure no token is set so not accidentally entering jwt validation 747 copts.Token = "" 748 auth.isTLS = true 749 750 DeferCleanup(func() { 751 os.RemoveAll(td) 752 }) 753 }) 754 755 It("Should deny other clients", func() { 756 auth.allowIssuerBasedTLSAccess = false 757 758 mockClient.EXPECT().GetNonce().Return([]byte("")).AnyTimes() 759 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}).AnyTimes() 760 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{}).AnyTimes() 761 762 Expect(auth.Check(mockClient)).To(BeFalse()) 763 }) 764 765 It("Should support allowing pub sub clients", func() { 766 auth.allowIssuerBasedTLSAccess = true 767 768 mockClient.EXPECT().GetNonce().Return([]byte("")).AnyTimes() 769 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}).AnyTimes() 770 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{VerifiedChains: [][]*x509.Certificate{nil}}).AnyTimes() 771 772 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 773 Expect(user.Username).To(Equal("")) 774 Expect(user.Account).To(Equal(auth.choriaAccount)) 775 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 776 Allow: []string{">"}, 777 Deny: []string{ 778 "*.broadcast.>", 779 "*.node.>", 780 "*.reply.>", 781 "choria.federation.>", 782 "choria.lifecycle.>", 783 "choria.machine.>", 784 }, 785 })) 786 787 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 788 Allow: []string{">"}, 789 Deny: []string{ 790 "*.broadcast.>", 791 "*.node.>", 792 "*.reply.>", 793 "choria.federation.>", 794 "choria.lifecycle.>", 795 "choria.machine.>", 796 }, 797 })) 798 }) 799 800 Expect(auth.Check(mockClient)).To(BeTrue()) 801 }) 802 }) 803 804 It("Should register other clients without restriction", func() { 805 mockClient.GetOpts().Token = "" 806 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}) 807 mockClient.EXPECT().GetNonce().Return(nil) 808 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 809 Expect(user.Username).To(BeEmpty()) 810 Expect(user.Account).To(Equal(auth.choriaAccount)) 811 Expect(user.Permissions.Subscribe).To(BeNil()) 812 Expect(user.Permissions.Publish).To(BeNil()) 813 }) 814 815 verified, err := auth.handleDefaultConnection(mockClient, verifiedConn, true, log) 816 Expect(err).ToNot(HaveOccurred()) 817 Expect(verified).To(BeTrue()) 818 }) 819 }) 820 821 Describe("verifyNonceSignature", func() { 822 It("Should fail when no signature is given", func() { 823 ok, err := auth.verifyNonceSignature(nil, "", "", log) 824 Expect(ok).To(BeFalse()) 825 Expect(err).To(MatchError("connection nonce was not signed")) 826 }) 827 828 It("Should fail when no public key is in the jwt", func() { 829 ok, err := auth.verifyNonceSignature(nil, "x", "", log) 830 Expect(ok).To(BeFalse()) 831 Expect(err).To(MatchError("no public key found in the JWT to verify nonce signature")) 832 }) 833 834 It("Should fail when the server did not set a nonce", func() { 835 ok, err := auth.verifyNonceSignature(nil, "x", "x", log) 836 Expect(ok).To(BeFalse()) 837 Expect(err).To(MatchError("server did not generate a nonce to verify")) 838 }) 839 840 It("Should fail for invalid nonce signatures", func() { 841 ok, err := auth.verifyNonceSignature([]byte("toomanysecrets"), "x", hex.EncodeToString(edPublicKey), log) 842 Expect(ok).To(BeFalse()) 843 Expect(err).To(MatchError("invalid url encoded signature: illegal base64 data at input byte 0")) 844 }) 845 846 It("Should not panic for invalid length public keys", func() { 847 nonce := []byte("toomanysecrets") 848 849 sig, err := choria.Ed25519Sign(edPrivateKey, nonce) 850 Expect(err).ToNot(HaveOccurred()) 851 Expect(sig).To(HaveLen(64)) 852 853 ok, err := auth.verifyNonceSignature(nonce, base64.RawURLEncoding.EncodeToString(sig), hex.EncodeToString([]byte(hex.EncodeToString(edPublicKey))), log) 854 Expect(err).To(MatchError("could not verify nonce signature: invalid public key length 64")) 855 Expect(ok).To(BeFalse()) 856 }) 857 858 It("Should pass correct signatures", func() { 859 nonce := []byte("toomanysecrets") 860 861 sig, err := choria.Ed25519Sign(edPrivateKey, nonce) 862 Expect(err).ToNot(HaveOccurred()) 863 Expect(sig).To(HaveLen(64)) 864 865 ok, err := auth.verifyNonceSignature(nonce, base64.RawURLEncoding.EncodeToString(sig), hex.EncodeToString(edPublicKey), log) 866 Expect(err).ToNot(HaveOccurred()) 867 Expect(ok).To(BeTrue()) 868 }) 869 }) 870 }) 871 872 Describe("handleVerifiedSystemAccount", func() { 873 It("Should fail without a password", func() { 874 auth.systemUser = "" 875 auth.systemPass = "" 876 877 verified, err := auth.handleVerifiedSystemAccount(mockClient, log) 878 Expect(err).To(MatchError("system user is required")) 879 Expect(verified).To(BeFalse()) 880 881 auth.systemUser = "system" 882 verified, err = auth.handleVerifiedSystemAccount(mockClient, log) 883 Expect(err).To(MatchError("system password is required")) 884 Expect(verified).To(BeFalse()) 885 }) 886 887 It("Should fail without an account", func() { 888 auth.systemUser = "system" 889 auth.systemPass = "s3cret" 890 891 verified, err := auth.handleVerifiedSystemAccount(mockClient, log) 892 Expect(err).To(MatchError("system account is not set")) 893 Expect(verified).To(BeFalse()) 894 }) 895 896 It("Should verify the password", func() { 897 auth.systemUser = "system" 898 auth.systemPass = "other" 899 auth.systemAccount = &server.Account{Name: "system"} 900 901 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Username: "system", Password: "s3cret"}).AnyTimes() 902 verified, err := auth.handleVerifiedSystemAccount(mockClient, log) 903 Expect(err).To(MatchError("invalid system credentials")) 904 Expect(verified).To(BeFalse()) 905 }) 906 907 It("Should correctly verify the password and register the user", func() { 908 auth.systemUser = "system" 909 auth.systemPass = "s3cret" 910 auth.systemAccount = &server.Account{Name: "system"} 911 912 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Username: "system", Password: "s3cret"}).AnyTimes() 913 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 914 Expect(user.Username).To(Equal("system")) 915 Expect(user.Password).To(Equal("s3cret")) 916 Expect(user.Account).To(Equal(auth.systemAccount)) 917 Expect(user.Permissions).To(Not(BeNil())) 918 Expect(user.Permissions.Publish).To(BeNil()) 919 Expect(user.Permissions.Subscribe).To(BeNil()) 920 Expect(user.Permissions.Response).To(BeNil()) 921 }) 922 923 verified, err := auth.handleVerifiedSystemAccount(mockClient, log) 924 Expect(err).ToNot(HaveOccurred()) 925 Expect(verified).To(BeTrue()) 926 }) 927 }) 928 929 Describe("handleProvisioningUserConnection", func() { 930 It("Should fail without a password", func() { 931 auth.provPass = "" 932 933 verified, err := auth.handleProvisioningUserConnection(mockClient, true) 934 Expect(err).To(MatchError("provisioning user password not enabled")) 935 Expect(verified).To(BeFalse()) 936 }) 937 938 It("Should fail without an account", func() { 939 auth.provPass = "s3cret" 940 941 verified, err := auth.handleProvisioningUserConnection(mockClient, true) 942 Expect(err).To(MatchError("provisioning account is not set")) 943 Expect(verified).To(BeFalse()) 944 }) 945 946 Context("Using Issuers", func() { 947 var td string 948 var err error 949 var issuerPubk ed25519.PublicKey 950 var issuerPrik ed25519.PrivateKey 951 952 BeforeEach(func() { 953 td, err = os.MkdirTemp("", "") 954 Expect(err).ToNot(HaveOccurred()) 955 956 issuerPubk, issuerPrik, err = iu.Ed25519KeyPair() 957 Expect(err).ToNot(HaveOccurred()) 958 959 auth.issuerTokens = map[string]string{"choria": hex.EncodeToString(issuerPubk)} 960 961 DeferCleanup(func() { 962 os.RemoveAll(td) 963 }) 964 }) 965 966 It("Should require a token", func() { 967 auth.provPass = "s3cret" 968 auth.provisioningAccount = &server.Account{Name: provisioningUser} 969 970 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Username: provisioningUser, Password: "s3cret"}).AnyTimes() 971 972 verified, err := auth.handleProvisioningUserConnection(mockClient, true) 973 Expect(err).To(MatchError("no token provided in connection")) 974 Expect(verified).To(BeFalse()) 975 }) 976 977 It("Should handle tokens that do not pass validation", func() { 978 auth.provPass = "s3cret" 979 auth.provisioningAccount = &server.Account{Name: provisioningUser} 980 981 provPubk, _, err := iu.Ed25519KeyPair() 982 Expect(err).ToNot(HaveOccurred()) 983 984 provClaims, err := tokens.NewClientIDClaims("provisioner", nil, "choria", nil, "", "", time.Hour, nil, provPubk) 985 Expect(err).ToNot(HaveOccurred()) 986 987 // make sure its expired 988 provClaims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(-time.Hour)) 989 signed, err := tokens.SignToken(provClaims, issuerPrik) 990 Expect(err).ToNot(HaveOccurred()) 991 992 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Username: provisioningUser, Password: "s3cret", Token: signed}).AnyTimes() 993 994 verified, err := auth.handleProvisioningUserConnection(mockClient, true) 995 Expect(err.Error()).To(MatchRegexp("token is expired by 1h")) 996 Expect(verified).To(BeFalse()) 997 }) 998 999 It("Should require the provisioner permission", func() { 1000 auth.provPass = "s3cret" 1001 auth.provisioningAccount = &server.Account{Name: provisioningUser} 1002 1003 provPubk, _, err := iu.Ed25519KeyPair() 1004 Expect(err).ToNot(HaveOccurred()) 1005 1006 provClaims, err := tokens.NewClientIDClaims("provisioner", nil, "choria", nil, "", "", time.Hour, nil, provPubk) 1007 Expect(err).ToNot(HaveOccurred()) 1008 signed, err := tokens.SignToken(provClaims, issuerPrik) 1009 Expect(err).ToNot(HaveOccurred()) 1010 1011 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Username: provisioningUser, Password: "s3cret", Token: signed}).AnyTimes() 1012 1013 verified, err := auth.handleProvisioningUserConnection(mockClient, true) 1014 Expect(err).To(MatchError("provisioner claim is false in token with caller id 'provisioner'")) 1015 Expect(verified).To(BeFalse()) 1016 }) 1017 1018 It("Should support v1 provisioner connections", func() { 1019 auth.provWithoutToken = true 1020 auth.provPass = "s3cret" 1021 auth.provisioningAccount = &server.Account{Name: provisioningUser} 1022 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Username: provisioningUser, Password: "s3cret"}).AnyTimes() 1023 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 1024 Expect(user.Username).To(Equal(provisioningUser)) 1025 Expect(user.Password).To(Equal("s3cret")) 1026 Expect(user.Account).To(Equal(auth.provisioningAccount)) 1027 Expect(user.Permissions).To(Not(BeNil())) 1028 Expect(user.Permissions.Publish).To(BeNil()) 1029 Expect(user.Permissions.Subscribe).To(BeNil()) 1030 Expect(user.Permissions.Response).To(BeNil()) 1031 }) 1032 1033 verified, err := auth.handleProvisioningUserConnection(mockClient, true) 1034 Expect(err).ToNot(HaveOccurred()) 1035 Expect(verified).To(BeTrue()) 1036 }) 1037 1038 It("Should correctly verify the password and register the user", func() { 1039 auth.provPass = "s3cret" 1040 auth.provisioningAccount = &server.Account{Name: provisioningUser} 1041 1042 provPubk, _, err := iu.Ed25519KeyPair() 1043 Expect(err).ToNot(HaveOccurred()) 1044 1045 provClaims, err := tokens.NewClientIDClaims("provisioner", nil, "choria", nil, "", "", time.Hour, &tokens.ClientPermissions{ServerProvisioner: true}, provPubk) 1046 Expect(err).ToNot(HaveOccurred()) 1047 signed, err := tokens.SignToken(provClaims, issuerPrik) 1048 Expect(err).ToNot(HaveOccurred()) 1049 1050 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Username: provisioningUser, Password: "s3cret", Token: signed}).AnyTimes() 1051 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 1052 Expect(user.Username).To(Equal(provisioningUser)) 1053 Expect(user.Password).To(Equal("s3cret")) 1054 Expect(user.Account).To(Equal(auth.provisioningAccount)) 1055 Expect(user.Permissions).To(Not(BeNil())) 1056 Expect(user.Permissions.Publish).To(BeNil()) 1057 Expect(user.Permissions.Subscribe).To(BeNil()) 1058 Expect(user.Permissions.Response).To(BeNil()) 1059 }) 1060 1061 verified, err := auth.handleProvisioningUserConnection(mockClient, true) 1062 Expect(err).ToNot(HaveOccurred()) 1063 Expect(verified).To(BeTrue()) 1064 }) 1065 }) 1066 1067 Context("Using mTLS", func() { 1068 It("Should fail when server not in TLS mode", func() { 1069 auth.provPass = "s3cret" 1070 auth.isTLS = false 1071 auth.provisioningAccount = &server.Account{Name: provisioningUser} 1072 1073 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Username: provisioningUser, Password: "s3cret"}).AnyTimes() 1074 1075 verified, err := auth.handleProvisioningUserConnection(mockClient, true) 1076 Expect(err).To(MatchError("provisioning user access requires TLS")) 1077 Expect(verified).To(BeFalse()) 1078 }) 1079 1080 It("Should fail when client is not using TLS", func() { 1081 auth.provPass = "s3cret" 1082 auth.isTLS = true 1083 auth.provisioningAccount = &server.Account{Name: provisioningUser} 1084 mockClient.EXPECT().GetTLSConnectionState().Return(nil).AnyTimes() 1085 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Username: provisioningUser, Password: "s3cret"}).AnyTimes() 1086 1087 verified, err := auth.handleProvisioningUserConnection(mockClient, false) 1088 Expect(err).To(MatchError("provisioning user is only allowed over verified TLS connections")) 1089 Expect(verified).To(BeFalse()) 1090 }) 1091 1092 It("Should correctly verify the password and register the user", func() { 1093 auth.provPass = "s3cret" 1094 auth.isTLS = true 1095 auth.provisioningAccount = &server.Account{Name: provisioningUser} 1096 mockClient.EXPECT().GetTLSConnectionState().Return(&tls.ConnectionState{}).AnyTimes() 1097 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Username: provisioningUser, Password: "s3cret"}).AnyTimes() 1098 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 1099 Expect(user.Username).To(Equal(provisioningUser)) 1100 Expect(user.Password).To(Equal("s3cret")) 1101 Expect(user.Account).To(Equal(auth.provisioningAccount)) 1102 Expect(user.Permissions).To(Not(BeNil())) 1103 Expect(user.Permissions.Publish).To(BeNil()) 1104 Expect(user.Permissions.Subscribe).To(BeNil()) 1105 Expect(user.Permissions.Response).To(BeNil()) 1106 }) 1107 1108 verified, err := auth.handleProvisioningUserConnection(mockClient, true) 1109 Expect(err).ToNot(HaveOccurred()) 1110 Expect(verified).To(BeTrue()) 1111 }) 1112 }) 1113 }) 1114 1115 Describe("handleUnverifiedProvisioningConnection", func() { 1116 Describe("Provisioner Client", func() { 1117 It("Should not accept connections from the provisioning user without verified TLS", func() { 1118 auth.provisioningTokenSigner = "testdata/ssl/certs/rip.mcollective.pem" 1119 auth.provisioningAccount = &server.Account{Name: "provisioning"} 1120 1121 copts := &server.ClientOpts{Username: "provisioner", Password: "s3cret"} 1122 mockClient.EXPECT().GetOpts().Return(copts).AnyTimes() 1123 1124 validated, err := auth.handleUnverifiedProvisioningConnection(mockClient) 1125 Expect(err).To(MatchError("provisioning user requires a verified connection")) 1126 Expect(validated).To(BeFalse()) 1127 }) 1128 }) 1129 1130 Context("Org Issuers", func() { 1131 var ( 1132 issuerPubk ed25519.PublicKey 1133 issuerPrik ed25519.PrivateKey 1134 valid, invalid, wrongOU, noOU string 1135 err error 1136 ) 1137 1138 BeforeEach(func() { 1139 issuerPubk, issuerPrik, err = iu.Ed25519KeyPair() 1140 Expect(err).ToNot(HaveOccurred()) 1141 1142 td, err := os.MkdirTemp("", "") 1143 Expect(err).ToNot(HaveOccurred()) 1144 DeferCleanup(func() { os.RemoveAll(td) }) 1145 1146 token, err := tokens.NewProvisioningClaims(false, true, "s3cret", "", "", nil, "example.net", "", "", "choria", "xxx", time.Hour) 1147 Expect(err).ToNot(HaveOccurred()) 1148 valid, err = tokens.SignToken(token, issuerPrik) 1149 Expect(err).ToNot(HaveOccurred()) 1150 1151 token, err = tokens.NewProvisioningClaims(false, true, "s3cret", "", "", nil, "example.net", "", "", "choria", "xxx", time.Hour) 1152 Expect(err).ToNot(HaveOccurred()) 1153 token.ExpiresAt = jwt.NewNumericDate(time.Now().Add(-1 * time.Hour)) 1154 invalid, err = tokens.SignToken(token, issuerPrik) 1155 Expect(err).ToNot(HaveOccurred()) 1156 1157 token, err = tokens.NewProvisioningClaims(false, true, "s3cret", "", "", nil, "example.net", "", "", "other", "xxx", time.Hour) 1158 Expect(err).ToNot(HaveOccurred()) 1159 wrongOU, err = tokens.SignToken(token, issuerPrik) 1160 Expect(err).ToNot(HaveOccurred()) 1161 1162 token, err = tokens.NewProvisioningClaims(false, true, "s3cret", "", "", nil, "example.net", "", "", "other", "xxx", time.Hour) 1163 token.OrganizationUnit = "" 1164 Expect(err).ToNot(HaveOccurred()) 1165 noOU, err = tokens.SignToken(token, issuerPrik) 1166 Expect(err).ToNot(HaveOccurred()) 1167 1168 auth.issuerTokens = map[string]string{"choria": hex.EncodeToString(issuerPubk)} 1169 auth.provPass = "s3cret" 1170 }) 1171 1172 It("Should fail without a provisioner account", func() { 1173 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{}).AnyTimes() 1174 1175 validated, err := auth.handleUnverifiedProvisioningConnection(mockClient) 1176 Expect(validated).To(BeFalse()) 1177 Expect(err).To(MatchError("provisioning account is not set")) 1178 }) 1179 1180 Describe("Servers", func() { 1181 BeforeEach(func() { 1182 auth.provisioningAccount = &server.Account{Name: "provisioning"} 1183 }) 1184 1185 It("Should fail for invalid tokens", func() { 1186 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Token: invalid}).AnyTimes() 1187 1188 validated, err := auth.handleUnverifiedProvisioningConnection(mockClient) 1189 Expect(validated).To(BeFalse()) 1190 Expect(err.Error()).To(MatchRegexp("token is expired by")) 1191 }) 1192 1193 It("Should detect missing ou claims", func() { 1194 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Token: noOU}).AnyTimes() 1195 1196 validated, err := auth.handleUnverifiedProvisioningConnection(mockClient) 1197 Expect(validated).To(BeFalse()) 1198 Expect(err.Error()).To(MatchRegexp("no ou claim in token")) 1199 }) 1200 1201 It("Should detect unconfigured Issuers", func() { 1202 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Token: wrongOU}).AnyTimes() 1203 1204 validated, err := auth.handleUnverifiedProvisioningConnection(mockClient) 1205 Expect(validated).To(BeFalse()) 1206 Expect(err.Error()).To(MatchRegexp("no issuer found for ou other")) 1207 }) 1208 1209 It("Should set server permissions and register", func() { 1210 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Token: valid}).AnyTimes() 1211 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}) 1212 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 1213 Expect(user.Username).To(BeEmpty()) 1214 Expect(user.Password).To(BeEmpty()) 1215 Expect(user.Account).To(Equal(auth.provisioningAccount)) 1216 Expect(user.Permissions).To(Not(BeNil())) 1217 Expect(user.Permissions.Subscribe.Allow).To(Equal([]string{ 1218 "provisioning.node.>", 1219 "provisioning.broadcast.agent.discovery", 1220 "provisioning.broadcast.agent.rpcutil", 1221 "provisioning.broadcast.agent.choria_util", 1222 "provisioning.broadcast.agent.choria_provision", 1223 })) 1224 Expect(user.Permissions.Publish.Allow).To(Equal([]string{ 1225 "choria.lifecycle.>", 1226 "provisioning.reply.>", 1227 "provisioning.registration.>", 1228 })) 1229 }) 1230 1231 validated, err := auth.handleUnverifiedProvisioningConnection(mockClient) 1232 Expect(validated).To(BeTrue()) 1233 Expect(err).ToNot(HaveOccurred()) 1234 }) 1235 }) 1236 }) 1237 1238 Context("mTLS", func() { 1239 It("Should fail without a signer cert set or present", func() { 1240 t, err := os.ReadFile("testdata/provisioning/invalid.jwt") 1241 Expect(err).ToNot(HaveOccurred()) 1242 1243 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Token: string(t)}).AnyTimes() 1244 auth.provisioningAccount = &server.Account{Name: "provisioning"} 1245 1246 validated, err := auth.handleUnverifiedProvisioningConnection(mockClient) 1247 Expect(validated).To(BeFalse()) 1248 Expect(err).To(MatchError("provisioning is not enabled")) 1249 1250 auth.provisioningTokenSigner = "/nonexisting" 1251 validated, err = auth.handleUnverifiedProvisioningConnection(mockClient) 1252 Expect(validated).To(BeFalse()) 1253 Expect(err).To(MatchError("provisioning signer certificate /nonexisting does not exist")) 1254 }) 1255 1256 It("Should fail without a provisioner account", func() { 1257 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{}).AnyTimes() 1258 1259 auth.provisioningTokenSigner = "testdata/ssl/certs/rip.mcollective.pem" 1260 1261 validated, err := auth.handleUnverifiedProvisioningConnection(mockClient) 1262 Expect(validated).To(BeFalse()) 1263 Expect(err).To(MatchError("provisioning account is not set")) 1264 }) 1265 1266 It("Should fail without a token", func() { 1267 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{}).AnyTimes() 1268 auth.provisioningAccount = &server.Account{Name: "provisioning"} 1269 auth.provisioningTokenSigner = "testdata/ssl/certs/rip.mcollective.pem" 1270 1271 validated, err := auth.handleUnverifiedProvisioningConnection(mockClient) 1272 Expect(validated).To(BeFalse()) 1273 Expect(err).To(MatchError("provisioning requires a token")) 1274 }) 1275 1276 Describe("Servers", func() { 1277 BeforeEach(func() { 1278 auth.provisioningTokenSigner = "testdata/ssl/certs/rip.mcollective.pem" 1279 auth.provisioningAccount = &server.Account{Name: "provisioning"} 1280 }) 1281 1282 It("Should fail for invalid tokens", func() { 1283 t, err := os.ReadFile("testdata/provisioning/invalid.jwt") 1284 Expect(err).ToNot(HaveOccurred()) 1285 1286 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Token: string(t)}).AnyTimes() 1287 1288 validated, err := auth.handleUnverifiedProvisioningConnection(mockClient) 1289 Expect(validated).To(BeFalse()) 1290 Expect(err).To(MatchError("could not parse provisioner token: crypto/rsa: verification error")) 1291 }) 1292 1293 It("Should set server permissions and register", func() { 1294 t, err := os.ReadFile("testdata/provisioning/secure.jwt") 1295 Expect(err).ToNot(HaveOccurred()) 1296 1297 mockClient.EXPECT().GetOpts().Return(&server.ClientOpts{Token: string(t)}).AnyTimes() 1298 mockClient.EXPECT().RemoteAddress().Return(&net.IPAddr{IP: net.ParseIP("192.168.0.1"), Zone: ""}) 1299 mockClient.EXPECT().RegisterUser(gomock.Any()).Do(func(user *server.User) { 1300 Expect(user.Username).To(BeEmpty()) 1301 Expect(user.Password).To(BeEmpty()) 1302 Expect(user.Account).To(Equal(auth.provisioningAccount)) 1303 Expect(user.Permissions).To(Not(BeNil())) 1304 Expect(user.Permissions.Subscribe.Allow).To(Equal([]string{ 1305 "provisioning.node.>", 1306 "provisioning.broadcast.agent.discovery", 1307 "provisioning.broadcast.agent.rpcutil", 1308 "provisioning.broadcast.agent.choria_util", 1309 "provisioning.broadcast.agent.choria_provision", 1310 })) 1311 Expect(user.Permissions.Publish.Allow).To(Equal([]string{ 1312 "choria.lifecycle.>", 1313 "provisioning.reply.>", 1314 "provisioning.registration.>", 1315 })) 1316 }) 1317 1318 validated, err := auth.handleUnverifiedProvisioningConnection(mockClient) 1319 Expect(validated).To(BeTrue()) 1320 Expect(err).ToNot(HaveOccurred()) 1321 }) 1322 }) 1323 }) 1324 }) 1325 1326 Describe("remoteInClientAllowList", func() { 1327 It("Should allow all when no allowlist is set", func() { 1328 ipv4Addr, _, err := net.ParseCIDR("192.0.2.1/24") 1329 Expect(err).ToNot(HaveOccurred()) 1330 1331 Expect(auth.remoteInClientAllowList(&net.IPAddr{IP: ipv4Addr})).To(BeTrue()) 1332 }) 1333 1334 It("Should handle nil remotes", func() { 1335 Expect(auth.remoteInClientAllowList(nil)).To(BeTrue()) 1336 }) 1337 1338 It("Should handle invalid remotes", func() { 1339 ipv4Addr, _, err := net.ParseCIDR("192.0.2.1/24") 1340 Expect(err).ToNot(HaveOccurred()) 1341 1342 auth.clientAllowList = []string{"192.0.2.1/24"} 1343 Expect(auth.remoteInClientAllowList(&net.IPAddr{IP: ipv4Addr})).To(BeFalse()) 1344 }) 1345 1346 It("Should handle simple strings", func() { 1347 ipv4Addr, _, err := net.ParseCIDR("192.0.2.1/24") 1348 Expect(err).ToNot(HaveOccurred()) 1349 1350 auth.clientAllowList = []string{"192.0.2.1"} 1351 Expect(auth.remoteInClientAllowList(&net.TCPAddr{IP: ipv4Addr, Port: 1232})).To(BeTrue()) 1352 }) 1353 1354 It("Should handle subnets", func() { 1355 ipv4Addr, _, err := net.ParseCIDR("192.0.2.1/24") 1356 Expect(err).ToNot(HaveOccurred()) 1357 1358 auth.clientAllowList = []string{"192.0.0.0/8"} 1359 Expect(auth.remoteInClientAllowList(&net.TCPAddr{IP: ipv4Addr, Port: 1232})).To(BeTrue()) 1360 }) 1361 1362 It("Should support IPv6", func() { 1363 auth.clientAllowList = []string{ 1364 "2a00:1450::/32", 1365 "2a01:1450:4002:801::200e", 1366 } 1367 1368 ipv6Addr, _, err := net.ParseCIDR("2a00:1450:4002:801::200e/64") 1369 Expect(err).ToNot(HaveOccurred()) 1370 Expect(auth.remoteInClientAllowList(&net.TCPAddr{IP: ipv6Addr, Port: 1232})).To(BeTrue()) 1371 1372 ipv6Addr, _, err = net.ParseCIDR("2a01:1450:4002:801::200e/64") 1373 Expect(err).ToNot(HaveOccurred()) 1374 Expect(auth.remoteInClientAllowList(&net.TCPAddr{IP: ipv6Addr, Port: 1232})).To(BeTrue()) 1375 1376 ipv6Addr, _, err = net.ParseCIDR("2a02:1450:4002:801::200e/64") 1377 Expect(err).ToNot(HaveOccurred()) 1378 Expect(auth.remoteInClientAllowList(&net.TCPAddr{IP: ipv6Addr, Port: 1232})).To(BeFalse()) 1379 }) 1380 1381 It("Should be false for un matched nodes", func() { 1382 ipv4Addr, _, err := net.ParseCIDR("192.0.2.1/24") 1383 Expect(err).ToNot(HaveOccurred()) 1384 1385 auth.clientAllowList = []string{"127.0.0.0/8"} 1386 Expect(auth.remoteInClientAllowList(&net.TCPAddr{IP: ipv4Addr, Port: 1232})).To(BeFalse()) 1387 1388 ipv4Addr, _, err = net.ParseCIDR("127.0.2.1/24") 1389 Expect(err).ToNot(HaveOccurred()) 1390 Expect(auth.remoteInClientAllowList(&net.TCPAddr{IP: ipv4Addr, Port: 1232})).To(BeTrue()) 1391 }) 1392 }) 1393 1394 Describe("parseServerJWT", func() { 1395 It("Should fail without a cert", func() { 1396 _, err := auth.parseServerJWT("") 1397 Expect(err).To(MatchError("no Server JWT signer or Organization Issuer set, denying all servers")) 1398 }) 1399 1400 It("Should fail for empty JWTs", func() { 1401 auth.serverJwtSigners = []string{"testdata/public.pem"} 1402 _, err := auth.parseServerJWT("") 1403 Expect(err).To(MatchError("no JWT received")) 1404 }) 1405 1406 Describe("Issuers", func() { 1407 var ( 1408 issuerPubk, serverPubk ed25519.PublicKey 1409 issuerPrik ed25519.PrivateKey 1410 err error 1411 ) 1412 1413 BeforeEach(func() { 1414 issuerPubk, issuerPrik, err = iu.Ed25519KeyPair() 1415 Expect(err).ToNot(HaveOccurred()) 1416 serverPubk, _, err = iu.Ed25519KeyPair() 1417 Expect(err).ToNot(HaveOccurred()) 1418 1419 auth.issuerTokens = map[string]string{"choria": hex.EncodeToString(issuerPubk)} 1420 }) 1421 1422 It("Should detect missing ou claims", func() { 1423 signed := createSignedServerJWT(issuerPrik, serverPubk, map[string]any{ 1424 "ou": nil, 1425 }) 1426 1427 _, err = auth.parseServerJWT(signed) 1428 Expect(err.Error()).To(MatchRegexp("no ou claim in token")) 1429 }) 1430 1431 It("Should detect unconfigured Issuers", func() { 1432 signed := createSignedServerJWT(issuerPrik, serverPubk, map[string]any{ 1433 "ou": "other", 1434 }) 1435 1436 _, err = auth.parseServerJWT(signed) 1437 Expect(err.Error()).To(MatchRegexp("no issuer found for ou other")) 1438 }) 1439 1440 It("Should parse the token and handle failures", func() { 1441 signed := createSignedClientJWT(issuerPrik, map[string]any{ 1442 "ou": "choria", 1443 }) 1444 1445 _, err := auth.parseServerJWT(signed) 1446 Expect(err).To(MatchError("failed to parse token issued by the choria chain: not a server token")) 1447 }) 1448 1449 It("Should handle valid tokens issued by a chain issuer", func() { 1450 // this is for provisioner signing servers 1451 chainIssuerPubk, chainIssuerPrik, err := iu.Ed25519KeyPair() 1452 Expect(err).ToNot(HaveOccurred()) 1453 chainIssuer, err := tokens.NewClientIDClaims("chain_issuer", nil, "choria", nil, "", "", time.Hour, nil, chainIssuerPubk) 1454 Expect(err).ToNot(HaveOccurred()) 1455 Expect(chainIssuer.AddOrgIssuerData(issuerPrik)).To(Succeed()) 1456 1457 serverPubk, _, err := iu.Ed25519KeyPair() 1458 Expect(err).ToNot(HaveOccurred()) 1459 server, err := tokens.NewServerClaims("ginkgo.example.net", []string{"choria"}, "choria", nil, nil, serverPubk, "", time.Hour) 1460 Expect(err).ToNot(HaveOccurred()) 1461 Expect(server.AddChainIssuerData(chainIssuer, chainIssuerPrik)).To(Succeed()) 1462 signed, err := tokens.SignToken(server, chainIssuerPrik) 1463 Expect(err).ToNot(HaveOccurred()) 1464 1465 claims, err := auth.parseServerJWT(signed) 1466 Expect(err).ToNot(HaveOccurred()) 1467 Expect(claims.ChoriaIdentity).To(Equal("ginkgo.example.net")) 1468 }) 1469 1470 It("Should handle valid tokens issued by the org issuer", func() { 1471 pubk, _, err := iu.Ed25519KeyPair() 1472 Expect(err).ToNot(HaveOccurred()) 1473 server, err := tokens.NewServerClaims("ginkgo.example.net", []string{"choria"}, "choria", nil, nil, pubk, "", time.Hour) 1474 Expect(err).ToNot(HaveOccurred()) 1475 signed, err := tokens.SignToken(server, issuerPrik) 1476 Expect(err).ToNot(HaveOccurred()) 1477 1478 claims, err := auth.parseServerJWT(signed) 1479 Expect(err).ToNot(HaveOccurred()) 1480 Expect(claims.ChoriaIdentity).To(Equal("ginkgo.example.net")) 1481 }) 1482 }) 1483 1484 Describe("Trusted Signers", func() { 1485 It("Should verify JWTs", func() { 1486 edPublicKey, edPriKey, err := choria.Ed25519KeyPair() 1487 Expect(err).ToNot(HaveOccurred()) 1488 1489 auth.serverJwtSigners = []string{hex.EncodeToString(edPublicKey)} 1490 signed := createSignedClientJWT(edPriKey, map[string]any{ 1491 "exp": time.Now().UTC().Add(-time.Hour).Unix(), 1492 }) 1493 1494 _, err = auth.parseServerJWT(signed) 1495 Expect(err).To(MatchError(jwt.ErrTokenExpired)) 1496 }) 1497 1498 It("Should check a purpose field", func() { 1499 edPublicKey, edPriKey, err := choria.Ed25519KeyPair() 1500 Expect(err).ToNot(HaveOccurred()) 1501 auth.serverJwtSigners = []string{hex.EncodeToString(edPublicKey)} 1502 1503 signed := createSignedClientJWT(edPriKey, nil) 1504 _, err = auth.parseServerJWT(signed) 1505 Expect(err).To(MatchError(tokens.ErrNotAServerToken)) 1506 1507 signed = createSignedClientJWT(edPriKey, map[string]any{ 1508 "purpose": "wrong", 1509 }) 1510 _, err = auth.parseServerJWT(signed) 1511 Expect(err).To(MatchError(tokens.ErrNotAServerToken)) 1512 }) 1513 1514 It("Should check the identity", func() { 1515 edPublicKey, edPriKey, err := choria.Ed25519KeyPair() 1516 Expect(err).ToNot(HaveOccurred()) 1517 auth.serverJwtSigners = []string{hex.EncodeToString(edPublicKey)} 1518 1519 signed := createSignedClientJWT(edPriKey, map[string]any{ 1520 "purpose": tokens.ServerPurpose, 1521 }) 1522 _, err = auth.parseServerJWT(signed) 1523 Expect(err).To(MatchError("identity not in claims")) 1524 }) 1525 1526 It("Should check the public key", func() { 1527 edPublicKey, edPriKey, err := choria.Ed25519KeyPair() 1528 Expect(err).ToNot(HaveOccurred()) 1529 auth.serverJwtSigners = []string{hex.EncodeToString(edPublicKey)} 1530 1531 signed := createSignedClientJWT(edPriKey, map[string]any{ 1532 "purpose": tokens.ServerPurpose, 1533 "identity": "ginkgo.example.net", 1534 }) 1535 _, err = auth.parseServerJWT(signed) 1536 Expect(err).To(MatchError("no public key in claims")) 1537 }) 1538 1539 It("Should handle public keys that are 64 long file names", func() { 1540 if runtime.GOOS == "windows" { 1541 Skip("Not supported on windows") 1542 } 1543 1544 td, err := os.MkdirTemp("/tmp", "") 1545 Expect(err).ToNot(HaveOccurred()) 1546 defer os.RemoveAll(td) 1547 1548 if len(td) > 50 { 1549 Skip("Temp directory is too long for filename test") 1550 } 1551 1552 pkPath := filepath.Join(td, strings.Repeat("x", 65-len(td)-strings.Count(td, string(os.PathSeparator)))) 1553 Expect(pkPath).To(HaveLen(64)) 1554 1555 edPublicKey, edPriKey, err := choria.Ed25519KeyPair() 1556 Expect(err).ToNot(HaveOccurred()) 1557 Expect(os.WriteFile(pkPath, []byte(hex.EncodeToString(edPublicKey)), 0600)).To(Succeed()) 1558 1559 auth.serverJwtSigners = []string{pkPath} 1560 1561 signed := createSignedServerJWT(edPriKey, edPublicKey, map[string]any{ 1562 "purpose": tokens.ServerPurpose, 1563 "identity": "ginkgo.example.net", 1564 "public_key": hex.EncodeToString(edPublicKey), 1565 }) 1566 1567 _, err = auth.parseServerJWT(signed) 1568 Expect(err).ToNot(HaveOccurred()) 1569 }) 1570 1571 It("Should handle multiple public identifiers", func() { 1572 edPublicKey, edPriKey, err := choria.Ed25519KeyPair() 1573 Expect(err).ToNot(HaveOccurred()) 1574 auth.serverJwtSigners = []string{"/nonexisting", hex.EncodeToString(edPublicKey)} 1575 1576 signed := createSignedServerJWT(edPriKey, edPublicKey, map[string]any{ 1577 "purpose": tokens.ServerPurpose, 1578 "identity": "ginkgo.example.net", 1579 "public_key": hex.EncodeToString(edPublicKey), 1580 }) 1581 1582 _, err = auth.parseServerJWT(signed) 1583 Expect(err).ToNot(HaveOccurred()) 1584 }) 1585 }) 1586 }) 1587 1588 Describe("parseClientIDJWT", func() { 1589 var td string 1590 var privateKey *rsa.PrivateKey 1591 1592 BeforeEach(func() { 1593 td, privateKey = createKeyPair() 1594 }) 1595 1596 AfterEach(func() { 1597 os.RemoveAll(td) 1598 }) 1599 1600 It("Should fail without a cert", func() { 1601 _, err := auth.parseClientIDJWT("") 1602 Expect(err).To(MatchError("no Client JWT signer or Organization Issuer set, denying all clients")) 1603 }) 1604 1605 It("Should fail for empty JWTs", func() { 1606 auth.clientJwtSigners = []string{"testdata/public.pem"} 1607 _, err := auth.parseClientIDJWT("") 1608 Expect(err).To(MatchError("no JWT received")) 1609 }) 1610 1611 Describe("Issuers", func() { 1612 var ( 1613 issuerPubk ed25519.PublicKey 1614 issuerPrik ed25519.PrivateKey 1615 err error 1616 ) 1617 1618 BeforeEach(func() { 1619 issuerPubk, issuerPrik, err = iu.Ed25519KeyPair() 1620 Expect(err).ToNot(HaveOccurred()) 1621 auth.issuerTokens = map[string]string{"choria": hex.EncodeToString(issuerPubk)} 1622 }) 1623 1624 It("Should detect missing ou claims", func() { 1625 signed := createSignedClientJWT(privateKey, nil) 1626 1627 _, err := auth.parseClientIDJWT(signed) 1628 Expect(err.Error()).To(MatchRegexp("no ou claim in token")) 1629 }) 1630 1631 It("Should detect unconfigured Issuers", func() { 1632 signed := createSignedClientJWT(privateKey, map[string]any{ 1633 "exp": time.Now().UTC().Add(-time.Hour).Unix(), 1634 "ou": "other", 1635 }) 1636 1637 _, err := auth.parseClientIDJWT(signed) 1638 Expect(err.Error()).To(MatchRegexp("no issuer configured for ou 'other'")) 1639 }) 1640 1641 It("Should parse the token and handle failures", func() { 1642 pubk, _, err := iu.Ed25519KeyPair() 1643 Expect(err).ToNot(HaveOccurred()) 1644 1645 signed := createSignedServerJWT(issuerPrik, pubk, map[string]any{ 1646 "ou": "choria", 1647 }) 1648 _, err = auth.parseClientIDJWT(signed) 1649 Expect(err.Error()).To(MatchRegexp("failed to parse client token issued by the choria chain: not a client token")) 1650 }) 1651 1652 It("Should handle valid tokens issued by a chain issuer", func() { 1653 chainIssuerPubk, chainIssuerPrik, err := iu.Ed25519KeyPair() 1654 Expect(err).ToNot(HaveOccurred()) 1655 chainIssuer, err := tokens.NewClientIDClaims("chain_issuer", nil, "choria", nil, "", "", time.Hour, nil, chainIssuerPubk) 1656 Expect(err).ToNot(HaveOccurred()) 1657 Expect(chainIssuer.AddOrgIssuerData(issuerPrik)).To(Succeed()) 1658 1659 clientPubk, _, err := iu.Ed25519KeyPair() 1660 Expect(err).ToNot(HaveOccurred()) 1661 client, err := tokens.NewClientIDClaims("ginkgo", nil, "choria", nil, "", "", time.Hour, nil, clientPubk) 1662 Expect(err).ToNot(HaveOccurred()) 1663 Expect(client.AddChainIssuerData(chainIssuer, chainIssuerPrik)).To(Succeed()) 1664 signed, err := tokens.SignToken(client, chainIssuerPrik) 1665 Expect(err).ToNot(HaveOccurred()) 1666 1667 claims, err := auth.parseClientIDJWT(signed) 1668 Expect(err).ToNot(HaveOccurred()) 1669 Expect(claims.CallerID).To(Equal("ginkgo")) 1670 }) 1671 1672 It("Should handle valid tokens issued by the org issuer", func() { 1673 pubk, _, err := iu.Ed25519KeyPair() 1674 Expect(err).ToNot(HaveOccurred()) 1675 client, err := tokens.NewClientIDClaims("ginkgo", nil, "choria", nil, "", "", time.Hour, nil, pubk) 1676 Expect(err).ToNot(HaveOccurred()) 1677 // Expect(client.AddOrgIssuerData(issuerPrik)).To(Succeed()) 1678 1679 signed, err := tokens.SignToken(client, issuerPrik) 1680 Expect(err).ToNot(HaveOccurred()) 1681 1682 claims, err := auth.parseClientIDJWT(signed) 1683 Expect(err).ToNot(HaveOccurred()) 1684 Expect(claims.CallerID).To(Equal("ginkgo")) 1685 }) 1686 }) 1687 1688 Describe("Trusted Signers", func() { 1689 It("Should verify JWTs", func() { 1690 auth.clientJwtSigners = []string{filepath.Join(td, "public.pem")} 1691 signed := createSignedClientJWT(privateKey, map[string]any{ 1692 "exp": time.Now().UTC().Add(-time.Hour).Unix(), 1693 }) 1694 1695 _, err := auth.parseClientIDJWT(signed) 1696 Expect(err.Error()).To(MatchRegexp("token is expired by")) 1697 }) 1698 1699 It("Should detect missing callers", func() { 1700 auth.clientJwtSigners = []string{filepath.Join(td, "public.pem")} 1701 signed := createSignedClientJWT(privateKey, map[string]any{ 1702 "callerid": "", 1703 "purpose": tokens.ClientIDPurpose, 1704 }) 1705 1706 _, err := auth.parseClientIDJWT(signed) 1707 Expect(err).To(MatchError("no callerid in claims")) 1708 }) 1709 1710 It("Should check the purpose field", func() { 1711 auth.clientJwtSigners = []string{filepath.Join(td, "public.pem")} 1712 signed := createSignedClientJWT(privateKey, nil) 1713 _, err := auth.parseClientIDJWT(signed) 1714 Expect(err).To(MatchError(tokens.ErrNotAClientToken)) 1715 1716 signed = createSignedClientJWT(privateKey, map[string]any{ 1717 "purpose": "wrong", 1718 }) 1719 _, err = auth.parseClientIDJWT(signed) 1720 Expect(err).To(MatchError(tokens.ErrNotAClientToken)) 1721 }) 1722 1723 It("Should check the caller", func() { 1724 edPublicKey, _, err := choria.Ed25519KeyPair() 1725 Expect(err).ToNot(HaveOccurred()) 1726 1727 auth.clientJwtSigners = []string{filepath.Join(td, "public.pem")} 1728 signed := createSignedClientJWT(privateKey, map[string]any{ 1729 "purpose": tokens.ClientIDPurpose, 1730 "public_key": hex.EncodeToString(edPublicKey), 1731 }) 1732 1733 claims, err := auth.parseClientIDJWT(signed) 1734 Expect(err).ToNot(HaveOccurred()) 1735 Expect(claims.CallerID).To(Equal("up=ginkgo")) 1736 }) 1737 1738 It("Should check the public key", func() { 1739 auth.clientJwtSigners = []string{filepath.Join(td, "public.pem")} 1740 signed := createSignedClientJWT(privateKey, map[string]any{ 1741 "purpose": tokens.ClientIDPurpose, 1742 }) 1743 1744 claims, err := auth.parseClientIDJWT(signed) 1745 Expect(err).To(MatchError("no public key in claims")) 1746 Expect(claims).To(BeNil()) 1747 }) 1748 1749 It("Should handle file names that are exactly 64 characters long", func() { 1750 if runtime.GOOS == "windows" { 1751 Skip("Not supported on windows") 1752 } 1753 1754 td, err := os.MkdirTemp("/tmp", "") 1755 Expect(err).ToNot(HaveOccurred()) 1756 defer os.RemoveAll(td) 1757 1758 if len(td) > 50 { 1759 Skip("Temp directory is too long for filename test") 1760 } 1761 1762 pkPath := filepath.Join(td, strings.Repeat("x", 65-len(td)-strings.Count(td, string(os.PathSeparator)))) 1763 Expect(pkPath).To(HaveLen(64)) 1764 1765 edPublicKey, edPriKey, err := choria.Ed25519KeyPair() 1766 Expect(err).ToNot(HaveOccurred()) 1767 Expect(os.WriteFile(pkPath, []byte(hex.EncodeToString(edPublicKey)), 0600)).To(Succeed()) 1768 1769 auth.clientJwtSigners = []string{pkPath} 1770 1771 signed := createSignedClientJWT(edPriKey, map[string]any{ 1772 "purpose": tokens.ClientIDPurpose, 1773 "public_key": hex.EncodeToString(edPublicKey), 1774 }) 1775 1776 claims, err := auth.parseClientIDJWT(signed) 1777 Expect(err).ToNot(HaveOccurred()) 1778 Expect(claims.CallerID).To(Equal("up=ginkgo")) 1779 }) 1780 1781 It("Should handle multiple public identifiers", func() { 1782 edPublicKey, edPriKey, err := choria.Ed25519KeyPair() 1783 Expect(err).ToNot(HaveOccurred()) 1784 signed := createSignedClientJWT(edPriKey, map[string]any{ 1785 "purpose": tokens.ClientIDPurpose, 1786 "public_key": hex.EncodeToString(edPublicKey), 1787 }) 1788 1789 // should fail the public key not there 1790 auth.clientJwtSigners = []string{filepath.Join(td, "public.pem")} 1791 claims, err := auth.parseClientIDJWT(signed) 1792 Expect(err).To(MatchError("could not parse client id token: ed25519 public key required")) 1793 Expect(claims).To(BeNil()) 1794 1795 // should now pass after having done a multi check 1796 auth.clientJwtSigners = []string{filepath.Join(td, "public.pem"), "/nonexisting", hex.EncodeToString(edPublicKey)} 1797 claims, err = auth.parseClientIDJWT(signed) 1798 Expect(err).ToNot(HaveOccurred()) 1799 Expect(claims.CallerID).To(Equal("up=ginkgo")) 1800 }) 1801 }) 1802 }) 1803 1804 Describe("setClientPermissions", func() { 1805 var ( 1806 log *logrus.Entry 1807 minSub = []string{"*.reply.>"} 1808 minPub = []string{"$SYS.REQ.USER.INFO"} 1809 ) 1810 1811 BeforeEach(func() { 1812 log = logrus.NewEntry(logrus.New()) 1813 log.Logger.SetOutput(GinkgoWriter) 1814 }) 1815 1816 Describe("System User", func() { 1817 It("Should should set correct permissions", func() { 1818 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{Permissions: &tokens.ClientPermissions{OrgAdmin: true}}, log) 1819 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 1820 Allow: []string{">"}, 1821 })) 1822 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 1823 Allow: []string{">"}, 1824 })) 1825 }) 1826 }) 1827 1828 Describe("Stream Users", func() { 1829 It("Should set no permissions for non choria users", func() { 1830 user.Account = auth.provisioningAccount 1831 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{Permissions: &tokens.ClientPermissions{StreamsUser: true}}, log) 1832 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 1833 Allow: minSub, 1834 })) 1835 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 1836 Allow: []string{"$SYS.REQ.USER.INFO"}, 1837 })) 1838 }) 1839 1840 It("Should set correct permissions for the choria user", func() { 1841 user.Account = auth.choriaAccount 1842 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{Permissions: &tokens.ClientPermissions{StreamsUser: true}}, log) 1843 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 1844 Allow: minSub, 1845 })) 1846 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 1847 Allow: append(minPub, 1848 "$JS.API.INFO", 1849 "$JS.API.STREAM.NAMES", 1850 "$JS.API.STREAM.LIST", 1851 "$JS.API.STREAM.INFO.*", 1852 "$JS.API.STREAM.MSG.GET.*", 1853 "$JS.API.STREAM.MSG.DELETE.*", 1854 "$JS.API.DIRECT.GET.*", 1855 "$JS.API.DIRECT.GET.*.>", 1856 "$JS.API.CONSUMER.CREATE.*", 1857 "$JS.API.CONSUMER.CREATE.*.>", 1858 "$JS.API.CONSUMER.DURABLE.CREATE.*.*", 1859 "$JS.API.CONSUMER.DELETE.*.*", 1860 "$JS.API.CONSUMER.NAMES.*", 1861 "$JS.API.CONSUMER.LIST.*", 1862 "$JS.API.CONSUMER.INFO.*.*", 1863 "$JS.API.CONSUMER.MSG.NEXT.*.*", 1864 "$JS.ACK.>", 1865 "$JS.FC.>", 1866 "$KV.>", 1867 "$O.>"), 1868 })) 1869 }) 1870 }) 1871 1872 Describe("Governor Users", func() { 1873 It("Should not set provisioner permissions", func() { 1874 user.Account = auth.provisioningAccount 1875 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{Permissions: &tokens.ClientPermissions{StreamsUser: true, Governor: true}}, log) 1876 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 1877 Allow: minSub, 1878 })) 1879 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 1880 Allow: minPub, 1881 })) 1882 }) 1883 1884 It("Should set choria permissions", func() { 1885 user.Account = auth.choriaAccount 1886 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{Permissions: &tokens.ClientPermissions{StreamsUser: true, Governor: true}}, log) 1887 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 1888 Allow: minSub, 1889 })) 1890 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 1891 Allow: append(minPub, []string{ 1892 "$JS.API.INFO", 1893 "$JS.API.STREAM.NAMES", 1894 "$JS.API.STREAM.LIST", 1895 "$JS.API.STREAM.INFO.*", 1896 "$JS.API.STREAM.MSG.GET.*", 1897 "$JS.API.STREAM.MSG.DELETE.*", 1898 "$JS.API.DIRECT.GET.*", 1899 "$JS.API.DIRECT.GET.*.>", 1900 "$JS.API.CONSUMER.CREATE.*", 1901 "$JS.API.CONSUMER.CREATE.*.>", 1902 "$JS.API.CONSUMER.DURABLE.CREATE.*.*", 1903 "$JS.API.CONSUMER.DELETE.*.*", 1904 "$JS.API.CONSUMER.NAMES.*", 1905 "$JS.API.CONSUMER.LIST.*", 1906 "$JS.API.CONSUMER.INFO.*.*", 1907 "$JS.API.CONSUMER.MSG.NEXT.*.*", 1908 "$JS.ACK.>", 1909 "$JS.FC.>", 1910 "$KV.>", 1911 "$O.>", 1912 "*.governor.*", 1913 "choria.lifecycle.event.governor.>", 1914 }...), 1915 })) 1916 }) 1917 1918 }) 1919 1920 Describe("Event Viewers", func() { 1921 It("Should set provisioning permissions", func() { 1922 user.Account = auth.provisioningAccount 1923 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{Permissions: &tokens.ClientPermissions{EventsViewer: true}}, log) 1924 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 1925 Allow: append(minSub, "choria.lifecycle.event.*.provision_mode_server"), 1926 })) 1927 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 1928 Allow: minPub, 1929 })) 1930 }) 1931 1932 It("Should set choria permissions", func() { 1933 user.Account = auth.choriaAccount 1934 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{Permissions: &tokens.ClientPermissions{EventsViewer: true}}, log) 1935 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 1936 Allow: append(minSub, "choria.lifecycle.event.>", 1937 "choria.machine.watcher.>", 1938 "choria.machine.transition"), 1939 })) 1940 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 1941 Allow: minPub, 1942 Deny: nil, 1943 })) 1944 }) 1945 }) 1946 1947 Describe("Election Users", func() { 1948 It("Should set provisioning permissions", func() { 1949 user.Account = auth.provisioningAccount 1950 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{Permissions: &tokens.ClientPermissions{ElectionUser: true}}, log) 1951 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 1952 Allow: minSub, 1953 })) 1954 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 1955 Allow: append(minPub, 1956 "choria.streams.STREAM.INFO.KV_CHORIA_LEADER_ELECTION", 1957 "$KV.CHORIA_LEADER_ELECTION.provisioner"), 1958 })) 1959 }) 1960 1961 It("Should set choria permissions", func() { 1962 user.Account = auth.choriaAccount 1963 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{Permissions: &tokens.ClientPermissions{ElectionUser: true}}, log) 1964 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 1965 Allow: minSub, 1966 })) 1967 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 1968 Allow: append(minPub, 1969 "$JS.API.STREAM.INFO.KV_CHORIA_LEADER_ELECTION", 1970 "$KV.CHORIA_LEADER_ELECTION.>"), 1971 })) 1972 }) 1973 }) 1974 Describe("Streams Admin", func() { 1975 It("Should set no permissions for non choria users", func() { 1976 user.Account = auth.provisioningAccount 1977 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{Permissions: &tokens.ClientPermissions{StreamsAdmin: true}}, log) 1978 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 1979 Allow: minSub, 1980 })) 1981 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 1982 Allow: minPub, 1983 })) 1984 }) 1985 1986 It("Should set correct permissions for choria user", func() { 1987 user.Account = auth.choriaAccount 1988 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{Permissions: &tokens.ClientPermissions{StreamsAdmin: true}}, log) 1989 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 1990 Allow: append(minSub, "$JS.EVENT.>"), 1991 })) 1992 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 1993 Allow: append(minPub, "$JS.>"), 1994 })) 1995 }) 1996 }) 1997 1998 Describe("Fleet Management", func() { 1999 It("Should set correct permissions for fleet management users", func() { 2000 user.Account = auth.choriaAccount 2001 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{Permissions: &tokens.ClientPermissions{FleetManagement: true}}, log) 2002 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 2003 Allow: minSub, 2004 })) 2005 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 2006 Allow: append(minPub, "*.broadcast.agent.>", "*.broadcast.service.>", "*.node.>", "choria.federation.*.federation"), 2007 })) 2008 }) 2009 }) 2010 2011 Describe("Additional subjects", func() { 2012 It("Should set the permissions correctly", func() { 2013 user.Account = auth.choriaAccount 2014 auth.setClientPermissions(user, "", &tokens.ClientIDClaims{ 2015 AdditionalSubscribeSubjects: []string{"sub.>"}, 2016 AdditionalPublishSubjects: []string{"pub.>"}, 2017 }, log) 2018 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 2019 Allow: append(minSub, "sub.>"), 2020 })) 2021 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 2022 Allow: append(minPub, "pub.>"), 2023 })) 2024 }) 2025 }) 2026 2027 Describe("Minimal Permissions", func() { 2028 It("Should support caller private reply subjects", func() { 2029 user.Account = auth.choriaAccount 2030 auth.setClientPermissions(user, "u=ginkgo", nil, log) 2031 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 2032 Allow: []string{"*.reply.0f47cbbd2accc01a51e57261d6e64b8b.>"}, 2033 })) 2034 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 2035 Allow: minPub, 2036 })) 2037 }) 2038 2039 It("Should support standard reply subjects", func() { 2040 user.Account = auth.choriaAccount 2041 auth.setClientPermissions(user, "", nil, log) 2042 Expect(user.Permissions.Subscribe).To(Equal(&server.SubjectPermission{ 2043 Allow: []string{"*.reply.>"}, 2044 })) 2045 Expect(user.Permissions.Publish).To(Equal(&server.SubjectPermission{ 2046 Allow: minPub, 2047 })) 2048 }) 2049 }) 2050 }) 2051 2052 Describe("setServerPermissions", func() { 2053 It("Should set correct permissions", func() { 2054 auth.setServerPermissions(user, nil, log) 2055 2056 Expect(user.Permissions.Publish.Allow).To(Equal([]string{ 2057 ">", 2058 })) 2059 2060 Expect(user.Permissions.Publish.Deny).To(Equal([]string{ 2061 "*.broadcast.agent.>", 2062 "*.broadcast.service.>", 2063 "*.node.>", 2064 "choria.federation.*.federation", 2065 })) 2066 2067 Expect(user.Permissions.Subscribe.Allow).To(BeEmpty()) 2068 Expect(user.Permissions.Subscribe.Deny).To(Equal([]string{ 2069 "*.reply.>", 2070 "choria.federation.>", 2071 "choria.lifecycle.>", 2072 })) 2073 }) 2074 2075 It("Should support denying servers", func() { 2076 auth.denyServers = true 2077 auth.setServerPermissions(user, nil, log) 2078 Expect(user.Permissions.Publish.Deny).To(Equal([]string{">"})) 2079 Expect(user.Permissions.Publish.Allow).To(BeNil()) 2080 }) 2081 }) 2082 })