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