github.com/chenbh/concourse/v6@v6.4.2/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/chenbh/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 opaResult struct {
    37  	Result *bool `json:"result,omitempty"`
    38  }
    39  
    40  type opa struct {
    41  	config OpaConfig
    42  	logger lager.Logger
    43  }
    44  
    45  func (c opa) Check(input policy.PolicyCheckInput) (bool, error) {
    46  	data := opaInput{input}
    47  	jsonBytes, err := json.Marshal(data)
    48  	if err != nil {
    49  		return false, err
    50  	}
    51  
    52  	c.logger.Debug("opa-check", lager.Data{"input": string(jsonBytes)})
    53  
    54  	req, err := http.NewRequest("POST", c.config.URL, bytes.NewBuffer(jsonBytes))
    55  	if err != nil {
    56  		return false, err
    57  	}
    58  	req.Header.Set("Content-Type", "application/json")
    59  
    60  	client := &http.Client{}
    61  	client.Timeout = c.config.Timeout
    62  	resp, err := client.Do(req)
    63  	if err != nil {
    64  		return false, err
    65  	}
    66  	defer resp.Body.Close()
    67  
    68  	statusCode := resp.StatusCode
    69  	if statusCode != http.StatusOK {
    70  		return false, fmt.Errorf("opa returned status: %d", statusCode)
    71  	}
    72  
    73  	body, err := ioutil.ReadAll(resp.Body)
    74  	if err != nil {
    75  		return false, fmt.Errorf("opa returned no response: %s", err.Error())
    76  	}
    77  
    78  	result := &opaResult{}
    79  	err = json.Unmarshal(body, &result)
    80  	if err != nil {
    81  		return false, fmt.Errorf("opa returned bad response: %s", err.Error())
    82  	}
    83  
    84  	// If no result returned, meaning that the requested policy decision is
    85  	// undefined OPA, then consider as pass.
    86  	if result.Result == nil {
    87  		return true, nil
    88  	}
    89  
    90  	return *result.Result, nil
    91  }