github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/policy/opa/opa.go (about)

     1  package opa
     2  
     3  import (
     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/pf-qiu/concourse/v6/atc/policy"
    14  )
    15  
    16  type 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  
    21  func init() {
    22  	policy.RegisterAgent(&OpaConfig{})
    23  }
    24  
    25  func (c *OpaConfig) Description() string { return "Open Policy Agent" }
    26  func (c *OpaConfig) IsConfigured() bool  { return c.URL != "" }
    27  
    28  func (c *OpaConfig) NewAgent(logger lager.Logger) (policy.Agent, error) {
    29  	return opa{*c, logger}, nil
    30  }
    31  
    32  type opaInput struct {
    33  	Input policy.PolicyCheckInput `json:"input"`
    34  }
    35  
    36  type opaOuptut struct {
    37  	Allowed *bool    `json:"allowed,omitempty"`
    38  	Reasons []string `json:"reasons,omitempty"`
    39  }
    40  
    41  type opaResult struct {
    42  	Result *opaOuptut `json:"result,omitempty"`
    43  }
    44  
    45  type opa struct {
    46  	config OpaConfig
    47  	logger lager.Logger
    48  }
    49  
    50  func (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  }