diff --git a/v1/plugins/rest/auth.go b/v1/plugins/rest/auth.go index b35c35d733..93a32fc224 100644 --- a/v1/plugins/rest/auth.go +++ b/v1/plugins/rest/auth.go @@ -5,7 +5,6 @@ package rest import ( - "cmp" "context" "crypto/rand" "crypto/sha256" @@ -33,7 +32,6 @@ import ( "github.com/lestrrat-go/jwx/v3/jws" "github.com/open-policy-agent/opa/internal/providers/aws" "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/logging" ) @@ -258,9 +256,7 @@ type oauth2ClientCredentialsAuthPlugin struct { signingKey *keys.Config signingKeyParsed any tokenCache *oauth2Token - tlsSkipVerify bool - minTLSVersion uint16 - cipherSuites *[]uint16 + tokenTLSConfig *tls.Config logger logging.Logger } @@ -502,10 +498,14 @@ func (ap *oauth2ClientCredentialsAuthPlugin) NewClient(c Config) (*http.Client, } } - // Inherit TLS settings from the "parent" config - ap.tlsSkipVerify = c.AllowInsecureTLS - ap.minTLSVersion = c.minTLSVersion - ap.cipherSuites = c.cipherSuites + // Inherit TLS config from the "parent" service config so that the token + // endpoint client uses the same CA certs, min TLS version, cipher suites, + // and insecure-skip-verify setting as the service itself. + ap.tokenTLSConfig = t.Clone() + // The token URL is always https (validated below). DefaultTLSConfig only + // sets InsecureSkipVerify when the *service* URL is https, but we need + // the setting to apply to the token endpoint regardless. + ap.tokenTLSConfig.InsecureSkipVerify = c.AllowInsecureTLS ap.logger = c.logger @@ -661,15 +661,7 @@ func (ap *oauth2ClientCredentialsAuthPlugin) requestToken(ctx context.Context) ( r.Header.Add(k, v) } - 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) + client := DefaultRoundTripperClient(ap.tokenTLSConfig.Clone(), 10) response, err := client.Do(r) if err != nil { return nil, err diff --git a/v1/plugins/rest/rest_test.go b/v1/plugins/rest/rest_test.go index ea6aed47bc..3deeb1b069 100644 --- a/v1/plugins/rest/rest_test.go +++ b/v1/plugins/rest/rest_test.go @@ -1481,6 +1481,56 @@ func TestOauth2ClientCredentials(t *testing.T) { } } +func TestOauth2ClientCredentialsTokenEndpointTLSCACert(t *testing.T) { + t.Parallel() + + // Service URL server (plain HTTP) + ts := testServer{t: t, expBearerToken: "token_1"} + ts.start() + defer ts.stop() + + // OAuth2 token server (TLS with self-signed cert) + ots := oauth2TestServer{t: t} + ots.start() + defer ots.stop() + + caCertPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: ots.server.Certificate().Raw, + }) + caFile := filepath.Join(t.TempDir(), "ca.pem") + if err := os.WriteFile(caFile, caCertPEM, 0o644); err != nil { + t.Fatal(err) + } + + // Configure without allow_insecure_tls — the CA cert alone should be + // sufficient for the token endpoint TLS handshake. + config := fmt.Sprintf(`{ + "name": "foo", + "url": %q, + "tls": { + "ca_cert": %q + }, + "credentials": { + "oauth2": { + "token_url": "%v/token", + "client_id": "client_one", + "client_secret": "super_secret" + } + } + }`, ts.server.URL, caFile, ots.server.URL) + + client, err := New([]byte(config), map[string]*keys.Config{}) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + + _, err = client.Do(t.Context(), "GET", "test") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } +} + func TestOauth2ClientCredentialsExpiringTokenIsRefreshed(t *testing.T) { t.Parallel()