11package rego
22
33import (
4+ "crypto/tls"
5+ "crypto/x509"
6+ "encoding/base64"
7+ "fmt"
48 "net/http"
9+ "net/url"
510 "time"
611
12+ "github.com/open-policy-agent/frameworks/constraint/pkg/apis/externaldata/unversioned"
713 "github.com/open-policy-agent/frameworks/constraint/pkg/externaldata"
814 "github.com/open-policy-agent/opa/ast"
915 "github.com/open-policy-agent/opa/rego"
@@ -12,6 +18,9 @@ import (
1218const (
1319 providerResponseAPIVersion = "externaldata.gatekeeper.sh/v1beta1"
1420 providerResponseKind = "ProviderResponse"
21+ HTTPSScheme = "https"
22+ idleConnTimeout = 90 * time .Second
23+ maxIdleConnsPerHost = 100
1524)
1625
1726func externalDataBuiltin (d * Driver ) func (bctx rego.BuiltinContext , regorequest * ast.Term ) (* ast.Term , error ) {
@@ -31,6 +40,12 @@ func externalDataBuiltin(d *Driver) func(bctx rego.BuiltinContext, regorequest *
3140 return externaldata .HandleError (http .StatusBadRequest , err )
3241 }
3342
43+ client , err := getClient (& provider , clientCert )
44+ if err != nil {
45+ return externaldata .HandleError (http .StatusInternalServerError ,
46+ fmt .Errorf ("failed to get HTTP client: %w" , err ))
47+ }
48+
3449 // check provider response cache
3550 var providerRequestKeys []string
3651 var providerResponseStatusCode int
@@ -71,7 +86,7 @@ func externalDataBuiltin(d *Driver) func(bctx rego.BuiltinContext, regorequest *
7186 }
7287
7388 if len (providerRequestKeys ) > 0 {
74- externaldataResponse , statusCode , err := d .sendRequestToProvider (bctx .Context , & provider , providerRequestKeys , clientCert )
89+ externaldataResponse , statusCode , err := d .sendRequestToProvider (bctx .Context , & provider , providerRequestKeys , client )
7590 if err != nil {
7691 return externaldata .HandleError (statusCode , err )
7792 }
@@ -115,3 +130,49 @@ func externalDataBuiltin(d *Driver) func(bctx rego.BuiltinContext, regorequest *
115130 return externaldata .PrepareRegoResponse (regoResponse )
116131 }
117132}
133+
134+ // getClient returns a new HTTP client, and set up its TLS configuration.
135+ func getClient (provider * unversioned.Provider , clientCert * tls.Certificate ) (* http.Client , error ) {
136+ u , err := url .Parse (provider .Spec .URL )
137+ if err != nil {
138+ return nil , fmt .Errorf ("failed to parse provider URL %s: %w" , provider .Spec .URL , err )
139+ }
140+
141+ if u .Scheme != HTTPSScheme {
142+ return nil , fmt .Errorf ("only HTTPS scheme is supported" )
143+ }
144+
145+ client := & http.Client {
146+ Timeout : time .Duration (provider .Spec .Timeout ) * time .Second ,
147+ }
148+
149+ tlsConfig := & tls.Config {MinVersion : tls .VersionTLS13 }
150+
151+ // present our client cert to the server
152+ // in case provider wants to verify it
153+ if clientCert != nil {
154+ tlsConfig .Certificates = []tls.Certificate {* clientCert }
155+ }
156+
157+ // if the provider presents its own CA bundle,
158+ // we will use it to verify the server's certificate
159+ caBundleData , err := base64 .StdEncoding .DecodeString (provider .Spec .CABundle )
160+ if err != nil {
161+ return nil , fmt .Errorf ("failed to decode CA bundle: %w" , err )
162+ }
163+
164+ providerCertPool := x509 .NewCertPool ()
165+ if ok := providerCertPool .AppendCertsFromPEM (caBundleData ); ! ok {
166+ return nil , fmt .Errorf ("failed to append provider's CA bundle to certificate pool" )
167+ }
168+
169+ tlsConfig .RootCAs = providerCertPool
170+
171+ client .Transport = & http.Transport {
172+ TLSClientConfig : tlsConfig ,
173+ IdleConnTimeout : idleConnTimeout ,
174+ MaxIdleConnsPerHost : maxIdleConnsPerHost ,
175+ }
176+
177+ return client , nil
178+ }
0 commit comments