1package opa 2 3import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "time" 10 11 "code.cloudfoundry.org/lager" 12 13 "github.com/concourse/concourse/atc/policy" 14) 15 16type OpaConfig struct { 17 URL string `long:"opa-url" description:"OPA policy check endpoint."` 18 Timeout time.Duration `long:"opa-timeout" default:"5s" description:"OPA request timeout."` 19} 20 21func init() { 22 policy.RegisterAgent(&OpaConfig{}) 23} 24 25func (c *OpaConfig) Description() string { return "Open Policy Agent" } 26func (c *OpaConfig) IsConfigured() bool { return c.URL != "" } 27 28func (c *OpaConfig) NewAgent(logger lager.Logger) (policy.Agent, error) { 29 return opa{*c, logger}, nil 30} 31 32type opaInput struct { 33 Input policy.PolicyCheckInput `json:"input"` 34} 35 36type opaOuptut struct { 37 Allowed *bool `json:"allowed,omitempty"` 38 Reasons []string `json:"reasons,omitempty"` 39} 40 41type opaResult struct { 42 Result *opaOuptut `json:"result,omitempty"` 43} 44 45type opa struct { 46 config OpaConfig 47 logger lager.Logger 48} 49 50func (c opa) Check(input policy.PolicyCheckInput) (policy.PolicyCheckOutput, error) { 51 data := opaInput{input} 52 jsonBytes, err := json.Marshal(data) 53 if err != nil { 54 return policy.FailedPolicyCheck(), err 55 } 56 57 c.logger.Debug("opa-check", lager.Data{"input": string(jsonBytes)}) 58 59 req, err := http.NewRequest("POST", c.config.URL, bytes.NewBuffer(jsonBytes)) 60 if err != nil { 61 return policy.FailedPolicyCheck(), err 62 } 63 req.Header.Set("Content-Type", "application/json") 64 65 client := &http.Client{} 66 client.Timeout = c.config.Timeout 67 resp, err := client.Do(req) 68 if err != nil { 69 return policy.FailedPolicyCheck(), err 70 } 71 defer resp.Body.Close() 72 73 statusCode := resp.StatusCode 74 if statusCode != http.StatusOK { 75 return policy.FailedPolicyCheck(), fmt.Errorf("opa returned status: %d", statusCode) 76 } 77 78 body, err := ioutil.ReadAll(resp.Body) 79 if err != nil { 80 return policy.FailedPolicyCheck(), fmt.Errorf("opa returned no response: %s", err.Error()) 81 } 82 83 result := &opaResult{} 84 err = json.Unmarshal(body, &result) 85 if err != nil { 86 return policy.FailedPolicyCheck(), fmt.Errorf("opa returned bad response: %s", err.Error()) 87 } 88 89 if result.Result == nil || result.Result.Allowed == nil { 90 return policy.FailedPolicyCheck(), fmt.Errorf("opa returned invalid response: %s", body) 91 } 92 93 return policy.PolicyCheckOutput{ 94 Allowed: *result.Result.Allowed, 95 Reasons: result.Result.Reasons, 96 }, nil 97} 98