@@ -273,6 +273,7 @@ type OAuth2 struct {
273273 Claims map [string ]any `yaml:"claims,omitempty" json:"claims,omitempty"`
274274 Scopes []string `yaml:"scopes,omitempty" json:"scopes,omitempty"`
275275 TokenURL string `yaml:"token_url,omitempty" json:"token_url,omitempty"`
276+ TokenURLFile string `yaml:"token_url_file,omitempty" json:"token_url_file,omitempty"`
276277 EndpointParams map [string ]string `yaml:"endpoint_params,omitempty" json:"endpoint_params,omitempty"`
277278 TLSConfig TLSConfig `yaml:"tls_config,omitempty"`
278279 ProxyConfig `yaml:",inline"`
@@ -302,6 +303,7 @@ func (o *OAuth2) SetDirectory(dir string) {
302303 return
303304 }
304305 o .ClientSecretFile = JoinDir (dir , o .ClientSecretFile )
306+ o .TokenURLFile = JoinDir (dir , o .TokenURLFile )
305307 o .TLSConfig .SetDirectory (dir )
306308}
307309
@@ -438,8 +440,11 @@ func (c *HTTPClientConfig) Validate() error {
438440 if len (c .OAuth2 .ClientID ) == 0 {
439441 return errors .New ("oauth2 client_id must be configured" )
440442 }
441- if len (c .OAuth2 .TokenURL ) == 0 {
442- return errors .New ("oauth2 token_url must be configured" )
443+ if len (c .OAuth2 .TokenURL ) == 0 && len (c .OAuth2 .TokenURLFile ) == 0 {
444+ return errors .New ("one of oauth2 token_url or token_url_file must be configured" )
445+ }
446+ if len (c .OAuth2 .TokenURL ) > 0 && len (c .OAuth2 .TokenURLFile ) > 0 {
447+ return errors .New ("at most one of oauth2 token_url & token_url_file must be configured" )
443448 }
444449 if c .OAuth2 .GrantType == grantTypeJWTBearer {
445450 if nonZeroCount (len (c .OAuth2 .ClientCertificateKey ) > 0 , len (c .OAuth2 .ClientCertificateKeyFile ) > 0 , len (c .OAuth2 .ClientCertificateKeyRef ) > 0 ) > 1 {
@@ -940,13 +945,15 @@ func (rt *basicAuthRoundTripper) CloseIdleConnections() {
940945}
941946
942947type oauth2RoundTripper struct {
943- mtx sync.RWMutex
944- lastRT * oauth2.Transport
945- lastSecret string
948+ mtx sync.RWMutex
949+ lastRT * oauth2.Transport
950+ lastSecret string
951+ lastTokenURL string
946952
947953 // Required for interaction with Oauth2 server.
948954 config * OAuth2
949955 oauthCredential SecretReader
956+ tokenURL SecretReader
950957 opts * httpClientOptions
951958 client * http.Client
952959}
@@ -966,20 +973,29 @@ func NewOAuth2RoundTripper(oauthCredential SecretReader, config *OAuth2, next ht
966973 opt .applyToHTTPClientOptions (& opts )
967974 }
968975
976+ var tokenURL SecretReader
977+ switch {
978+ case config .TokenURLFile != "" :
979+ tokenURL = NewFileSecret (config .TokenURLFile )
980+ default :
981+ tokenURL = NewInlineSecret (config .TokenURL )
982+ }
983+
969984 return & oauth2RoundTripper {
970985 config : config ,
971986 // A correct tokenSource will be added later on.
972987 lastRT : & oauth2.Transport {Base : next },
973988 opts : & opts ,
974989 oauthCredential : oauthCredential ,
990+ tokenURL : tokenURL ,
975991 }
976992}
977993
978994type oauth2TokenSourceConfig interface {
979995 TokenSource (ctx context.Context ) oauth2.TokenSource
980996}
981997
982- func (rt * oauth2RoundTripper ) newOauth2TokenSource (req * http.Request , clientCredential string ) (client * http.Client , source oauth2.TokenSource , err error ) {
998+ func (rt * oauth2RoundTripper ) newOauth2TokenSource (req * http.Request , clientCredential , tokenURL string ) (client * http.Client , source oauth2.TokenSource , err error ) {
983999 tlsConfig , err := NewTLSConfig (& rt .config .TLSConfig , WithSecretManager (rt .opts .secretManager ))
9841000 if err != nil {
9851001 return nil , nil , err
@@ -1045,7 +1061,7 @@ func (rt *oauth2RoundTripper) newOauth2TokenSource(req *http.Request, clientCred
10451061 PrivateKey : []byte (clientCredential ),
10461062 PrivateKeyID : rt .config .ClientCertificateKeyID ,
10471063 Scopes : rt .config .Scopes ,
1048- TokenURL : rt . config . TokenURL ,
1064+ TokenURL : tokenURL ,
10491065 SigningAlgorithm : sig ,
10501066 Iss : iss ,
10511067 Subject : rt .config .ClientID ,
@@ -1058,7 +1074,7 @@ func (rt *oauth2RoundTripper) newOauth2TokenSource(req *http.Request, clientCred
10581074 ClientID : rt .config .ClientID ,
10591075 ClientSecret : clientCredential ,
10601076 Scopes : rt .config .Scopes ,
1061- TokenURL : rt . config . TokenURL ,
1077+ TokenURL : tokenURL ,
10621078 EndpointParams : mapToValues (rt .config .EndpointParams ),
10631079 }
10641080 }
@@ -1082,6 +1098,7 @@ func (rt *oauth2RoundTripper) RoundTrip(req *http.Request) (*http.Response, erro
10821098
10831099 var (
10841100 secret string
1101+ tokenURL string
10851102 needsInit bool
10861103 )
10871104
@@ -1093,31 +1110,46 @@ func (rt *oauth2RoundTripper) RoundTrip(req *http.Request) (*http.Response, erro
10931110
10941111 rt .mtx .RLock ()
10951112 secret = rt .lastSecret
1113+ tokenURL = rt .lastTokenURL
10961114 needsInit = rt .lastRT .Source == nil
10971115 rt .mtx .RUnlock ()
10981116
10991117 // Fetch the secret if it's our first run or always if the secret can change.
1118+ newSecret := secret
11001119 if ! rt .oauthCredential .Immutable () || needsInit {
1101- newSecret , err := rt .oauthCredential .Fetch (req .Context ())
1120+ var err error
1121+ newSecret , err = rt .oauthCredential .Fetch (req .Context ())
11021122 if err != nil {
11031123 return nil , fmt .Errorf ("unable to read oauth2 client secret: %w" , err )
11041124 }
1105- if newSecret != secret || needsInit {
1106- // Secret changed or it's a first run. Rebuilt oauth2 setup.
1107- client , source , err := rt .newOauth2TokenSource (req , newSecret )
1108- if err != nil {
1109- return nil , err
1110- }
1125+ }
11111126
1112- rt .mtx .Lock ()
1113- rt .lastSecret = newSecret
1114- rt .lastRT .Source = source
1115- if rt .client != nil {
1116- rt .client .CloseIdleConnections ()
1117- }
1118- rt .client = client
1119- rt .mtx .Unlock ()
1127+ // Fetch the token URL if it's our first run or always if it can change (file-backed).
1128+ newTokenURL := tokenURL
1129+ if ! rt .tokenURL .Immutable () || needsInit {
1130+ var err error
1131+ newTokenURL , err = rt .tokenURL .Fetch (req .Context ())
1132+ if err != nil {
1133+ return nil , fmt .Errorf ("unable to read oauth2 token_url: %w" , err )
1134+ }
1135+ }
1136+
1137+ if newSecret != secret || newTokenURL != tokenURL || needsInit {
1138+ // Secret or token URL changed or it's a first run. Rebuild oauth2 setup.
1139+ client , source , err := rt .newOauth2TokenSource (req , newSecret , newTokenURL )
1140+ if err != nil {
1141+ return nil , err
1142+ }
1143+
1144+ rt .mtx .Lock ()
1145+ rt .lastSecret = newSecret
1146+ rt .lastTokenURL = newTokenURL
1147+ rt .lastRT .Source = source
1148+ if rt .client != nil {
1149+ rt .client .CloseIdleConnections ()
11201150 }
1151+ rt .client = client
1152+ rt .mtx .Unlock ()
11211153 }
11221154
11231155 rt .mtx .RLock ()
0 commit comments