Skip to content

Commit

Permalink
Merge pull request #3 from iskey/iskey/evaluate
Browse files Browse the repository at this point in the history
Fix Evaluate logic
  • Loading branch information
swaroopar authored Sep 25, 2023
2 parents af67568 + 787aeff commit 90b0d9b
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 49 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,20 @@ Flags:
-p, --port string The port of the HTTP server
```
## Eval a policy
## Evaluate the input by a policy list
Only `allow` and `deny` will be evaluated. If the variable `allow` be evaluated as false, or the variable `deny` be
evaluated as true, The policy will be evaluated as false.
```shell
$ curl -X POST http://localhost:8080/evaluate/policies -H 'Content-Type: application/json' -d '
{
"rego_list": [{
"policy": "import future.keywords.if\nimport future.keywords.in\n\ndefault allow := false\n\nallow if {\n input.method == \"GET\"\n input.path == [\"salary\", input.subject.user]\n}\n\nallow if is_admin\n\nis_admin if \"admin\" in input.subject.groups",
"isAllow": true
}
"policy_list": [
"import future.keywords.if\nimport future.keywords.in\n\ndefault allow := false\n\nallow if {\n input.method == \"GET\"\n input.path == [\"salary\", input.subject.user]\n}\n\nallow if is_admin\n\nis_admin if \"admin\" in input.subject.groups",
"import future.keywords.if\nimport future.keywords.in\n\ndefault deny := false\n\nallow if {\n input.method == \"GET\"\n input.path == [\"salary\", input.subject.user]\n}\n\nallow if is_admin\n\nis_admin if \"admin\" in input.subject.groups"
],
"input": "{\"method\":\"GET\",\"path\":[\"salary\",\"bob\"],\"subject\":{\"user\":\"bob\",\"groups\":[\"sales\",\"marketing\"]}}"
}'
"input": "{\"method\":\"GET\",\"path\":[\"salary\",\"bob\"],\"subject\":{\"user\":\"bob\",\"groups\":[\"sales\",\"marketing\"]}}"
}'
{"isSuccessful":true}
```
4 changes: 2 additions & 2 deletions log/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ func InitLog(level, path string) error {
basic.SetFormatter(&logrus.JSONFormatter{})
} else {
basic.Formatter = &logrus.TextFormatter{
TimestampFormat: "2023/01/01 - 01:01:01",
TimestampFormat: "2006/01/02 - 15:04:05",
FullTimestamp: true,
}

basic.Formatter = &logrus.TextFormatter{
TimestampFormat: "2023/01/01 - 01:01:01",
TimestampFormat: "2006/01/02 - 15:04:05",
FullTimestamp: true,
}
}
Expand Down
20 changes: 20 additions & 0 deletions server/handle_common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* SPDX-License-Identifier: Apache-2.0
* SPDX-FileCopyrightText: Huawei Inc.
*/

package server

import "github.com/gin-gonic/gin"

type ErrorResult struct {
ErrCode int `json:"err_code,omitempty"`
ErrMsg string `json:"err_msg,omitempty"`
}

func abortWithError(c *gin.Context, code int, message string) {
c.AbortWithStatusJSON(code, ErrorResult{
ErrCode: code,
ErrMsg: message,
})
}
25 changes: 25 additions & 0 deletions server/handle_health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* SPDX-License-Identifier: Apache-2.0
* SPDX-FileCopyrightText: Huawei Inc.
*/

package server

import "github.com/gin-gonic/gin"

type HealthStatus string

const (
healthOk HealthStatus = "OK"
)

type HealthStatusResponse struct {
HealthStatus HealthStatus `json:"healthStatus"`
}

func healthHandler(c *gin.Context) {
c.JSON(200, HealthStatusResponse{
HealthStatus: healthOk,
})
return
}
106 changes: 66 additions & 40 deletions server/handle_policy_eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,35 +17,27 @@ import (
"github.com/open-policy-agent/opa/rego"
"net/http"
"runtime/debug"
"strings"
)

type EvalRego struct {
Policy string `json:"policy" binding:"required"`
IsAllow bool `json:"isAllow" binding:"required"`
Policy string `json:"policy" binding:"required"`
}

type EvalCmd struct {
Rego EvalRego `json:"rego" binding:"required"`
Input string `json:"input" binding:"required"`
Policy string `json:"policy" binding:"required"`
Input string `json:"input" binding:"required"`
}

type EvalCmdList struct {
Input string `json:"input" binding:"required"`
RegoList []EvalRego `json:"rego_list" binding:"required"`
Input string `json:"input" binding:"required"`
PolicyList []string `json:"policy_list" binding:"required"`
}

func abortWithError(c *gin.Context, code int, message string) {
c.AbortWithStatusJSON(code, gin.H{
"code": code,
"message": message,
})
}

func healthHandler(c *gin.Context) {
c.JSON(200, gin.H{
"healthStatus": "OK",
})
return
type EvalResult struct {
Input string `json:"input,omitempty"`
Policy string `json:"policy,omitempty"`
IsSuccessful bool `json:"isSuccessful"`
}

func policiesEvaluateHandler(_ *config.Conf) gin.HandlerFunc {
Expand All @@ -59,21 +51,24 @@ func policiesEvaluateHandler(_ *config.Conf) gin.HandlerFunc {
return
}

for _, rego := range cmdList.RegoList {
decision, err := policyQuery(rego.Policy, rego.IsAllow, cmdList.Input)
for _, policy := range cmdList.PolicyList {
decision, err := policyQuery(policy, cmdList.Input)
if err != nil {
abortWithError(c, 500, err.Error())
return
}
if rego.IsAllow && !decision || !rego.IsAllow && decision {
c.JSON(200, gin.H{
"isSuccessful": false,
if !decision {
c.JSON(200, EvalResult{
IsSuccessful: false,
Policy: policy,
Input: cmdList.Input,
})
return
}
}

c.JSON(200, gin.H{
"isSuccessful": true,
c.JSON(200, EvalResult{
IsSuccessful: true,
})
return
}
Expand All @@ -90,31 +85,50 @@ func policyEvaluateHandler(_ *config.Conf) gin.HandlerFunc {
return
}

decision, err := policyQuery(cmd.Rego.Policy, cmd.Rego.IsAllow, cmd.Input)
decision, err := policyQuery(cmd.Policy, cmd.Input)
if err != nil {
abortWithError(c, 500, err.Error())
return
}

c.JSON(200, gin.H{
"isAllow": cmd.Rego.IsAllow,
"isSuccessful": decision,
c.JSON(200, EvalResult{
IsSuccessful: decision,
})
return
}
}

func policyQuery(policyRego string, isAllow bool, input interface{}) (decision bool, err error) {
func policyQuery(policyRego string, input interface{}) (decision bool, err error) {

policyRegoEx := fmt.Sprintf("package example.auth\n\n%v", policyRego)
var policyQuery string
if isAllow {
policyQuery = fmt.Sprintf("data.example.auth.allow")
} else {
policyQuery = fmt.Sprintf("data.example.auth.deny")
policyRegoFixed := removePackageAtTheBeginning(policyRego)
policyRegoEx := fmt.Sprintf("package policyman.auth\n\n%v", policyRegoFixed)
policyQuery := fmt.Sprintf("data.policyman.auth")
return policyEval(policyRegoEx, policyQuery, input)
}

func removePackageAtTheBeginning(input string) string {
lines := strings.Split(input, "\n")
var outputLines []string

for _, line := range lines {
// Strip the spaces
line = strings.TrimSpace(line)

// Skip the blank lines and the lines starts with `#`
if len(line) == 0 || strings.HasPrefix(line, "#") {
continue
}

// Remove the line start with "package"
if len(outputLines) == 0 && strings.HasPrefix(line, "package") {
continue
}

outputLines = append(outputLines, line)
}

return policyEval(policyRegoEx, policyQuery, input)
result := strings.Join(outputLines, "\n")
return result
}

func policyEval(policyRego string, policyQuery string, input interface{}) (decision bool, err error) {
Expand Down Expand Up @@ -154,10 +168,22 @@ func policyEval(policyRego string, policyQuery string, input interface{}) (decis
} else if len(results) == 0 {
return false, nil
} else {
decision, ok := results[0].Bindings["result"].(bool)
if !ok || !decision {
result, ok := results[0].Bindings["result"].(map[string]any)
if !ok {
return false, nil
}
return decision, nil
if allow, ok := result["allow"]; ok {
if allowBool, ok := allow.(bool); ok && !allowBool {
return false, nil
}
}
if deny, ok := result["deny"]; ok {
if denyBool, ok := deny.(bool); ok && denyBool {
return false, nil
}
}

}

return true, nil
}

0 comments on commit 90b0d9b

Please sign in to comment.