|
14 | 14 | import java.security.PublicKey; |
15 | 15 | import java.security.interfaces.RSAPublicKey; |
16 | 16 | import java.time.Duration; |
| 17 | +import java.time.Instant; |
17 | 18 | import java.util.Base64; |
18 | 19 | import java.util.Collections; |
19 | 20 | import java.util.List; |
@@ -66,11 +67,21 @@ public class OkeWorkloadIdentityResourcePrincipalsFederationClient |
66 | 67 | private static final String JWT_FORMAT = "Bearer %s"; |
67 | 68 | private static final String KUBERNETES_SERVICE_HOST = "KUBERNETES_SERVICE_HOST"; |
68 | 69 | private static final int PROXYMUX_SERVER_PORT = 12250; |
| 70 | + private static final long MAX_RETRY_DELAY_SECONDS = 900; // 15 minutes |
69 | 71 | private final ServiceAccountTokenSupplier serviceAccountTokenSupplier; |
70 | 72 |
|
71 | 73 | /** The authentication provider to sign the internal requests. */ |
72 | 74 | private final OkeTenancyOnlyAuthenticationDetailsProvider provider; |
73 | 75 |
|
| 76 | + /** Tracks the time of the last failed refresh attempt. */ |
| 77 | + private volatile Instant lastFailureTime = null; |
| 78 | + |
| 79 | + /** |
| 80 | + * The duration for which token refresh should be skipped after a failure. Calculated as a |
| 81 | + * fraction of the remaining token validity. |
| 82 | + */ |
| 83 | + private volatile Duration backoffDuration = Duration.ZERO; |
| 84 | + |
74 | 85 | /** |
75 | 86 | * Constructor of OkeWorkloadIdentityResourcePrincipalsFederationClient. |
76 | 87 | * |
@@ -110,24 +121,63 @@ public OkeWorkloadIdentityResourcePrincipalsFederationClient( |
110 | 121 | @Override |
111 | 122 | public String getSecurityToken() { |
112 | 123 | SecurityTokenAdapter securityTokenAdapter = getSecurityTokenAdapter(); |
| 124 | + boolean isTokenValid = securityTokenAdapter.isValid(); |
| 125 | + |
| 126 | + // Check for valid token and ongoing backoff period |
| 127 | + if (isTokenValid && lastFailureTime != null) { |
| 128 | + // Check if we are still within the calculated backoff period |
| 129 | + Duration elapsedSinceFailure = Duration.between(lastFailureTime, Instant.now()); |
| 130 | + if (elapsedSinceFailure.compareTo(backoffDuration) < 0) { |
| 131 | + // Skip the token refresh request because of previous failure and valid token |
| 132 | + LOG.info( |
| 133 | + "Skipping token refresh due to recent failure (backoff active for {}s). Returning cached token.", |
| 134 | + backoffDuration.getSeconds()); |
| 135 | + return securityTokenAdapter.getSecurityToken(); |
| 136 | + } |
| 137 | + LOG.info("Backoff period expired. Attempting token refresh."); |
| 138 | + } |
| 139 | + |
113 | 140 | try { |
114 | 141 | Duration time = Duration.ZERO; |
115 | | - if (securityTokenAdapter.isValid()) { |
116 | | - if (securityTokenAdapter.getTokenValidDuration() != null) { |
117 | | - // Calculate the half of the token's total valid duration |
118 | | - Duration halfDuration = |
119 | | - securityTokenAdapter.getTokenValidDuration().dividedBy(2); |
120 | | - // Generate Jitter Factor: a random value between 0.95 and 1.05 (i.e., ±5%) |
121 | | - double jitterFactor = 1.0 + (Math.random() * 0.1 - 0.05); |
122 | | - // Apply the jitter factor |
123 | | - time = halfDuration.multipliedBy((long) (jitterFactor * 1000)).dividedBy(1000); |
124 | | - } |
| 142 | + if (isTokenValid) { |
| 143 | + // Calculate the half of the token's total valid duration |
| 144 | + Duration halfDuration = securityTokenAdapter.getTokenValidDuration().dividedBy(2); |
| 145 | + // Generate Jitter Factor: a random value between 0.95 and 1.05 (i.e., ±5%) |
| 146 | + double jitterFactor = 1.0 + (Math.random() * 0.1 - 0.05); |
| 147 | + time = halfDuration.multipliedBy((long) (jitterFactor * 1000)).dividedBy(1000); |
125 | 148 | } |
| 149 | + |
126 | 150 | String token = refreshAndGetSecurityTokenIfExpiringWithin(time); |
| 151 | + |
| 152 | + // Success: clear any pending failure state |
| 153 | + lastFailureTime = null; |
| 154 | + backoffDuration = Duration.ZERO; |
| 155 | + |
127 | 156 | logTokenInfo(token); |
128 | 157 | return token; |
129 | 158 | } catch (Exception e) { |
130 | | - LOG.info("Refresh RPST token failed, use cached RPST token.", e); |
| 159 | + LOG.error( |
| 160 | + "Refresh Workload Identity Auth token failed, use cached Workload Identity Auth token.", |
| 161 | + e); |
| 162 | + // Mark previous execution as failed |
| 163 | + lastFailureTime = Instant.now(); |
| 164 | + |
| 165 | + if (isTokenValid) { |
| 166 | + Duration remainingDuration = securityTokenAdapter.getTokenRemainingDuration(); |
| 167 | + LOG.info("remainingDurationSeconds: {}", remainingDuration.getSeconds()); |
| 168 | + |
| 169 | + backoffDuration = |
| 170 | + remainingDuration.getSeconds() < Duration.ofHours(2).getSeconds() |
| 171 | + ? Duration.ofSeconds(0) |
| 172 | + : Duration.ofSeconds(MAX_RETRY_DELAY_SECONDS); |
| 173 | + LOG.error( |
| 174 | + "Token refresh failed. Initiating backoff for {}s.", |
| 175 | + backoffDuration.getSeconds()); |
| 176 | + } else { |
| 177 | + backoffDuration = Duration.ZERO; |
| 178 | + LOG.error("Token refresh failed and cached token is invalid/expired. No backoff"); |
| 179 | + } |
| 180 | + |
131 | 181 | return securityTokenAdapter.getSecurityToken(); |
132 | 182 | } |
133 | 183 | } |
|
0 commit comments