plugins/rest: various changes re: TLS, *http.Client caching (#8376)

* plugins/rest: cache *http.Client and auth plugin

This will require further changes to cert TLS and token auth methods to
stay compatible with the previous behaviour.

* plugins/rest: configurable re-read interval for TLS cert+key

Defaulting to re-reading all the time, more or less like we did before.

(I write "more or less" because we now do it in `GetClientCertificate()`.)

* plugins/rest: document change (code comments, CHANGELOG)
* plugins/rest: set minimum TLS version where `&tls.Config{}` is used
* plugins/rest: ensure min TLS version and ciphersuites are used

...as  configured with the server.


Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
This commit is contained in:
Stephan Renatus
2026-03-17 08:30:57 +01:00
committed by GitHub
parent 5e57a2758b
commit e0d66617c3
11 changed files with 823 additions and 375 deletions
+11
View File
@@ -5,6 +5,17 @@ project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased ## Unreleased
### Custom HTTPAuthPlugin behavior change
The `HTTPAuthPlugin.NewClient()` method is now called once per `Client` instance and cached rather than being called for every request. Custom plugins that performed per-request operations in `NewClient()` (such as request counters, per-request transport wrapping, or logging/metrics side effects) will now only execute those operations once. All per-request authentication logic must be moved from `NewClient()` to `Prepare()`. All plugins included in OPA have been updated and are unaffected by this change.
### Runtime, SDK, Tooling
- plugins/rest: Configurable re-read interval for TLS client certificates via `cert_reread_interval_seconds` field.
Defaults to re-reading on every request for backwards compatibility.
The implementation also uses content hashing to detect changes and avoid re-parsing unchanged TLS certificates and keys.
- plugins/rest: All TLS configurations now inherit the minimum version and TLS ciphersuites as configured for the server.
## 1.14.1 ## 1.14.1
This is a patch release collecting two bug fixes and various dependency updates for Golang standard library and common package vulnerabilities. This is a patch release collecting two bug fixes and various dependency updates for Golang standard library and common package vulnerabilities.
+10 -2
View File
@@ -29,6 +29,8 @@ type ServiceOptions struct {
Keys map[string]*keys.Config Keys map[string]*keys.Config
Logger logging.Logger Logger logging.Logger
DistributedTacingOpts tracing.Options DistributedTacingOpts tracing.Options
MinTLSVersion uint16
CipherSuites *[]uint16
} }
// ParseServicesConfig returns a set of named service clients. The service // ParseServicesConfig returns a set of named service clients. The service
@@ -41,10 +43,16 @@ func ParseServicesConfig(opts ServiceOptions) (map[string]rest.Client, error) {
var arr []json.RawMessage var arr []json.RawMessage
var obj map[string]json.RawMessage var obj map[string]json.RawMessage
clientOpts := []func(*rest.Client){
rest.AuthPluginLookup(opts.AuthPlugin),
rest.Logger(opts.Logger),
rest.DistributedTracingOpts(opts.DistributedTacingOpts),
rest.MinTLSVersion(opts.MinTLSVersion),
rest.CipherSuites(opts.CipherSuites)}
if err := util.Unmarshal(opts.Raw, &arr); err == nil { if err := util.Unmarshal(opts.Raw, &arr); err == nil {
for _, s := range arr { for _, s := range arr {
client, err := rest.New(s, opts.Keys, rest.AuthPluginLookup(opts.AuthPlugin), rest.Logger(opts.Logger), rest.DistributedTracingOpts(opts.DistributedTacingOpts)) client, err := rest.New(s, opts.Keys, clientOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -52,7 +60,7 @@ func ParseServicesConfig(opts ServiceOptions) (map[string]rest.Client, error) {
} }
} else if util.Unmarshal(opts.Raw, &obj) == nil { } else if util.Unmarshal(opts.Raw, &obj) == nil {
for k := range obj { for k := range obj {
client, err := rest.New(obj[k], opts.Keys, rest.Name(k), rest.AuthPluginLookup(opts.AuthPlugin), rest.Logger(opts.Logger), rest.DistributedTracingOpts(opts.DistributedTacingOpts)) client, err := rest.New(obj[k], opts.Keys, append(clientOpts, rest.Name(k))...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+6
View File
@@ -6,6 +6,7 @@
package config package config
import ( import (
"crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -22,6 +23,11 @@ import (
"github.com/open-policy-agent/opa/v1/version" "github.com/open-policy-agent/opa/v1/version"
) )
const (
// DefaultMinTLSVersion is the minimum TLS version used by OPA server and REST clients
DefaultMinTLSVersion = tls.VersionTLS12
)
// ServerConfig represents the different server configuration options. // ServerConfig represents the different server configuration options.
type ServerConfig struct { type ServerConfig struct {
Metrics json.RawMessage `json:"metrics,omitempty"` Metrics json.RawMessage `json:"metrics,omitempty"`
+18
View File
@@ -218,6 +218,8 @@ type Manager struct {
bootstrapConfigLabels map[string]string bootstrapConfigLabels map[string]string
hooks hooks.Hooks hooks hooks.Hooks
enableVersionCheck bool enableVersionCheck bool
minTLSVersion uint16
cipherSuites *[]uint16
versionChecker versioncheck.Checker versionChecker versioncheck.Checker
opaReportNotifyCh chan struct{} opaReportNotifyCh chan struct{}
stop chan chan struct{} stop chan chan struct{}
@@ -460,6 +462,20 @@ func WithBundleActivatorPlugin(bundleActivatorPlugin string) func(*Manager) {
} }
} }
// WithMinTLSVersion sets the minimum TLS version for REST client connections
func WithMinTLSVersion(v uint16) func(*Manager) {
return func(m *Manager) {
m.minTLSVersion = v
}
}
// WithCipherSuites sets the cipher suites for REST client connections
func WithCipherSuites(cs *[]uint16) func(*Manager) {
return func(m *Manager) {
m.cipherSuites = cs
}
}
// New creates a new Manager using config. // New creates a new Manager using config.
func New(raw []byte, id string, store storage.Store, opts ...func(*Manager)) (*Manager, error) { func New(raw []byte, id string, store storage.Store, opts ...func(*Manager)) (*Manager, error) {
parsedConfig, err := config.ParseConfig(raw, id) parsedConfig, err := config.ParseConfig(raw, id)
@@ -842,6 +858,8 @@ func (m *Manager) DefaultServiceOpts(config *config.Config) cfg.ServiceOptions {
Logger: m.logger, Logger: m.logger,
Keys: m.keys, Keys: m.keys,
DistributedTacingOpts: m.distributedTacingOpts, DistributedTacingOpts: m.distributedTacingOpts,
MinTLSVersion: m.minTLSVersion,
CipherSuites: m.cipherSuites,
} }
} }
+16 -175
View File
@@ -5,9 +5,9 @@
package rest package rest
import ( import (
"cmp"
"context" "context"
"crypto/rand" "crypto/rand"
"crypto/rsa"
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"crypto/tls" "crypto/tls"
@@ -33,6 +33,7 @@ import (
"github.com/lestrrat-go/jwx/v3/jws" "github.com/lestrrat-go/jwx/v3/jws"
"github.com/open-policy-agent/opa/internal/providers/aws" "github.com/open-policy-agent/opa/internal/providers/aws"
"github.com/open-policy-agent/opa/internal/uuid" "github.com/open-policy-agent/opa/internal/uuid"
"github.com/open-policy-agent/opa/v1/config"
"github.com/open-policy-agent/opa/v1/keys" "github.com/open-policy-agent/opa/v1/keys"
"github.com/open-policy-agent/opa/v1/logging" "github.com/open-policy-agent/opa/v1/logging"
) )
@@ -44,59 +45,6 @@ const (
defaultClientAssertionType = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" defaultClientAssertionType = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
) )
// DefaultTLSConfig defines standard TLS configurations based on the Config
func DefaultTLSConfig(c Config) (*tls.Config, error) {
t := &tls.Config{}
url, err := url.Parse(c.URL)
if err != nil {
return nil, err
}
if url.Scheme == "https" {
t.InsecureSkipVerify = c.AllowInsecureTLS
}
if c.TLS != nil && c.TLS.CACert != "" {
caCert, err := os.ReadFile(c.TLS.CACert)
if err != nil {
return nil, err
}
var rootCAs *x509.CertPool
if c.TLS.SystemCARequired {
rootCAs, err = x509.SystemCertPool()
if err != nil {
return nil, err
}
} else {
rootCAs = x509.NewCertPool()
}
ok := rootCAs.AppendCertsFromPEM(caCert)
if !ok {
return nil, errors.New("unable to parse and append CA certificate to certificate pool")
}
t.RootCAs = rootCAs
}
return t, nil
}
// DefaultRoundTripperClient is a reasonable set of defaults for HTTP auth plugins
func DefaultRoundTripperClient(t *tls.Config, timeout int64) *http.Client {
// Ensure we use a http.Transport with proper settings: the zero values are not
// a good choice, as they cause leaking connections:
// https://github.com/golang/go/issues/19620
// copy, we don't want to alter the default client's Transport
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.ResponseHeaderTimeout = time.Duration(timeout) * time.Second
tr.TLSClientConfig = t
c := *http.DefaultClient
c.Transport = tr
return &c
}
// defaultAuthPlugin represents baseline 'no auth' behavior if no alternative plugin is specified for a service // defaultAuthPlugin represents baseline 'no auth' behavior if no alternative plugin is specified for a service
type defaultAuthPlugin struct{} type defaultAuthPlugin struct{}
@@ -112,11 +60,6 @@ func (*defaultAuthPlugin) Prepare(*http.Request) error {
return nil return nil
} }
type serverTLSConfig struct {
CACert string `json:"ca_cert,omitempty"`
SystemCARequired bool `json:"system_ca_required,omitempty"`
}
// bearerAuthPlugin represents authentication via a bearer token in the HTTP Authorization header // bearerAuthPlugin represents authentication via a bearer token in the HTTP Authorization header
type bearerAuthPlugin struct { type bearerAuthPlugin struct {
Token string `json:"token"` Token string `json:"token"`
@@ -316,6 +259,8 @@ type oauth2ClientCredentialsAuthPlugin struct {
signingKeyParsed any signingKeyParsed any
tokenCache *oauth2Token tokenCache *oauth2Token
tlsSkipVerify bool tlsSkipVerify bool
minTLSVersion uint16
cipherSuites *[]uint16
logger logging.Logger logger logging.Logger
} }
@@ -557,8 +502,10 @@ func (ap *oauth2ClientCredentialsAuthPlugin) NewClient(c Config) (*http.Client,
} }
} }
// Inherit skip verify from the "parent" settings. Should this be configurable on the credentials too? // Inherit TLS settings from the "parent" config
ap.tlsSkipVerify = c.AllowInsecureTLS ap.tlsSkipVerify = c.AllowInsecureTLS
ap.minTLSVersion = c.minTLSVersion
ap.cipherSuites = c.cipherSuites
ap.logger = c.logger ap.logger = c.logger
@@ -714,7 +661,15 @@ func (ap *oauth2ClientCredentialsAuthPlugin) requestToken(ctx context.Context) (
r.Header.Add(k, v) r.Header.Add(k, v)
} }
client := DefaultRoundTripperClient(&tls.Config{InsecureSkipVerify: ap.tlsSkipVerify}, 10) tlsConfig := &tls.Config{
MinVersion: cmp.Or(ap.minTLSVersion, uint16(config.DefaultMinTLSVersion)),
InsecureSkipVerify: ap.tlsSkipVerify,
}
if ap.cipherSuites != nil {
tlsConfig.CipherSuites = *ap.cipherSuites
}
client := DefaultRoundTripperClient(tlsConfig, 10)
response, err := client.Do(r) response, err := client.Do(r)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -761,120 +716,6 @@ func (ap *oauth2ClientCredentialsAuthPlugin) Prepare(req *http.Request) error {
return nil return nil
} }
// clientTLSAuthPlugin represents authentication via client certificate on a TLS connection
type clientTLSAuthPlugin struct {
Cert string `json:"cert"`
PrivateKey string `json:"private_key"`
PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"`
CACert string `json:"ca_cert,omitempty"` // Deprecated: Use `services[_].tls.ca_cert` instead
SystemCARequired bool `json:"system_ca_required,omitempty"` // Deprecated: Use `services[_].tls.system_ca_required` instead
}
func (ap *clientTLSAuthPlugin) NewClient(c Config) (*http.Client, error) {
tlsConfig, err := DefaultTLSConfig(c)
if err != nil {
return nil, err
}
if ap.Cert == "" {
return nil, errors.New("client certificate is needed when client TLS is enabled")
}
if ap.PrivateKey == "" {
return nil, errors.New("private key is needed when client TLS is enabled")
}
var keyPEMBlock []byte
data, err := os.ReadFile(ap.PrivateKey)
if err != nil {
return nil, err
}
block, _ := pem.Decode(data)
if block == nil {
return nil, errors.New("PEM data could not be found")
}
// nolint: staticcheck // We don't want to forbid users from using this encryption.
if x509.IsEncryptedPEMBlock(block) {
if ap.PrivateKeyPassphrase == "" {
return nil, errors.New("client certificate passphrase is needed, because the certificate is password encrypted")
}
// nolint: staticcheck // We don't want to forbid users from using this encryption.
block, err := x509.DecryptPEMBlock(block, []byte(ap.PrivateKeyPassphrase))
if err != nil {
return nil, err
}
key, err := x509.ParsePKCS8PrivateKey(block)
if err != nil {
key, err = x509.ParsePKCS1PrivateKey(block)
if err != nil {
return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8; parse error: %v", err)
}
}
rsa, ok := key.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("private key is invalid")
}
keyPEMBlock = pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(rsa),
},
)
} else {
keyPEMBlock = data
}
certPEMBlock, err := os.ReadFile(ap.Cert)
if err != nil {
return nil, err
}
cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
var client *http.Client
if c.TLS != nil && c.TLS.CACert != "" {
client = DefaultRoundTripperClient(tlsConfig, *c.ResponseHeaderTimeoutSeconds)
} else {
if ap.CACert != "" {
c.logger.Warn("Deprecated 'services[_].credentials.client_tls.ca_cert' configuration specified. Use 'services[_].tls.ca_cert' instead. See https://www.openpolicyagent.org/docs/latest/configuration/#services")
caCert, err := os.ReadFile(ap.CACert)
if err != nil {
return nil, err
}
var caCertPool *x509.CertPool
if ap.SystemCARequired {
caCertPool, err = x509.SystemCertPool()
if err != nil {
return nil, err
}
} else {
caCertPool = x509.NewCertPool()
}
ok := caCertPool.AppendCertsFromPEM(caCert)
if !ok {
return nil, errors.New("unable to parse and append CA certificate to certificate pool")
}
tlsConfig.RootCAs = caCertPool
}
client = DefaultRoundTripperClient(tlsConfig, *c.ResponseHeaderTimeoutSeconds)
}
return client, nil
}
func (*clientTLSAuthPlugin) Prepare(_ *http.Request) error {
return nil
}
// awsSigningAuthPlugin represents authentication using AWS V4 HMAC signing in the Authorization header // awsSigningAuthPlugin represents authentication using AWS V4 HMAC signing in the Authorization header
type awsSigningAuthPlugin struct { type awsSigningAuthPlugin struct {
AWSEnvironmentCredentials *awsEnvironmentCredentialService `json:"environment_credentials,omitempty"` AWSEnvironmentCredentials *awsEnvironmentCredentialService `json:"environment_credentials,omitempty"`
+250
View File
@@ -0,0 +1,250 @@
// Copyright 2026 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package rest
import (
"cmp"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"net/http"
"net/url"
"os"
"sync"
"time"
"github.com/open-policy-agent/opa/v1/config"
)
// DefaultTLSConfig defines standard TLS configurations based on the Config
func DefaultTLSConfig(c Config) (*tls.Config, error) {
t := &tls.Config{
MinVersion: cmp.Or(c.minTLSVersion, uint16(config.DefaultMinTLSVersion)),
}
if c.cipherSuites != nil {
t.CipherSuites = *c.cipherSuites
}
url, err := url.Parse(c.URL)
if err != nil {
return nil, err
}
if url.Scheme == "https" {
t.InsecureSkipVerify = c.AllowInsecureTLS
}
if c.TLS != nil && c.TLS.CACert != "" {
caCert, err := os.ReadFile(c.TLS.CACert)
if err != nil {
return nil, err
}
var rootCAs *x509.CertPool
if c.TLS.SystemCARequired {
rootCAs, err = x509.SystemCertPool()
if err != nil {
return nil, err
}
} else {
rootCAs = x509.NewCertPool()
}
ok := rootCAs.AppendCertsFromPEM(caCert)
if !ok {
return nil, errors.New("unable to parse and append CA certificate to certificate pool")
}
t.RootCAs = rootCAs
}
return t, nil
}
// DefaultRoundTripperClient is a reasonable set of defaults for HTTP auth plugins
func DefaultRoundTripperClient(t *tls.Config, timeout int64) *http.Client {
// Ensure we use a http.Transport with proper settings: the zero values are not
// a good choice, as they cause leaking connections:
// https://github.com/golang/go/issues/19620
// copy, we don't want to alter the default client's Transport
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.ResponseHeaderTimeout = time.Duration(timeout) * time.Second
tr.TLSClientConfig = t
c := *http.DefaultClient
c.Transport = tr
return &c
}
type serverTLSConfig struct {
CACert string `json:"ca_cert,omitempty"`
SystemCARequired bool `json:"system_ca_required,omitempty"`
}
// clientTLSAuthPlugin represents authentication via client certificate on a TLS connection
type clientTLSAuthPlugin struct {
Cert string `json:"cert"`
PrivateKey string `json:"private_key"`
PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"`
CACert string `json:"ca_cert,omitempty"` // Deprecated: Use `services[_].tls.ca_cert` instead
SystemCARequired bool `json:"system_ca_required,omitempty"` // Deprecated: Use `services[_].tls.system_ca_required` instead
CertRereadIntervalSeconds *int64 `json:"cert_reread_interval_seconds,omitempty"`
mu sync.RWMutex
cachedCert *tls.Certificate
certFileHash [32]byte
keyFileHash [32]byte
lastLoadTime time.Time
}
func (ap *clientTLSAuthPlugin) loadCertificate() (*tls.Certificate, error) {
rereadIntervalSeconds := int64(0)
if ap.CertRereadIntervalSeconds != nil {
rereadIntervalSeconds = *ap.CertRereadIntervalSeconds
}
ap.mu.RLock()
if ap.cachedCert != nil && rereadIntervalSeconds > 0 {
timeSinceLastLoad := time.Since(ap.lastLoadTime).Seconds()
if timeSinceLastLoad < float64(rereadIntervalSeconds) {
cert := ap.cachedCert
ap.mu.RUnlock()
return cert, nil
}
}
ap.mu.RUnlock()
certPEMBlock, err := os.ReadFile(ap.Cert)
if err != nil {
return nil, fmt.Errorf("failed to read client certificate file: %w", err)
}
keyData, err := os.ReadFile(ap.PrivateKey)
if err != nil {
return nil, fmt.Errorf("failed to read client key file: %w", err)
}
certHash := sha256.Sum256(certPEMBlock)
keyHash := sha256.Sum256(keyData)
ap.mu.RLock()
if ap.cachedCert != nil && ap.certFileHash == certHash && ap.keyFileHash == keyHash {
cert := ap.cachedCert
ap.mu.RUnlock()
return cert, nil
}
ap.mu.RUnlock()
var keyPEMBlock []byte
block, _ := pem.Decode(keyData)
if block == nil {
return nil, errors.New("PEM data could not be found")
}
// nolint: staticcheck // We don't want to forbid users from using this encryption.
if x509.IsEncryptedPEMBlock(block) {
if ap.PrivateKeyPassphrase == "" {
return nil, errors.New("client private key passphrase is needed, because the certificate is password encrypted")
}
// nolint: staticcheck // We don't want to forbid users from using this encryption.
decryptedBlock, err := x509.DecryptPEMBlock(block, []byte(ap.PrivateKeyPassphrase))
if err != nil {
return nil, err
}
key, err := x509.ParsePKCS8PrivateKey(decryptedBlock)
if err != nil {
key, err = x509.ParsePKCS1PrivateKey(decryptedBlock)
if err != nil {
return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8; parse error: %v", err)
}
}
rsaKey, ok := key.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("private key is invalid")
}
keyPEMBlock = pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(rsaKey),
},
)
} else {
keyPEMBlock = keyData
}
cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
return nil, fmt.Errorf("failed to parse public/private key pair: %v", err)
}
ap.mu.Lock()
ap.cachedCert = &cert
ap.certFileHash = certHash
ap.keyFileHash = keyHash
ap.lastLoadTime = time.Now()
ap.mu.Unlock()
return &cert, nil
}
func (ap *clientTLSAuthPlugin) NewClient(c Config) (*http.Client, error) {
tlsConfig, err := DefaultTLSConfig(c)
if err != nil {
return nil, err
}
if ap.Cert == "" {
return nil, errors.New("client certificate is needed when client TLS is enabled")
}
if ap.PrivateKey == "" {
return nil, errors.New("private key is needed when client TLS is enabled")
}
tlsConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
return ap.loadCertificate()
}
var client *http.Client
if c.TLS != nil && c.TLS.CACert != "" {
client = DefaultRoundTripperClient(tlsConfig, *c.ResponseHeaderTimeoutSeconds)
} else {
if ap.CACert != "" {
c.logger.Warn("Deprecated 'services[_].credentials.client_tls.ca_cert' configuration specified. Use 'services[_].tls.ca_cert' instead. See https://www.openpolicyagent.org/docs/latest/configuration/#services")
caCert, err := os.ReadFile(ap.CACert)
if err != nil {
return nil, err
}
var caCertPool *x509.CertPool
if ap.SystemCARequired {
caCertPool, err = x509.SystemCertPool()
if err != nil {
return nil, err
}
} else {
caCertPool = x509.NewCertPool()
}
ok := caCertPool.AppendCertsFromPEM(caCert)
if !ok {
return nil, errors.New("unable to parse and append CA certificate to certificate pool")
}
tlsConfig.RootCAs = caCertPool
}
client = DefaultRoundTripperClient(tlsConfig, *c.ResponseHeaderTimeoutSeconds)
}
return client, nil
}
func (*clientTLSAuthPlugin) Prepare(*http.Request) error {
return nil
}
+432
View File
@@ -0,0 +1,432 @@
package rest
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/open-policy-agent/opa/v1/keys"
"github.com/open-policy-agent/opa/v1/logging"
"github.com/open-policy-agent/opa/v1/util/test"
)
func generateTestCertificate(t *testing.T, certPath, keyPath string, serialNumber int64) {
t.Helper()
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("failed to generate private key: %v", err)
}
template := x509.Certificate{
SerialNumber: big.NewInt(serialNumber),
Subject: pkix.Name{
Organization: []string{"Test Org"},
CommonName: "Test Cert",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
t.Fatalf("failed to create certificate: %v", err)
}
certOut, err := os.Create(certPath)
if err != nil {
t.Fatalf("failed to open cert file for writing: %v", err)
}
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
t.Fatalf("failed to write certificate: %v", err)
}
if err := certOut.Close(); err != nil {
t.Fatalf("error closing cert file: %v", err)
}
keyOut, err := os.Create(keyPath)
if err != nil {
t.Fatalf("failed to open key file for writing: %v", err)
}
privBytes, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
t.Fatalf("failed to marshal private key: %v", err)
}
if err := pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privBytes}); err != nil {
t.Fatalf("failed to write private key: %v", err)
}
if err := keyOut.Close(); err != nil {
t.Fatalf("error closing key file: %v", err)
}
}
func TestClientTLSAuthPlugin_CertificateRotation(t *testing.T) {
tmpDir := t.TempDir()
certPath := filepath.Join(tmpDir, "cert.pem")
keyPath := filepath.Join(tmpDir, "key.pem")
generateTestCertificate(t, certPath, keyPath, 1)
plugin := &clientTLSAuthPlugin{
Cert: certPath,
PrivateKey: keyPath,
}
config := Config{
URL: "https://example.com",
ResponseHeaderTimeoutSeconds: &[]int64{10}[0],
logger: logging.New(),
}
client, err := plugin.NewClient(config)
if err != nil {
t.Fatalf("NewClient() failed: %v", err)
}
transport, ok := client.Transport.(*http.Transport)
if !ok {
t.Fatal("client transport is not *http.Transport")
}
if transport.TLSClientConfig.GetClientCertificate == nil {
t.Fatal("client transport has no GetClientCertificate")
}
cert1, err := transport.TLSClientConfig.GetClientCertificate(&tls.CertificateRequestInfo{})
if err != nil {
t.Fatalf("first GetClientCertificate failed: %v", err)
}
if len(cert1.Certificate) == 0 {
t.Fatal("first certificate is empty")
}
parsedCert1, err := x509.ParseCertificate(cert1.Certificate[0])
if err != nil {
t.Fatalf("failed to parse first certificate: %v", err)
}
if parsedCert1.SerialNumber.Int64() != 1 {
t.Errorf("first certificate serial number = %d, want 1", parsedCert1.SerialNumber.Int64())
}
cert2, err := transport.TLSClientConfig.GetClientCertificate(&tls.CertificateRequestInfo{})
if err != nil {
t.Fatalf("second GetClientCertificate failed: %v", err)
}
parsedCert2, err := x509.ParseCertificate(cert2.Certificate[0])
if err != nil {
t.Fatalf("failed to parse second certificate: %v", err)
}
if parsedCert2.SerialNumber.Int64() != 1 {
t.Errorf("second certificate serial number = %d, want 1 (should be cached)", parsedCert2.SerialNumber.Int64())
}
generateTestCertificate(t, certPath, keyPath, 2)
cert3, err := transport.TLSClientConfig.GetClientCertificate(&tls.CertificateRequestInfo{})
if err != nil {
t.Fatalf("third GetClientCertificate failed: %v", err)
}
parsedCert3, err := x509.ParseCertificate(cert3.Certificate[0])
if err != nil {
t.Fatalf("failed to parse third certificate: %v", err)
}
if parsedCert3.SerialNumber.Int64() != 2 {
t.Errorf("third certificate serial number = %d, want 2 (should be reloaded)", parsedCert3.SerialNumber.Int64())
}
if parsedCert1.SerialNumber.Cmp(parsedCert3.SerialNumber) == 0 {
t.Error("certificate was not rotated after file change")
}
}
func TestClientTLSAuthPlugin_ConfigParsing(t *testing.T) {
tmpDir := t.TempDir()
certPath := filepath.Join(tmpDir, "cert.pem")
keyPath := filepath.Join(tmpDir, "key.pem")
generateTestCertificate(t, certPath, keyPath, 1)
tests := []struct {
name string
buildConfig func(cert, key, ca string) string
expectSystemCARequired bool
expectError bool
}{
{
name: "system_ca_required true",
buildConfig: func(cert, key, ca string) string {
return fmt.Sprintf(`{
"name": "test",
"url": "https://example.com",
"credentials": {
"client_tls": {
"cert": %q,
"private_key": %q,
"system_ca_required": true
}
}
}`, cert, key)
},
expectSystemCARequired: true,
},
{
name: "system_ca_required false",
buildConfig: func(cert, key, ca string) string {
return fmt.Sprintf(`{
"name": "test",
"url": "https://example.com",
"credentials": {
"client_tls": {
"cert": %q,
"private_key": %q,
"system_ca_required": false
}
}
}`, cert, key)
},
expectSystemCARequired: false,
},
{
name: "deprecated ca_cert field with system_ca_required",
buildConfig: func(cert, key, ca string) string {
return fmt.Sprintf(`{
"name": "test",
"url": "https://example.com",
"credentials": {
"client_tls": {
"cert": %q,
"private_key": %q,
"ca_cert": %q,
"system_ca_required": true
}
}
}`, cert, key, ca)
},
expectSystemCARequired: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
config := tc.buildConfig(certPath, keyPath, certPath)
client, err := New([]byte(config), map[string]*keys.Config{})
if tc.expectError {
if err == nil {
t.Fatal("expected error but got none")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if client.config.Credentials.ClientTLS == nil {
t.Fatal("ClientTLS credentials not parsed")
}
if client.config.Credentials.ClientTLS.SystemCARequired != tc.expectSystemCARequired {
t.Errorf("SystemCARequired = %v, want %v",
client.config.Credentials.ClientTLS.SystemCARequired,
tc.expectSystemCARequired)
}
})
}
}
func TestClientCert(t *testing.T) {
t.Parallel()
ts := testServer{
t: t,
tls: true,
expectClientCert: true,
}
ts.start()
defer ts.stop()
files := map[string]string{
"client.pem": string(ts.clientCertPem),
"client.key": string(ts.clientCertKey),
}
test.WithTempFS(files, func(path string) {
certPath := filepath.Join(path, "client.pem")
keyPath := filepath.Join(path, "client.key")
client := newTestClient(t, &ts, certPath, keyPath)
ctx := t.Context()
if _, err := client.Do(ctx, "GET", "test"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Scramble the keys in the server
ts.stop()
ts.start()
// Ensure the keys don't work anymore, make a new client as the url will have changed
client = newTestClient(t, &ts, certPath, keyPath)
_, err := client.Do(ctx, "GET", "test")
expectedErrMsg := func(s string) bool {
switch {
case strings.Contains(s, "tls: unknown certificate authority"):
case strings.Contains(s, "tls: bad certificate"):
default:
return false
}
return true
}
if err == nil || !expectedErrMsg(err.Error()) {
t.Fatalf("Unexpected error %v", err)
}
// Update the key files and try again..
if err := os.WriteFile(filepath.Join(path, "client.pem"), ts.clientCertPem, 0600); err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if err := os.WriteFile(filepath.Join(path, "client.key"), ts.clientCertKey, 0600); err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if _, err := client.Do(ctx, "GET", "test"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
})
}
func TestClientCertPassword(t *testing.T) {
t.Parallel()
ts := testServer{
t: t,
tls: true,
expectClientCert: true,
clientCertPassword: "password",
}
ts.start()
defer ts.stop()
files := map[string]string{
"client.pem": string(ts.clientCertPem),
"client.key": string(ts.clientCertKey),
}
test.WithTempFS(files, func(path string) {
certPath := filepath.Join(path, "client.pem")
keyPath := filepath.Join(path, "client.key")
client := newTestClient(t, &ts, certPath, keyPath)
ctx := t.Context()
if _, err := client.Do(ctx, "GET", "test"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
})
}
func TestClientTLSWithCustomCACert(t *testing.T) {
t.Parallel()
ts := testServer{
t: t,
tls: true,
expectClientCert: true,
}
ts.start()
defer ts.stop()
files := map[string]string{
"client.pem": string(ts.clientCertPem),
"client.key": string(ts.clientCertKey),
"ca.pem": string(ts.rootCertPEM),
}
test.WithTempFS(files, func(path string) {
certPath := filepath.Join(path, "client.pem")
keyPath := filepath.Join(path, "client.key")
ts.caCert = filepath.Join(path, "ca.pem")
client := newTestClient(t, &ts, certPath, keyPath)
ctx := t.Context()
if _, err := client.Do(ctx, "GET", "test"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
})
}
func TestClientTLSWithCustomCACertAndSystemCA(t *testing.T) {
t.Parallel()
ts := testServer{
t: t,
tls: true,
expectClientCert: true,
expectSystemCA: true,
}
ts.start()
defer ts.stop()
files := map[string]string{
"client.pem": string(ts.clientCertPem),
"client.key": string(ts.clientCertKey),
"ca.pem": string(ts.rootCertPEM),
}
test.WithTempFS(files, func(path string) {
certPath := filepath.Join(path, "client.pem")
keyPath := filepath.Join(path, "client.key")
ts.caCert = filepath.Join(path, "ca.pem")
client := newTestClient(t, &ts, certPath, keyPath)
ctx := t.Context()
if _, err := client.Do(ctx, "GET", "test"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
})
}
func newTestClient(t *testing.T, ts *testServer, certPath string, keypath string) *Client {
config := fmt.Sprintf(`{
"name": "foo",
"url": %q,
"allow_insecure_tls": true,
"tls": {"ca_cert": %q, system_ca_required: %v},
"credentials": {
"client_tls": {
"cert": %q,
"private_key": %q
}
}
}`, ts.server.URL, ts.caCert, ts.expectSystemCA, certPath, keypath)
client, err := New([]byte(config), map[string]*keys.Config{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if ts.clientCertPassword != "" {
client.Config().Credentials.ClientTLS.PrivateKeyPassphrase = ts.clientCertPassword
}
return &client
}
+75 -17
View File
@@ -16,6 +16,7 @@ import (
"net/http/httputil" "net/http/httputil"
"reflect" "reflect"
"strings" "strings"
"sync"
"github.com/open-policy-agent/opa/internal/version" "github.com/open-policy-agent/opa/internal/version"
"github.com/open-policy-agent/opa/v1/keys" "github.com/open-policy-agent/opa/v1/keys"
@@ -39,8 +40,13 @@ var maskedHeaderKeys = map[string]struct{}{
// An HTTPAuthPlugin represents a mechanism to construct and configure HTTP authentication for a REST service // An HTTPAuthPlugin represents a mechanism to construct and configure HTTP authentication for a REST service
type HTTPAuthPlugin interface { type HTTPAuthPlugin interface {
// implementations can assume NewClient will be called before Prepare // NewClient is called once per Client instance and the result is cached.
// Implementations MUST NOT perform per-request operations here.
// All per-request authentication logic MUST be in Prepare().
NewClient(Config) (*http.Client, error) NewClient(Config) (*http.Client, error)
// Prepare is called before every HTTP request.
// Implementations should perform per-request authentication here.
Prepare(*http.Request) error Prepare(*http.Request) error
} }
@@ -61,9 +67,11 @@ type Config struct {
AzureManagedIdentity *azureManagedIdentitiesAuthPlugin `json:"azure_managed_identity,omitempty"` AzureManagedIdentity *azureManagedIdentitiesAuthPlugin `json:"azure_managed_identity,omitempty"`
Plugin *string `json:"plugin,omitempty"` Plugin *string `json:"plugin,omitempty"`
} `json:"credentials"` } `json:"credentials"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
keys map[string]*keys.Config keys map[string]*keys.Config
logger logging.Logger logger logging.Logger
minTLSVersion uint16
cipherSuites *[]uint16
} }
// Equal returns true if this client config is equal to the other. // Equal returns true if this client config is equal to the other.
@@ -112,6 +120,14 @@ func (c *Config) AuthPlugin(lookup AuthPluginLookupFunc) (HTTPAuthPlugin, error)
return candidate, nil return candidate, nil
} }
// clientCache holds the cached HTTP client and related state with thread-safe initialization
type clientCache struct {
mu sync.Mutex
httpClient *http.Client
authPlugin HTTPAuthPlugin
initErr error
}
// Client implements an HTTP/REST client for communicating with remote // Client implements an HTTP/REST client for communicating with remote
// services. // services.
type Client struct { type Client struct {
@@ -123,6 +139,7 @@ type Client struct {
logger logging.Logger logger logging.Logger
loggerFields map[string]any loggerFields map[string]any
distributedTacingOpts tracing.Options distributedTacingOpts tracing.Options
cache *clientCache
} }
// Name returns an option that overrides the service name on the client. // Name returns an option that overrides the service name on the client.
@@ -156,6 +173,20 @@ func DistributedTracingOpts(tr tracing.Options) func(*Client) {
} }
} }
// MinTLSVersion sets the minimum TLS version for the client
func MinTLSVersion(v uint16) func(*Client) {
return func(c *Client) {
c.config.minTLSVersion = v
}
}
// CipherSuites sets the cipher suites for the client
func CipherSuites(cs *[]uint16) func(*Client) {
return func(c *Client) {
c.config.cipherSuites = cs
}
}
// New returns a new Client for config. // New returns a new Client for config.
func New(config []byte, keys map[string]*keys.Config, opts ...func(*Client)) (Client, error) { func New(config []byte, keys map[string]*keys.Config, opts ...func(*Client)) (Client, error) {
var parsedConfig Config var parsedConfig Config
@@ -174,6 +205,7 @@ func New(config []byte, keys map[string]*keys.Config, opts ...func(*Client)) (Cl
client := Client{ client := Client{
config: parsedConfig, config: parsedConfig,
cache: &clientCache{},
} }
for _, f := range opts { for _, f := range opts {
@@ -189,6 +221,36 @@ func New(config []byte, keys map[string]*keys.Config, opts ...func(*Client)) (Cl
return client, nil return client, nil
} }
func (c *Client) ensureHTTPClient() error {
c.cache.mu.Lock()
defer c.cache.mu.Unlock()
if c.cache.httpClient != nil && c.cache.authPlugin != nil {
return c.cache.initErr
}
plugin, err := c.config.AuthPlugin(c.authPluginLookup)
if err != nil {
c.cache.initErr = err
return err
}
hc, err := plugin.NewClient(c.config)
if err != nil {
c.cache.initErr = err
return err
}
if len(c.distributedTacingOpts) > 0 {
hc.Transport = tracing.NewTransport(hc.Transport, c.distributedTacingOpts)
}
c.cache.httpClient = hc
c.cache.authPlugin = plugin
c.cache.initErr = nil
return nil
}
// AuthPluginLookup returns the lookup function to find a custom registered // AuthPluginLookup returns the lookup function to find a custom registered
// auth plugin by its name. // auth plugin by its name.
func (c Client) AuthPluginLookup() AuthPluginLookupFunc { func (c Client) AuthPluginLookup() AuthPluginLookupFunc {
@@ -208,6 +270,7 @@ func (c Client) Config() *Config {
// SetResponseHeaderTimeout sets the "ResponseHeaderTimeout" in the http client's Transport // SetResponseHeaderTimeout sets the "ResponseHeaderTimeout" in the http client's Transport
func (c Client) SetResponseHeaderTimeout(timeout *int64) Client { func (c Client) SetResponseHeaderTimeout(timeout *int64) Client {
c.config.ResponseHeaderTimeoutSeconds = timeout c.config.ResponseHeaderTimeoutSeconds = timeout
c.cache = &clientCache{}
return c return c
} }
@@ -253,20 +316,10 @@ func (c Client) WithBytes(body []byte) Client {
// Do executes a request using the client. // Do executes a request using the client.
func (c Client) Do(ctx context.Context, method, path string) (*http.Response, error) { func (c Client) Do(ctx context.Context, method, path string) (*http.Response, error) {
plugin, err := c.config.AuthPlugin(c.authPluginLookup) if err := c.ensureHTTPClient(); err != nil {
if err != nil {
return nil, err return nil, err
} }
hc, err := plugin.NewClient(c.config)
if err != nil {
return nil, err
}
if len(c.distributedTacingOpts) > 0 {
hc.Transport = tracing.NewTransport(hc.Transport, c.distributedTacingOpts)
}
path = strings.Trim(path, "/") path = strings.Trim(path, "/")
var body io.Reader var body io.Reader
@@ -296,7 +349,12 @@ func (c Client) Do(ctx context.Context, method, path string) (*http.Response, er
req.Header.Add(key, value) req.Header.Add(key, value)
} }
if err := plugin.Prepare(req); err != nil { c.cache.mu.Lock()
authPlugin := c.cache.authPlugin
httpClient := c.cache.httpClient
c.cache.mu.Unlock()
if err := authPlugin.Prepare(req); err != nil {
return nil, err return nil, err
} }
@@ -310,7 +368,7 @@ func (c Client) Do(ctx context.Context, method, path string) (*http.Response, er
c.logger.WithFields(c.loggerFields).Debug("Sending request.") c.logger.WithFields(c.loggerFields).Debug("Sending request.")
} }
resp, err := hc.Do(req) resp, err := httpClient.Do(req)
if resp != nil && c.logger.GetLevel() >= logging.Debug { if resp != nil && c.logger.GetLevel() >= logging.Debug {
// Only log for debug purposes. If an error occurred, the caller should handle // Only log for debug purposes. If an error occurred, the caller should handle
-177
View File
@@ -1409,159 +1409,6 @@ func newTestBearerClient(t *testing.T, ts *testServer, tokenPath string) *Client
return &client return &client
} }
func TestClientCert(t *testing.T) {
t.Parallel()
ts := testServer{
t: t,
tls: true,
expectClientCert: true,
}
ts.start()
defer ts.stop()
files := map[string]string{
"client.pem": string(ts.clientCertPem),
"client.key": string(ts.clientCertKey),
}
test.WithTempFS(files, func(path string) {
certPath := filepath.Join(path, "client.pem")
keyPath := filepath.Join(path, "client.key")
client := newTestClient(t, &ts, certPath, keyPath)
ctx := t.Context()
if _, err := client.Do(ctx, "GET", "test"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Scramble the keys in the server
ts.stop()
ts.start()
// Ensure the keys don't work anymore, make a new client as the url will have changed
client = newTestClient(t, &ts, certPath, keyPath)
_, err := client.Do(ctx, "GET", "test")
expectedErrMsg := func(s string) bool {
switch {
case strings.Contains(s, "tls: unknown certificate authority"):
case strings.Contains(s, "tls: bad certificate"):
default:
return false
}
return true
}
if err == nil || !expectedErrMsg(err.Error()) {
t.Fatalf("Unexpected error %v", err)
}
// Update the key files and try again..
if err := os.WriteFile(filepath.Join(path, "client.pem"), ts.clientCertPem, 0600); err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if err := os.WriteFile(filepath.Join(path, "client.key"), ts.clientCertKey, 0600); err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if _, err := client.Do(ctx, "GET", "test"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
})
}
func TestClientCertPassword(t *testing.T) {
t.Parallel()
ts := testServer{
t: t,
tls: true,
expectClientCert: true,
clientCertPassword: "password",
}
ts.start()
defer ts.stop()
files := map[string]string{
"client.pem": string(ts.clientCertPem),
"client.key": string(ts.clientCertKey),
}
test.WithTempFS(files, func(path string) {
certPath := filepath.Join(path, "client.pem")
keyPath := filepath.Join(path, "client.key")
client := newTestClient(t, &ts, certPath, keyPath)
ctx := t.Context()
if _, err := client.Do(ctx, "GET", "test"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
})
}
func TestClientTLSWithCustomCACert(t *testing.T) {
t.Parallel()
ts := testServer{
t: t,
tls: true,
expectClientCert: true,
}
ts.start()
defer ts.stop()
files := map[string]string{
"client.pem": string(ts.clientCertPem),
"client.key": string(ts.clientCertKey),
"ca.pem": string(ts.rootCertPEM),
}
test.WithTempFS(files, func(path string) {
certPath := filepath.Join(path, "client.pem")
keyPath := filepath.Join(path, "client.key")
ts.caCert = filepath.Join(path, "ca.pem")
client := newTestClient(t, &ts, certPath, keyPath)
ctx := t.Context()
if _, err := client.Do(ctx, "GET", "test"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
})
}
func TestClientTLSWithCustomCACertAndSystemCA(t *testing.T) {
t.Parallel()
ts := testServer{
t: t,
tls: true,
expectClientCert: true,
expectSystemCA: true,
}
ts.start()
defer ts.stop()
files := map[string]string{
"client.pem": string(ts.clientCertPem),
"client.key": string(ts.clientCertKey),
"ca.pem": string(ts.rootCertPEM),
}
test.WithTempFS(files, func(path string) {
certPath := filepath.Join(path, "client.pem")
keyPath := filepath.Join(path, "client.key")
ts.caCert = filepath.Join(path, "ca.pem")
client := newTestClient(t, &ts, certPath, keyPath)
ctx := t.Context()
if _, err := client.Do(ctx, "GET", "test"); err != nil {
t.Fatalf("Unexpected error: %v", err)
}
})
}
func TestOauth2ClientCredentials(t *testing.T) { func TestOauth2ClientCredentials(t *testing.T) {
t.Parallel() t.Parallel()
@@ -2125,30 +1972,6 @@ func TestDebugLoggingRequestMaskAuthorizationHeader(t *testing.T) {
} }
} }
func newTestClient(t *testing.T, ts *testServer, certPath string, keypath string) *Client {
config := fmt.Sprintf(`{
"name": "foo",
"url": %q,
"allow_insecure_tls": true,
"tls": {"ca_cert": %q, system_ca_required: %v},
"credentials": {
"client_tls": {
"cert": %q,
"private_key": %q
}
}
}`, ts.server.URL, ts.caCert, ts.expectSystemCA, certPath, keypath)
client, err := New([]byte(config), map[string]*keys.Config{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if ts.clientCertPassword != "" {
client.Config().Credentials.ClientTLS.PrivateKeyPassphrase = ts.clientCertPassword
}
return &client
}
type testPluginCustomizer func(c *Config) type testPluginCustomizer func(c *Config)
type testServer struct { type testServer struct {
+2
View File
@@ -516,6 +516,8 @@ func NewRuntime(ctx context.Context, params Params) (*Runtime, error) {
plugins.WithDistributedTracingOpts(params.DistributedTracingOpts), plugins.WithDistributedTracingOpts(params.DistributedTracingOpts),
plugins.WithBundleActivatorPlugin(params.BundleActivatorPlugin), plugins.WithBundleActivatorPlugin(params.BundleActivatorPlugin),
plugins.WithHooks(params.Hooks), plugins.WithHooks(params.Hooks),
plugins.WithMinTLSVersion(params.MinTLSVersion),
plugins.WithCipherSuites(params.CipherSuites),
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("config error: %w", err) return nil, fmt.Errorf("config error: %w", err)
+3 -4
View File
@@ -34,6 +34,7 @@ import (
"github.com/open-policy-agent/opa/internal/json/patch" "github.com/open-policy-agent/opa/internal/json/patch"
"github.com/open-policy-agent/opa/v1/ast" "github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/bundle" "github.com/open-policy-agent/opa/v1/bundle"
"github.com/open-policy-agent/opa/v1/config"
"github.com/open-policy-agent/opa/v1/hooks" "github.com/open-policy-agent/opa/v1/hooks"
"github.com/open-policy-agent/opa/v1/logging" "github.com/open-policy-agent/opa/v1/logging"
"github.com/open-policy-agent/opa/v1/metrics" "github.com/open-policy-agent/opa/v1/metrics"
@@ -80,8 +81,6 @@ const (
) )
const ( const (
defaultMinTLSVersion = tls.VersionTLS12
// Set of handlers for use in the "handler" dimension of the duration metric. // Set of handlers for use in the "handler" dimension of the duration metric.
PromHandlerV0Data = "v0/data" PromHandlerV0Data = "v0/data"
PromHandlerV1Data = "v1/data" PromHandlerV1Data = "v1/data"
@@ -416,7 +415,7 @@ func (s *Server) WithMinTLSVersion(minTLSVersion uint16) *Server {
if slices.Contains(supportedTLSVersions, minTLSVersion) { if slices.Contains(supportedTLSVersions, minTLSVersion) {
s.minTLSVersion = minTLSVersion s.minTLSVersion = minTLSVersion
} else { } else {
s.minTLSVersion = defaultMinTLSVersion s.minTLSVersion = config.DefaultMinTLSVersion
} }
return s return s
} }
@@ -691,7 +690,7 @@ func (s *Server) getListenerForHTTPSServer(u *url.URL, h http.Handler, t httpLis
if s.minTLSVersion != 0 { if s.minTLSVersion != 0 {
cfg.MinVersion = s.minTLSVersion cfg.MinVersion = s.minTLSVersion
} else { } else {
cfg.MinVersion = defaultMinTLSVersion cfg.MinVersion = config.DefaultMinTLSVersion
} }
if s.cipherSuites != nil { if s.cipherSuites != nil {