@@ -133,6 +133,188 @@ func newStaticFederationClient(sessionToken string, supplier sessionKeySupplier)
133133 }, nil
134134}
135135
136+ // oAuth2FederationClient retrieves a security token from the scoped OAuth endpoint in Auth Service
137+ type oAuth2FederationClient struct {
138+ sessionKeySupplier sessionKeySupplier
139+ authClientKeyProvider common.KeyProvider
140+ authClient * common.BaseClient
141+ securityToken securityToken
142+ lastRefresh time.Time
143+ scope string
144+ targetCompartment string
145+ mux sync.Mutex
146+ }
147+
148+ var OAuthTokenStaleWindow = 20 * time .Minute
149+
150+ // newOAuth2FederationClient creates a new oAuth2FederationClient from the provided configProvider and Auth request parameters
151+ func newOAuth2FederationClient (configProvider common.ConfigurationProvider , scope string , targetCompartment string , sessionKeySupplier sessionKeySupplier ) (federationClient , error ) {
152+ client := & oAuth2FederationClient {}
153+ client .sessionKeySupplier = sessionKeySupplier
154+ region , err := configProvider .Region ()
155+ if err != nil {
156+ return nil , fmt .Errorf ("failed to build OAuth Federation Client: %s" , err .Error ())
157+ }
158+ authClient := newAuthClient (common .StringToRegion (region ), configProvider , "v1/oauth2/scoped" )
159+ client .authClient = authClient
160+ client .authClientKeyProvider = configProvider
161+ client .scope = scope
162+ client .targetCompartment = targetCompartment
163+ return client , nil
164+ }
165+
166+ // KeyID calls the KeyID method of the auth provider given to the federation client
167+ func (c * oAuth2FederationClient ) KeyID () (string , error ) {
168+ return c .authClientKeyProvider .KeyID ()
169+ }
170+
171+ // PrivateRSAKey calls the PrivateRSAKey method of the auth provider given to the federation client
172+ func (c * oAuth2FederationClient ) PrivateRSAKey () (* rsa.PrivateKey , error ) {
173+ return c .authClientKeyProvider .PrivateRSAKey ()
174+ }
175+
176+ func (c * oAuth2FederationClient ) GetClaim (key string ) (interface {}, error ) {
177+ c .mux .Lock ()
178+ defer c .mux .Unlock ()
179+
180+ if err := c .renewKeyAndSecurityTokenIfNotValid (); err != nil {
181+ return nil , err
182+ }
183+ return c .securityToken .GetClaim (key )
184+ }
185+
186+ // isTokenStale returns true if the JWT token is older than OAuthTokenStaleWindow
187+ func (c * oAuth2FederationClient ) isTokenStale () bool {
188+ return c .lastRefresh .IsZero () || time .Now ().After (c .lastRefresh .Add (OAuthTokenStaleWindow ))
189+ }
190+
191+ func (c * oAuth2FederationClient ) renewKeyAndSecurityTokenIfNotValid () (err error ) {
192+ return c .renewSecurityTokenIfNotValid ()
193+ }
194+
195+ func (c * oAuth2FederationClient ) renewSecurityTokenIfNotValid () (err error ) {
196+
197+ // Get a new token if this one is stale (or nil), even if it is still valid
198+ if c .securityToken == nil || c .isTokenStale () {
199+ if err = c .renewSecurityToken (); err != nil {
200+ if c .securityToken != nil && c .securityToken .Valid () {
201+ // Token is stale but still valid. We failed to get a new token
202+ // but we can still use the old one
203+ common .Debugln ("failed to refresh OAuth token. Using valid cached token" )
204+ return nil
205+ }
206+
207+ return fmt .Errorf ("failed to refresh token: %s" , err .Error ())
208+ }
209+ }
210+
211+ // Token exists and is not stale,
212+ // or token was stale and a new one was retrieved
213+ return nil
214+ }
215+
216+ func (c * oAuth2FederationClient ) renewSecurityToken () (err error ) {
217+ if err = c .sessionKeySupplier .Refresh (); err != nil {
218+ return fmt .Errorf ("failed to refresh session key: %s" , err .Error ())
219+ }
220+
221+ common .Logf ("Renewing security token at: %v\n " , time .Now ().Format ("15:04:05.000" ))
222+ if newToken , err := c .getSecurityToken (); err != nil {
223+ return fmt .Errorf ("failed to get security token: %s" , err .Error ())
224+ } else {
225+ // only update token if a new one was retrieved.
226+ c .lastRefresh = time .Now ()
227+ c .securityToken = newToken
228+ }
229+
230+ common .Logf ("Security token renewed at: %v\n " , time .Now ().Format ("15:04:05.000" ))
231+
232+ return nil
233+
234+ }
235+
236+ func (c * oAuth2FederationClient ) getSecurityToken () (securityToken , error ) {
237+ var err error
238+ var httpRequest http.Request
239+ var httpResponse * http.Response
240+ defer common .CloseBodyIfValid (httpResponse )
241+ for retry := 0 ; retry < 3 ; retry ++ {
242+ request := c .makeOAuthFederationRequest ()
243+
244+ if httpRequest , err = common .MakeDefaultHTTPRequestWithTaggedStruct (http .MethodPost , "" , request ); err != nil {
245+ return nil , fmt .Errorf ("failed to make http request: %s" , err .Error ())
246+ }
247+
248+ if httpResponse , err = c .authClient .Call (context .Background (), & httpRequest ); err == nil {
249+ break
250+ }
251+ // Don't retry on 4xx errors
252+ if httpResponse != nil && httpResponse .StatusCode >= 400 && httpResponse .StatusCode <= 499 {
253+ return nil , fmt .Errorf ("error %s returned by auth service: %s" , httpResponse .Status , err .Error ())
254+ }
255+ nextDuration := time .Duration (1000.0 * (math .Pow (2.0 , float64 (retry )))) * time .Millisecond
256+ time .Sleep (nextDuration )
257+ }
258+ if err != nil {
259+ return nil , fmt .Errorf ("failed to call: %s" , err .Error ())
260+ }
261+
262+ response := oAuthFederationResponse {}
263+ if err = common .UnmarshalResponse (httpResponse , & response ); err != nil {
264+ return nil , fmt .Errorf ("failed to unmarshal the response: %s" , err .Error ())
265+ }
266+
267+ return newPrincipalToken (response .Token .Token )
268+
269+ }
270+
271+ type oAuthFederationRequest struct {
272+ OAuthFederationDetails `contributesTo:"body"`
273+ }
274+
275+ // OAuthFederationDetails Scoped Oauth federation details
276+ // The scope type should correspond to the type of config provider used to create
277+ // the OAuth Federation Client
278+ type OAuthFederationDetails struct {
279+ Scope string `mandatory:"true" json:"scope,omitempty"`
280+ PublicKey string `mandatory:"true" json:"public_key,omitempty"`
281+ TargetCompartment string `mandatory:"true" json:"target_compartment,omitempty"`
282+ }
283+
284+ type oAuthFederationResponse struct {
285+ Token `presentIn:"body"`
286+ }
287+
288+ func (c * oAuth2FederationClient ) makeOAuthFederationRequest () * oAuthFederationRequest {
289+ publicKey := sanitizeCertificateString (string (c .sessionKeySupplier .PublicKeyPemRaw ()))
290+ details := OAuthFederationDetails {
291+ Scope : c .scope ,
292+ PublicKey : publicKey ,
293+ TargetCompartment : c .targetCompartment ,
294+ }
295+ return & oAuthFederationRequest {details }
296+ }
297+
298+ func (c * oAuth2FederationClient ) PrivateKey () (* rsa.PrivateKey , error ) {
299+ c .mux .Lock ()
300+ defer c .mux .Unlock ()
301+
302+ if err := c .renewSecurityTokenIfNotValid (); err != nil {
303+ return nil , err
304+ }
305+ return c .sessionKeySupplier .PrivateKey (), nil
306+ }
307+
308+ func (c * oAuth2FederationClient ) SecurityToken () (token string , err error ) {
309+ c .mux .Lock ()
310+ defer c .mux .Unlock ()
311+
312+ if err = c .renewSecurityTokenIfNotValid (); err != nil {
313+ return "" , err
314+ }
315+ return c .securityToken .String (), nil
316+ }
317+
136318// x509FederationClient retrieves a security token from Auth service.
137319type x509FederationClient struct {
138320 tenancyID string
@@ -151,7 +333,7 @@ func newX509FederationClient(region common.Region, tenancyID string, leafCertifi
151333 intermediateCertificateRetrievers : intermediateCertificateRetrievers ,
152334 }
153335 client .sessionKeySupplier = newSessionKeySupplier ()
154- authClient := newAuthClient (region , client )
336+ authClient := newAuthClient (region , client , "v1/x509" )
155337
156338 var err error
157339
@@ -176,7 +358,7 @@ func newX509FederationClientWithCerts(region common.Region, tenancyID string, le
176358 intermediateCertificateRetrievers : intermediateRetrievers ,
177359 }
178360 client .sessionKeySupplier = newSessionKeySupplier ()
179- authClient := newAuthClient (region , client )
361+ authClient := newAuthClient (region , client , "v1/x509" )
180362
181363 var err error
182364
@@ -194,15 +376,16 @@ var (
194376 bodyHeaders = []string {"content-length" , "content-type" , "x-content-sha256" }
195377)
196378
197- func newAuthClient (region common.Region , provider common.KeyProvider ) * common.BaseClient {
379+ func newAuthClient (region common.Region , provider common.KeyProvider , authBasePath string ) * common.BaseClient {
198380 signer := common .RequestSigner (provider , genericHeaders , bodyHeaders )
199381 client := common .DefaultBaseClientWithSigner (signer )
382+
200383 if regionURL , ok := os .LookupEnv ("OCI_SDK_AUTH_CLIENT_REGION_URL" ); ok {
201384 client .Host = regionURL
202385 } else {
203386 client .Host = region .Endpoint ("auth" )
204387 }
205- client .BasePath = "v1/x509"
388+ client .BasePath = authBasePath
206389
207390 if common .GlobalAuthClientCircuitBreakerSetting != nil {
208391 client .Configuration .CircuitBreaker = common .NewCircuitBreaker (common .GlobalAuthClientCircuitBreakerSetting )
0 commit comments