github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/agent/mcorpc/authz_rego_test.go (about) 1 // Copyright (c) 2020-2021, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package mcorpc 6 7 import ( 8 "context" 9 "encoding/json" 10 "fmt" 11 "os" 12 "time" 13 14 "github.com/choria-io/go-choria/config" 15 "github.com/choria-io/go-choria/filter/classes" 16 "github.com/choria-io/go-choria/inter" 17 imock "github.com/choria-io/go-choria/inter/imocks" 18 "github.com/choria-io/go-choria/protocol" 19 "github.com/choria-io/go-choria/server/agents" 20 "github.com/golang/mock/gomock" 21 . "github.com/onsi/ginkgo/v2" 22 . "github.com/onsi/gomega" 23 ) 24 25 var _ = Describe("RegoPolicy", func() { 26 var ( 27 mockctl *gomock.Controller 28 fw *imock.MockFramework 29 cfg *config.Config 30 requests = make(chan inter.ConnectorMessage) 31 authz *regoPolicy 32 conn *imock.MockConnector 33 connInfo *imock.MockConnectorInfo 34 srvInfo *MockServerInfoSource 35 ctx context.Context 36 am *agents.Manager 37 facts json.RawMessage 38 err error 39 ) 40 41 BeforeEach(func() { 42 mockctl = gomock.NewController(GinkgoT()) 43 fw, cfg = imock.NewFrameworkForTests(mockctl, GinkgoWriter) 44 fw.EXPECT().ProvisionMode().Return(false).AnyTimes() 45 }) 46 47 AfterEach(func() { 48 mockctl.Finish() 49 }) 50 51 Describe(" tests", func() { 52 BeforeEach(func() { 53 cfg.ClassesFile = "testdata/policies/rego/classes.txt" 54 cfg.FactSourceFile = "testdata/policies/rego/facts.json" 55 cfg.ConfigFile = "testdata/server.conf" 56 cfg.DisableSecurityProviderVerify = true 57 58 conn = imock.NewMockConnector(mockctl) 59 conn.EXPECT().QueueSubscribe(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 60 conn.EXPECT().AgentBroadcastTarget(gomock.AssignableToTypeOf("collective"), gomock.AssignableToTypeOf("agent")).DoAndReturn(func(c, a string) string { 61 return fmt.Sprintf("broadcast.%s.%s", c, a) 62 }).AnyTimes() 63 64 connInfo = imock.NewMockConnectorInfo(mockctl) 65 66 facts, err = os.ReadFile(cfg.FactSourceFile) 67 Expect(err).ToNot(HaveOccurred()) 68 Expect(facts).ToNot(BeEmpty()) 69 70 klasses, err := classes.ReadClasses(cfg.ClassesFile) 71 Expect(err).ToNot(HaveOccurred()) 72 73 srvInfo = NewMockServerInfoSource(mockctl) 74 srvInfo.EXPECT().Classes().Return(klasses).AnyTimes() 75 srvInfo.EXPECT().KnownAgents().Return([]string{"ginkgo", "stub_agent", "buts_agent"}).AnyTimes() 76 srvInfo.EXPECT().Facts().DoAndReturn(func() json.RawMessage { return facts }) 77 78 ctx = context.Background() 79 80 am = agents.New(requests, fw, connInfo, srvInfo, fw.Logger("test")) 81 testAgents := []string{"stub_agent", "buts_agent"} 82 83 // Additional agents for testing comparisons in rego files 84 for i := range testAgents { 85 metadata := &agents.Metadata{ 86 Name: testAgents[i], 87 Author: "stub@example.com", 88 License: "Apache-2.0", 89 Timeout: 10, 90 URL: "https://choria.io", 91 Version: "1.0.0", 92 } 93 94 agent := New(metadata.Name, metadata, am.Choria(), fw.Logger("test")) 95 action := func(ctx context.Context, req *Request, reply *Reply, agent *Agent, conn inter.ConnectorInfo) {} 96 agent.MustRegisterAction("boop", action) 97 Expect(err).ToNot(HaveOccurred()) 98 99 agent.SetServerInfo(srvInfo) 100 101 err = am.RegisterAgent(ctx, metadata.Name, agent, conn) 102 // err = cn.ServerInstance().RegisterAgent(ctx, agent.Name(), agent) 103 Expect(err).ToNot(HaveOccurred()) 104 } 105 106 metadata := &agents.Metadata{ 107 Name: "ginkgo", 108 Author: "stub@example.com", 109 License: "Apache-2.0", 110 Timeout: 10, 111 URL: "https://choria.io", 112 Version: "1.0.0", 113 } 114 115 ginkgoAgent := New(metadata.Name, metadata, am.Choria(), fw.Logger("test")) 116 action := func(ctx context.Context, req *Request, reply *Reply, agent *Agent, conn inter.ConnectorInfo) {} 117 ginkgoAgent.MustRegisterAction("boop", action) 118 ginkgoAgent.SetServerInfo(srvInfo) 119 Expect(err).ToNot(HaveOccurred()) 120 121 authz = ®oPolicy{ 122 cfg: cfg, 123 log: fw.Logger("x"), 124 req: &Request{ 125 Agent: ginkgoAgent.meta.Name, 126 Action: "boop", 127 CallerID: "choria=ginkgo.mcollective", 128 Data: json.RawMessage(`{"foo": "bar"}`), 129 TTL: 60, 130 Time: time.Now(), 131 Filter: protocol.NewFilter(), 132 }, 133 agent: ginkgoAgent, 134 } 135 }) 136 137 AfterEach(func() { 138 mockctl.Finish() 139 // cn.Stop() 140 ctx.Done() 141 }) 142 143 Describe("Basic tests", func() { 144 Context("When the user agent or caller is right", func() { 145 It("Should succeed", func() { 146 auth, err := authz.authorize() 147 Expect(err).ToNot(HaveOccurred()) 148 Expect(auth).To(BeTrue()) 149 }) 150 151 It("Default policy should fail", func() { 152 authz.agent.meta.Name = "boop" 153 auth, err := authz.authorize() 154 155 Expect(err).ToNot(HaveOccurred()) 156 Expect(auth).To(BeFalse()) 157 }) 158 159 }) 160 161 Context("When facts are correct", func() { 162 It("Should succeed", func() { 163 authz.agent.meta.Name = "facts" 164 auth, err := authz.authorize() 165 Expect(err).ToNot(HaveOccurred()) 166 Expect(auth).To(BeTrue()) 167 168 }) 169 }) 170 171 Context("When classes are present and available", func() { 172 It("Should succeed", func() { 173 authz.agent.meta.Name = "classes" 174 auth, err := authz.authorize() 175 176 Expect(err).ToNot(HaveOccurred()) 177 Expect(auth).To(BeTrue()) 178 }) 179 }) 180 }) 181 182 Describe("Failing tests", func() { 183 Context("When the user agent or caller is wrong", func() { 184 It("Should fail if agent isn't ginkgo", func() { 185 authz.req.CallerID = "not=it" 186 auth, err := authz.authorize() 187 188 Expect(err).ToNot(HaveOccurred()) 189 Expect(auth).To(BeFalse()) 190 }) 191 192 It("Should fail with a default policy", func() { 193 authz.req.CallerID = "not=it" 194 authz.agent.meta.Name = "boop" 195 Expect(authz.agent.Name()).To(Equal("boop")) 196 197 authz.cfg.SetOption("plugin.regopolicy.enable_default", "y") 198 auth, err := authz.authorize() 199 200 Expect(err).ToNot(HaveOccurred()) 201 Expect(auth).To(BeFalse()) 202 }) 203 }) 204 }) 205 206 Describe("Agents", func() { 207 Context("If agent exists on the server", func() { 208 It("Should succeed", func() { 209 authz.agent.meta.Name = "agent" 210 auth, err := authz.authorize() 211 212 Expect(err).ToNot(HaveOccurred()) 213 Expect(auth).To(BeTrue()) 214 }) 215 }) 216 }) 217 218 Describe("Request data", func() { 219 Context("It should succeed if the request parameters are set right", func() { 220 It("Should succeed", func() { 221 authz.agent.meta.Name = "data" 222 auth, err := authz.authorize() 223 224 Expect(err).ToNot(HaveOccurred()) 225 Expect(auth).To(BeTrue()) 226 }) 227 }) 228 }) 229 }) 230 231 Describe("Auth deny tests", func() { 232 BeforeEach(func() { 233 cfg.ClassesFile = "testdata/policies/rego/classes_fail.txt" 234 cfg.FactSourceFile = "testdata/policies/rego/facts_fail.json" 235 cfg.ConfigFile = "testdata/server.conf" 236 cfg.DisableSecurityProviderVerify = true 237 238 conn = imock.NewMockConnector(mockctl) 239 conn.EXPECT().QueueSubscribe(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 240 conn.EXPECT().AgentBroadcastTarget(gomock.AssignableToTypeOf("collective"), gomock.AssignableToTypeOf("agent")).DoAndReturn(func(c, a string) string { 241 return fmt.Sprintf("broadcast.%s.%s", c, a) 242 }).AnyTimes() 243 244 connInfo = imock.NewMockConnectorInfo(mockctl) 245 246 facts, err = os.ReadFile(cfg.FactSourceFile) 247 Expect(err).ToNot(HaveOccurred()) 248 Expect(facts).ToNot(BeEmpty()) 249 250 klasses, err := classes.ReadClasses(cfg.ClassesFile) 251 Expect(err).ToNot(HaveOccurred()) 252 253 srvInfo = NewMockServerInfoSource(mockctl) 254 srvInfo.EXPECT().Classes().Return(klasses).AnyTimes() 255 srvInfo.EXPECT().KnownAgents().Return([]string{"ginkgo"}).AnyTimes() 256 srvInfo.EXPECT().Facts().DoAndReturn(func() json.RawMessage { return facts }).AnyTimes() 257 258 ctx = context.Background() 259 260 am = agents.New(requests, fw, conn, srvInfo, fw.Logger("test")) 261 262 metadata := &agents.Metadata{ 263 Name: "ginkgo", 264 Author: "stub@example.com", 265 License: "Apache-2.0", 266 Timeout: 10, 267 URL: "https://choria.io", 268 Version: "1.0.0", 269 } 270 271 ginkgoAgent := New(metadata.Name, metadata, am.Choria(), fw.Logger("test")) 272 action := func(ctx context.Context, req *Request, reply *Reply, agent *Agent, conn inter.ConnectorInfo) {} 273 ginkgoAgent.MustRegisterAction("boop", action) 274 Expect(err).ToNot(HaveOccurred()) 275 276 err = am.RegisterAgent(ctx, ginkgoAgent.Name(), ginkgoAgent, conn) 277 Expect(err).ToNot(HaveOccurred()) 278 Expect(ginkgoAgent.ServerInfoSource.Facts()).ToNot(BeNil()) 279 280 authz = ®oPolicy{ 281 cfg: cfg, 282 log: fw.Logger(""), 283 req: &Request{ 284 Agent: ginkgoAgent.meta.Name, 285 Action: "boop", 286 CallerID: "choria=rip.mcollective", 287 SenderID: "choria=rip.mcollective", 288 Data: json.RawMessage(`{"bar": "foo"}`), // reversed from above 289 TTL: 60, 290 Time: time.Now(), 291 Filter: protocol.NewFilter(), 292 }, 293 agent: ginkgoAgent, 294 } 295 296 }) 297 298 AfterEach(func() { 299 ctx.Done() 300 }) 301 302 Describe("Basic tests", func() { 303 Context("When the user agent or caller is wrong", func() { 304 It("Should deny", func() { 305 auth, err := authz.authorize() 306 Expect(err).ToNot(HaveOccurred()) 307 Expect(auth).To(BeFalse()) 308 }) 309 310 It("Default policy should fail", func() { 311 authz.agent.meta.Name = "boop" 312 auth, err := authz.authorize() 313 314 Expect(err).ToNot(HaveOccurred()) 315 Expect(auth).To(BeFalse()) 316 }) 317 318 }) 319 320 Context("When facts are incorrect", func() { 321 It("Should deny", func() { 322 323 authz.agent.meta.Name = "facts" 324 auth, err := authz.authorize() 325 Expect(err).ToNot(HaveOccurred()) 326 Expect(auth).To(BeFalse()) 327 328 }) 329 }) 330 331 Context("When classes are different but available", func() { 332 It("Should fail", func() { 333 authz.agent.meta.Name = "classes" 334 auth, err := authz.authorize() 335 336 Expect(err).ToNot(HaveOccurred()) 337 Expect(auth).To(BeFalse()) 338 }) 339 }) 340 }) 341 342 Describe("Agents", func() { 343 Context("If agent does not exist on the server", func() { 344 It("Should fail", func() { 345 authz.agent.meta.Name = "agent" 346 auth, err := authz.authorize() 347 348 Expect(err).ToNot(HaveOccurred()) 349 Expect(auth).To(BeFalse()) 350 }) 351 }) 352 }) 353 354 Describe("Request data", func() { 355 Context("The request parameters aren't set right", func() { 356 It("Should fail", func() { 357 authz.agent.meta.Name = "data" 358 auth, err := authz.authorize() 359 360 Expect(err).ToNot(HaveOccurred()) 361 Expect(auth).To(BeFalse()) 362 }) 363 }) 364 }) 365 }) 366 367 Describe("Multiple allow statement tests", func() { 368 BeforeEach(func() { 369 cfg.ClassesFile = "testdata/policies/rego/classes_fail.txt" 370 cfg.FactSourceFile = "testdata/policies/rego/facts_fail.json" 371 cfg.ConfigFile = "testdata/server.conf" 372 cfg.DisableSecurityProviderVerify = true 373 374 conn = imock.NewMockConnector(mockctl) 375 conn.EXPECT().QueueSubscribe(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() 376 conn.EXPECT().AgentBroadcastTarget(gomock.AssignableToTypeOf("collective"), gomock.AssignableToTypeOf("agent")).DoAndReturn(func(c, a string) string { 377 return fmt.Sprintf("broadcast.%s.%s", c, a) 378 }).AnyTimes() 379 380 connInfo = imock.NewMockConnectorInfo(mockctl) 381 382 facts, err = os.ReadFile(cfg.FactSourceFile) 383 Expect(err).ToNot(HaveOccurred()) 384 Expect(facts).ToNot(BeEmpty()) 385 386 klasses, err := classes.ReadClasses(cfg.ClassesFile) 387 Expect(err).ToNot(HaveOccurred()) 388 389 srvInfo = NewMockServerInfoSource(mockctl) 390 srvInfo.EXPECT().Classes().Return(klasses).AnyTimes() 391 srvInfo.EXPECT().KnownAgents().Return([]string{"ginkgo"}).AnyTimes() 392 srvInfo.EXPECT().Facts().DoAndReturn(func() json.RawMessage { return facts }).AnyTimes() 393 394 ctx = context.Background() 395 396 am = agents.New(requests, fw, conn, srvInfo, fw.Logger("test")) 397 398 }) 399 400 AfterEach(func() { 401 ctx.Done() 402 }) 403 404 Describe("With the agent set to gingko", func() { 405 BeforeEach(func() { 406 metadata := &agents.Metadata{ 407 Name: "ginkgo", 408 Author: "stub@example.com", 409 License: "Apache-2.0", 410 Timeout: 10, 411 URL: "https://choria.io", 412 Version: "1.0.0", 413 } 414 415 ginkgoAgent := New(metadata.Name, metadata, am.Choria(), fw.Logger("test")) 416 action := func(ctx context.Context, req *Request, reply *Reply, agent *Agent, conn inter.ConnectorInfo) {} 417 ginkgoAgent.MustRegisterAction("boop", action) 418 Expect(err).ToNot(HaveOccurred()) 419 420 err = am.RegisterAgent(ctx, ginkgoAgent.Name(), ginkgoAgent, conn) 421 Expect(err).ToNot(HaveOccurred()) 422 Expect(ginkgoAgent.ServerInfoSource.Facts()).ToNot(BeNil()) 423 424 authz = ®oPolicy{ 425 cfg: cfg, 426 log: fw.Logger(""), 427 req: &Request{ 428 Agent: ginkgoAgent.meta.Name, 429 Action: "boop", 430 CallerID: "choria=rip.mcollective", 431 SenderID: "choria=rip.mcollective", 432 Data: json.RawMessage(`{"bar": "foo"}`), // reversed from above 433 TTL: 60, 434 Time: time.Now(), 435 Filter: protocol.NewFilter(), 436 }, 437 agent: ginkgoAgent, 438 } 439 }) 440 441 Context("with multiple allow statements", func() { 442 It("Should allow", func() { 443 authz.agent.meta.Name = "multiple" 444 auth, err := authz.authorize() 445 446 Expect(err).ToNot(HaveOccurred()) 447 Expect(auth).To(BeTrue()) 448 }) 449 }) 450 }) 451 452 Describe("With the agent set to other", func() { 453 BeforeEach(func() { 454 metadata := &agents.Metadata{ 455 Name: "other", 456 Author: "stub@example.com", 457 License: "Apache-2.0", 458 Timeout: 10, 459 URL: "https://choria.io", 460 Version: "1.0.0", 461 } 462 463 ginkgoAgent := New(metadata.Name, metadata, am.Choria(), fw.Logger("test")) 464 action := func(ctx context.Context, req *Request, reply *Reply, agent *Agent, conn inter.ConnectorInfo) {} 465 ginkgoAgent.MustRegisterAction("boop", action) 466 Expect(err).ToNot(HaveOccurred()) 467 468 err = am.RegisterAgent(ctx, ginkgoAgent.Name(), ginkgoAgent, conn) 469 Expect(err).ToNot(HaveOccurred()) 470 Expect(ginkgoAgent.ServerInfoSource.Facts()).ToNot(BeNil()) 471 472 authz = ®oPolicy{ 473 cfg: cfg, 474 log: fw.Logger(""), 475 req: &Request{ 476 Agent: ginkgoAgent.meta.Name, 477 Action: "poob", 478 CallerID: "choria=rip.mcollective", 479 SenderID: "choria=rip.mcollective", 480 Data: json.RawMessage(`{"bar": "foo"}`), // reversed from above 481 TTL: 60, 482 Time: time.Now(), 483 Filter: protocol.NewFilter(), 484 }, 485 agent: ginkgoAgent, 486 } 487 }) 488 489 Context("with multiple allow statements", func() { 490 It("Should allow", func() { 491 authz.agent.meta.Name = "multiple" 492 auth, err := authz.authorize() 493 494 Expect(err).ToNot(HaveOccurred()) 495 Expect(auth).To(BeTrue()) 496 }) 497 }) 498 }) 499 500 Describe("With the agent set to somethingelse", func() { 501 BeforeEach(func() { 502 metadata := &agents.Metadata{ 503 Name: "somethingelse", 504 Author: "stub@example.com", 505 License: "Apache-2.0", 506 Timeout: 10, 507 URL: "https://choria.io", 508 Version: "1.0.0", 509 } 510 511 ginkgoAgent := New(metadata.Name, metadata, am.Choria(), fw.Logger("test")) 512 action := func(ctx context.Context, req *Request, reply *Reply, agent *Agent, conn inter.ConnectorInfo) {} 513 ginkgoAgent.MustRegisterAction("boop", action) 514 Expect(err).ToNot(HaveOccurred()) 515 516 err = am.RegisterAgent(ctx, ginkgoAgent.Name(), ginkgoAgent, conn) 517 Expect(err).ToNot(HaveOccurred()) 518 Expect(ginkgoAgent.ServerInfoSource.Facts()).ToNot(BeNil()) 519 520 authz = ®oPolicy{ 521 cfg: cfg, 522 log: fw.Logger(""), 523 req: &Request{ 524 Agent: ginkgoAgent.meta.Name, 525 Action: "poob", 526 CallerID: "choria=rip.mcollective", 527 SenderID: "choria=rip.mcollective", 528 Data: json.RawMessage(`{"bar": "foo"}`), // reversed from above 529 TTL: 60, 530 Time: time.Now(), 531 Filter: protocol.NewFilter(), 532 }, 533 agent: ginkgoAgent, 534 } 535 }) 536 537 Context("with multiple allow statements", func() { 538 It("Should deny", func() { 539 authz.agent.meta.Name = "multiple" 540 auth, err := authz.authorize() 541 542 Expect(err).ToNot(HaveOccurred()) 543 Expect(auth).To(BeFalse()) 544 }) 545 }) 546 }) 547 }) 548 })