github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/agent/mcorpc/authz_jwt_test.go (about) 1 // Copyright (c) 2022, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package mcorpc 6 7 import ( 8 "crypto/ed25519" 9 "encoding/json" 10 "fmt" 11 "os" 12 "time" 13 14 imock "github.com/choria-io/go-choria/inter/imocks" 15 iu "github.com/choria-io/go-choria/internal/util" 16 "github.com/choria-io/go-choria/protocol" 17 "github.com/choria-io/go-choria/server/agents" 18 "github.com/choria-io/tokens" 19 "github.com/golang/mock/gomock" 20 . "github.com/onsi/ginkgo/v2" 21 . "github.com/onsi/gomega" 22 "github.com/onsi/gomega/gbytes" 23 "github.com/sirupsen/logrus" 24 ) 25 26 var _ = Describe("McoRPC/JWTAuthorizer", func() { 27 var log *logrus.Entry 28 var req *Request 29 var claims *tokens.ClientIDClaims 30 31 readFixture := func(f string) string { 32 c, err := os.ReadFile(f) 33 if err != nil { 34 panic(err) 35 } 36 37 return string(c) 38 } 39 40 BeforeEach(func() { 41 logger := logrus.New() 42 logger.Out = GinkgoWriter 43 logger.Level = logrus.DebugLevel 44 log = logrus.NewEntry(logger) 45 claims = &tokens.ClientIDClaims{} 46 47 req = &Request{ 48 Agent: "myco", 49 Action: "deploy", 50 Data: json.RawMessage(`{"component":"frontend"}`), 51 SenderID: "some.node", 52 Collective: "ginkgo", 53 TTL: 60, 54 Time: time.Now(), 55 Filter: protocol.NewFilter(), 56 } 57 }) 58 59 Describe("aaasvcPolicyAuthorize", func() { 60 var agent *Agent 61 var logBuff *gbytes.Buffer 62 var pubk ed25519.PublicKey 63 var prik ed25519.PrivateKey 64 var err error 65 66 BeforeEach(func() { 67 logBuff = gbytes.NewBuffer() 68 mockctl := gomock.NewController(GinkgoT()) 69 DeferCleanup(func() { 70 mockctl.Finish() 71 }) 72 73 fw, cfg := imock.NewFrameworkForTests(mockctl, logBuff) 74 log = fw.Logger("ginkgo") 75 log.Logger.SetLevel(logrus.DebugLevel) 76 77 agent = &Agent{ 78 meta: &agents.Metadata{Name: "myco"}, 79 Log: log, 80 Config: cfg, 81 Choria: fw, 82 } 83 84 pubk, prik, err = iu.Ed25519KeyPair() 85 Expect(err).ToNot(HaveOccurred()) 86 }) 87 88 It("Should fail for no caller public data", func() { 89 allowed, err := aaasvcPolicyAuthorize(req, agent, log) 90 Expect(err).To(MatchError("no policy received in request")) 91 Expect(allowed).To(BeFalse()) 92 }) 93 94 It("Should handle invalid tokens", func() { 95 req.CallerPublicData = "blah" 96 allowed, err := aaasvcPolicyAuthorize(req, agent, log) 97 Expect(err).To(MatchError("invalid token in request: token contains an invalid number of segments")) 98 Expect(allowed).To(BeFalse()) 99 }) 100 101 It("Should allow discovery agent", func() { 102 claims, err = tokens.NewClientIDClaims("ginkgo", nil, "choria", nil, "", "", time.Hour, nil, pubk) 103 Expect(err).ToNot(HaveOccurred()) 104 req.CallerPublicData, err = tokens.SignToken(claims, prik) 105 Expect(err).ToNot(HaveOccurred()) 106 107 req.Agent = "discovery" 108 allowed, err := aaasvcPolicyAuthorize(req, agent, log) 109 Expect(err).ToNot(HaveOccurred()) 110 Expect(allowed).To(BeTrue()) 111 Expect(logBuff).To(gbytes.Say("Allowing discovery request")) 112 }) 113 114 It("Should require a policy", func() { 115 claims, err = tokens.NewClientIDClaims("ginkgo", nil, "choria", nil, "", "", time.Hour, nil, pubk) 116 Expect(err).ToNot(HaveOccurred()) 117 req.CallerPublicData, err = tokens.SignToken(claims, prik) 118 Expect(err).ToNot(HaveOccurred()) 119 120 allowed, err := aaasvcPolicyAuthorize(req, agent, log) 121 Expect(err).To(MatchError("no policy received in token")) 122 Expect(allowed).To(BeFalse()) 123 }) 124 125 Context("Allowed Agents", func() { 126 It("Should handle failures", func() { 127 claims, err = tokens.NewClientIDClaims("ginkgo", []string{"fail"}, "choria", nil, "", "", time.Hour, nil, pubk) 128 Expect(err).ToNot(HaveOccurred()) 129 req.CallerPublicData, err = tokens.SignToken(claims, prik) 130 Expect(err).ToNot(HaveOccurred()) 131 132 allowed, err := aaasvcPolicyAuthorize(req, agent, log) 133 Expect(err).To(MatchError("invalid agent policy: fail")) 134 Expect(allowed).To(BeFalse()) 135 }) 136 137 It("Should allow valid requests", func() { 138 claims, err = tokens.NewClientIDClaims("ginkgo", []string{"myco.deploy"}, "choria", nil, "", "", time.Hour, nil, pubk) 139 Expect(err).ToNot(HaveOccurred()) 140 req.CallerPublicData, err = tokens.SignToken(claims, prik) 141 Expect(err).ToNot(HaveOccurred()) 142 143 allowed, err := aaasvcPolicyAuthorize(req, agent, log) 144 Expect(err).ToNot(HaveOccurred()) 145 Expect(allowed).To(BeTrue()) 146 }) 147 }) 148 149 Context("OPA Policy", func() { 150 It("Should handle failures", func() { 151 claims, err = tokens.NewClientIDClaims("ginkgo", nil, "choria", nil, "invalid rego", "", time.Hour, nil, pubk) 152 Expect(err).ToNot(HaveOccurred()) 153 req.CallerPublicData, err = tokens.SignToken(claims, prik) 154 Expect(err).ToNot(HaveOccurred()) 155 156 allowed, err := aaasvcPolicyAuthorize(req, agent, log) 157 Expect(err).To(HaveOccurred()) 158 Expect(err.Error()).To(MatchRegexp("could not initialize opa evaluator")) 159 Expect(allowed).To(BeFalse()) 160 }) 161 162 It("Should allow valid requests", func() { 163 claims, err = tokens.NewClientIDClaims("ginkgo", nil, "choria", nil, readFixture("testdata/policies/rego/aaa_scenario1.rego"), "", time.Hour, nil, pubk) 164 Expect(err).ToNot(HaveOccurred()) 165 req.CallerPublicData, err = tokens.SignToken(claims, prik) 166 Expect(err).ToNot(HaveOccurred()) 167 168 allowed, err := aaasvcPolicyAuthorize(req, agent, log) 169 Expect(err).ToNot(HaveOccurred()) 170 Expect(allowed).To(BeTrue()) 171 }) 172 }) 173 }) 174 175 Describe("EvaluateAgentListPolicy", func() { 176 It("Should support '*' agents", func() { 177 ok, err := EvaluateAgentListPolicy("agent", "action", []string{"*"}, log) 178 Expect(ok).To(BeTrue()) 179 Expect(err).ToNot(HaveOccurred()) 180 }) 181 182 It("Should support action wildcards", func() { 183 ok, err := EvaluateAgentListPolicy("rpcutil", "action", []string{"rpcutil.*"}, log) 184 Expect(ok).To(BeTrue()) 185 Expect(err).ToNot(HaveOccurred()) 186 187 ok, err = EvaluateAgentListPolicy("other", "action", []string{"rpcutil.*"}, log) 188 Expect(err).ToNot(HaveOccurred()) 189 Expect(ok).To(BeFalse()) 190 }) 191 192 It("Should support specific agent.action", func() { 193 ok, err := EvaluateAgentListPolicy("rpcutil", "ping", []string{"rpcutil.ping"}, log) 194 Expect(ok).To(BeTrue()) 195 Expect(err).ToNot(HaveOccurred()) 196 197 ok, err = EvaluateAgentListPolicy("rpcutil", "other", []string{"rpcutil.ping"}, log) 198 Expect(err).ToNot(HaveOccurred()) 199 Expect(ok).To(BeFalse()) 200 201 ok, err = EvaluateAgentListPolicy("other", "action", []string{"rpcutil.ping"}, log) 202 Expect(err).ToNot(HaveOccurred()) 203 Expect(ok).To(BeFalse()) 204 }) 205 206 It("Should handle invalid policies", func() { 207 ok, err := EvaluateAgentListPolicy("rpcutil", "ping", []string{"rpcutil"}, log) 208 Expect(ok).To(BeFalse()) 209 Expect(err).To(MatchError("invalid agent policy: rpcutil")) 210 211 }) 212 }) 213 214 Describe("EvaluateOpenPolicyAgentPolicy", func() { 215 It("Should allow common scenarios", func() { 216 req.Filter.AddClassFilter("apache") 217 req.Filter.AddIdentityFilter("some.node") 218 req.Filter.AddFactFilter("country", "==", "mt") 219 220 claims.CallerID = "up=bob" 221 claims.UserProperties = map[string]string{ 222 "group": "admins", 223 } 224 225 for r := 1; r <= 5; r++ { 226 policy := readFixture(fmt.Sprintf("testdata/policies/rego/aaa_scenario%d.rego", r)) 227 claims.OPAPolicy = policy 228 229 allowed, err := EvaluateOpenPolicyAgentPolicy(req, policy, claims, "ginkgo", log) 230 Expect(err).ToNot(HaveOccurred()) 231 Expect(allowed).To(BeTrue()) 232 } 233 }) 234 235 It("Should fail on all common scenarios", func() { 236 policy := readFixture("testdata/policies/rego/aaa_scenario5.rego") 237 claims.OPAPolicy = policy 238 claims.CallerID = "up=bob" 239 claims.UserProperties = map[string]string{ 240 "group": "admins", 241 } 242 243 req.Filter.AddClassFilter("apache") 244 req.Filter.AddIdentityFilter("some.node") 245 req.Filter.AddFactFilter("country", "==", "mt") 246 247 allowed, err := EvaluateOpenPolicyAgentPolicy(req, policy, claims, "ginkgo", log) 248 Expect(err).ToNot(HaveOccurred()) 249 Expect(allowed).To(BeTrue()) 250 251 allowed, err = EvaluateOpenPolicyAgentPolicy(req, policy, claims, "x", log) 252 Expect(err).ToNot(HaveOccurred()) 253 Expect(allowed).To(BeFalse()) 254 }) 255 }) 256 })