Skip to content

Commit

Permalink
(choria-io#42) spike around OPA
Browse files Browse the repository at this point in the history
  • Loading branch information
ripienaar committed Dec 19, 2019
1 parent 6896221 commit 9dac73f
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 56 deletions.
8 changes: 6 additions & 2 deletions authenticators/userlist/userlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type User struct {
Username string `json:"username"`
Password string `json:"password"`
ACLs []string `json:"acls"`
Policies []string `json:"rego_policies"`
}

// AuthenticatorConfig configures the user/pass authenticator
Expand Down Expand Up @@ -90,15 +91,18 @@ func (a *Authenticator) processLogin(req *models.LoginRequest) (resp *models.Log
return
}

token := jwt.NewWithClaims(jwt.GetSigningMethod("RS512"), jwt.MapClaims{
claims := map[string]interface{}{
"exp": time.Now().UTC().Add(a.validity).Unix(),
"nbf": time.Now().UTC().Add(-1 * time.Minute).Unix(),
"iat": time.Now().UTC().Unix(),
"iss": "Choria Userlist Authenticator",
"callerid": fmt.Sprintf("up=%s", req.Username),
"sub": fmt.Sprintf("up=%s", req.Username),
"rego": user.Policies,
"agents": user.ACLs,
})
}

token := jwt.NewWithClaims(jwt.GetSigningMethod("RS512"), jwt.MapClaims(claims))

signKey, err := a.signKey()
if err != nil {
Expand Down
87 changes: 79 additions & 8 deletions authorizers/actionlist/actionlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package actionlist

import (
"context"
"encoding/json"
"fmt"
"strings"
Expand All @@ -27,6 +28,8 @@ import (
"github.com/choria-io/go-protocol/protocol"
"github.com/choria-io/mcorpc-agent-provider/mcorpc"
jwt "github.com/dgrijalva/jwt-go"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/rego"
"github.com/sirupsen/logrus"
)

Expand Down Expand Up @@ -80,7 +83,7 @@ func (a *Authorizer) authorize(req protocol.Request, claims jwt.MapClaims) (allo
return false, req.Agent(), err
}

ok, err := validateAction(rpcreq.Agent, rpcreq.Action, claims, a.log)
ok, err := validateAction(rpcreq, a.site, claims, a.log)
if err != nil {
a.log.Warnf("Validating request %s from %s@%s for agent %s failed: %s", req.RequestID(), req.CallerID(), req.SenderID(), rpcreq.Agent, err)
}
Expand All @@ -94,16 +97,11 @@ func (a *Authorizer) authorize(req protocol.Request, claims jwt.MapClaims) (allo
return ok, fmt.Sprintf("%s.%s", rpcreq.Agent, rpcreq.Action), err
}

func validateAction(agent string, action string, claims jwt.MapClaims, log *logrus.Entry) (ok bool, err error) {
agents, ok := claims["agents"].([]interface{})
if !ok {
return false, fmt.Errorf("invalid agent claims")
}

func validateAgentsList(agent string, action string, agents []interface{}, log *logrus.Entry) (ok bool, err error) {
for _, allow := range agents {
claim, ok := allow.(string)
if !ok {
return false, fmt.Errorf("invalid agent claim found in token %s", allow)
return false, fmt.Errorf("invalid agent claim found in token %v", allow)
}

// all things are allowed
Expand Down Expand Up @@ -132,3 +130,76 @@ func validateAction(agent string, action string, claims jwt.MapClaims, log *logr
// nothing matched all passed or list is empty, deny all
return false, nil
}

func validateRegoPolicies(req *mcorpc.Request, site string, policies []interface{}, claims jwt.MapClaims, log *logrus.Entry) (ok bool, err error) {
for _, policyi := range policies {
policy, ok := policyi.(string)
if !ok {
return false, fmt.Errorf("invalid rego policy claim found in token %v", policyi)
}

compiler, err := ast.CompileModules(map[string]string{
"choria.rego": policy,
})
if err != nil {
return false, fmt.Errorf("could not compile rego policy claim found in token: %v", err)
}

data := make(map[string]interface{})
err = json.Unmarshal(req.Data, &data)
if err != nil {
return false, fmt.Errorf("could not parse data embedded in request: %v", err)
}

r := rego.New(
rego.Query("data.choria.allow"),
rego.Compiler(compiler),
rego.Input(map[string]interface{}{
"agent": req.Agent,
"action": req.Action,
"data": data,
"sender": req.SenderID,
"collective": req.Collective,
"ttl": req.TTL,
"time": req.Time,
"filter": req.Filter,
"site": site,
"claims": claims,
}),
)

rs, err := r.Eval(context.Background())
if err != nil {
return false, fmt.Errorf("could not evaluate rego policy claim found in token: %v", err)
}

if len(rs) != 1 {
return false, fmt.Errorf("invalid result from rego policy, expected 1 received %d", len(rs))
}

allow, ok := rs[0].Expressions[0].Value.(bool)
if !ok {
return false, fmt.Errorf("did not receive a boolean from rego policy")
}

if !allow {
return false, nil
}
}

return true, nil
}

func validateAction(req *mcorpc.Request, site string, claims jwt.MapClaims, log *logrus.Entry) (ok bool, err error) {
agents, ok := claims["agents"].([]interface{})
if ok {
return validateAgentsList(req.Agent, req.Action, agents, log)
}

policies, ok := claims["rego"].([]interface{})
if ok {
return validateRegoPolicies(req, site, policies, claims,log)
}

return false, fmt.Errorf("neither agents or rego claims were valid")
}
163 changes: 119 additions & 44 deletions authorizers/actionlist/actionlist_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package actionlist

import (
"encoding/json"
"io/ioutil"
"testing"
"time"

"github.com/sirupsen/logrus"

"github.com/choria-io/go-protocol/protocol/v1"
"github.com/choria-io/go-protocol/protocol"
v1 "github.com/choria-io/go-protocol/protocol/v1"
"github.com/choria-io/mcorpc-agent-provider/mcorpc"
jwt "github.com/dgrijalva/jwt-go"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
Expand Down Expand Up @@ -57,56 +60,128 @@ var _ = Describe("Authorizers/Actionlist", func() {
})

Describe("validateAction", func() {
It("Should detect invalid agent claims", func() {
claims := jwt.MapClaims{
"agents": "invalid",
var req *mcorpc.Request

BeforeEach(func() {
req = &mcorpc.Request{
Agent: "agent",
Action: "action",
Data: json.RawMessage(`{"hello":"world"}`),
SenderID: "some.node",
Collective: "ginkgo",
TTL: 60,
Time: time.Now(),
Filter: protocol.NewFilter(),
}

ok, err := validateAction("agent", "action", claims, log)
Expect(err).To(MatchError("invalid agent claims"))
Expect(ok).To(BeFalse())
})

It("Should support '*' agents", func() {
claims := jwt.MapClaims{
"agents": []interface{}{"*"},
}
Describe("OPA", func() {
It("Should execute rego claims", func() {
policy := `
package choria
ok, err := validateAction("agent", "action", claims, log)
Expect(ok).To(BeTrue())
Expect(err).ToNot(HaveOccurred())
})
default allow = false
It("Should support action wildcards", func() {
claims := jwt.MapClaims{
"agents": []interface{}{"rpcutil.*"},
}

ok, err := validateAction("rpcutil", "action", claims, log)
Expect(ok).To(BeTrue())
Expect(err).ToNot(HaveOccurred())
allow {
input.action = "rego"
input.agent = "rpcutil"
input.data.hello = "world"
count(input.filter.fact) > 0
input.collective = "production"
}
ok, err = validateAction("other", "action", claims, log)
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeFalse())
allow {
input.agent = "rpcutil"
input.collective = "development"
}
`
claims := jwt.MapClaims{
"callerid": "up=bob",
"rego": []interface{}{
policy,
},
}

ok, err := validateAction(req, "ginkgo", claims, log)
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeFalse())

req.Agent = "rpcutil"
req.Collective = "development"
ok, err = validateAction(req, "ginkgo", claims, log)
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())

req.Action = "rego"
req.Collective = "production"
req.Filter.AddFactFilter("country", "==", "mt")

ok, err = validateAction(req, "ginkgo", claims, log)
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeTrue())

})
})

It("Should support specific agent.action", func() {
claims := jwt.MapClaims{
"agents": []interface{}{"rpcutil.ping"},
}

ok, err := validateAction("rpcutil", "ping", claims, log)
Expect(ok).To(BeTrue())
Expect(err).ToNot(HaveOccurred())

ok, err = validateAction("rpcutil", "other", claims, log)
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeFalse())

ok, err = validateAction("other", "action", claims, log)
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeFalse())
Describe("Agent List", func() {
It("Should detect invalid agent claims", func() {
claims := jwt.MapClaims{
"agents": "invalid",
}

ok, err := validateAction(req, "ginkgo", claims, log)
Expect(err).To(MatchError("neither agents or rego claims were valid"))
Expect(ok).To(BeFalse())
})

It("Should support '*' agents", func() {
claims := jwt.MapClaims{
"agents": []interface{}{"*"},
}

ok, err := validateAction(req, "ginkgo", claims, log)
Expect(ok).To(BeTrue())
Expect(err).ToNot(HaveOccurred())
})

It("Should support action wildcards", func() {
claims := jwt.MapClaims{
"agents": []interface{}{"rpcutil.*"},
}

req.Agent = "rpcutil"
ok, err := validateAction(req, "ginkgo", claims, log)
Expect(ok).To(BeTrue())
Expect(err).ToNot(HaveOccurred())

req.Agent = "other"
ok, err = validateAction(req, "ginkgo", claims, log)
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeFalse())
})

It("Should support specific agent.action", func() {
claims := jwt.MapClaims{
"agents": []interface{}{"rpcutil.ping"},
}

req.Agent = "rpcutil"
req.Action = "ping"
ok, err := validateAction(req, "ginkgo", claims, log)
Expect(ok).To(BeTrue())
Expect(err).ToNot(HaveOccurred())

req.Action = "other"
ok, err = validateAction(req, "ginkgo", claims, log)
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeFalse())

req.Agent = "other"
req.Action = "action"
ok, err = validateAction(req, "ginkgo", claims, log)
Expect(err).ToNot(HaveOccurred())
Expect(ok).To(BeFalse())
})
})
})
})
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ require (
github.com/jessevdk/go-flags v1.4.0
github.com/nats-io/stan.go v0.5.2
github.com/okta/okta-sdk-golang v0.1.0
github.com/onsi/ginkgo v1.10.3
github.com/onsi/gomega v1.7.1
github.com/onsi/ginkgo v1.11.0
github.com/onsi/gomega v1.8.0
github.com/open-policy-agent/opa v0.16.0
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v1.2.1
Expand Down
Loading

0 comments on commit 9dac73f

Please sign in to comment.