Skip to content

Commit 1aaaec9

Browse files
iting0321iting0321
andauthored
Make OPA file-based bearer token timing configurable (#4972)
* make OPA file token timing configurable * update OPA config docs for file token timing --------- Co-authored-by: iting0321 <iting0321@MacBook-11111156.local>
1 parent 1001073 commit 1aaaec9

7 files changed

Lines changed: 65 additions & 2 deletions

File tree

extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaAuthorizationConfig.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,27 @@ interface FileBasedConfig {
174174
*/
175175
Optional<Duration> jwtExpirationBuffer();
176176

177+
/**
178+
* How long to wait for the first token load before failing a request. Defaults to 5 seconds.
179+
*/
180+
Optional<Duration> initialTokenWait();
181+
182+
/** How long to wait before retrying after a failed token refresh. Defaults to 1 second. */
183+
Optional<Duration> refreshRetryInterval();
184+
177185
default void validate() {
178186
checkArgument(
179187
refreshInterval().isEmpty() || refreshInterval().get().isPositive(),
180188
"refreshInterval must be positive");
181189
checkArgument(
182190
jwtExpirationBuffer().isEmpty() || jwtExpirationBuffer().get().isPositive(),
183191
"jwtExpirationBuffer must be positive");
192+
checkArgument(
193+
initialTokenWait().isEmpty() || initialTokenWait().get().isPositive(),
194+
"initialTokenWait must be positive");
195+
checkArgument(
196+
refreshRetryInterval().isEmpty() || refreshRetryInterval().get().isPositive(),
197+
"refreshRetryInterval must be positive");
184198
}
185199
}
186200
}

extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,13 +170,17 @@ private BearerTokenProvider createBearerTokenProvider(
170170
Duration refreshInterval = fileConfig.refreshInterval().orElse(Duration.ofMinutes(5));
171171
boolean jwtExpirationRefresh = fileConfig.jwtExpirationRefresh().orElse(true);
172172
Duration jwtExpirationBuffer = fileConfig.jwtExpirationBuffer().orElse(Duration.ofMinutes(1));
173+
Duration initialTokenWait = fileConfig.initialTokenWait().orElse(Duration.ofSeconds(5));
174+
Duration refreshRetryInterval =
175+
fileConfig.refreshRetryInterval().orElse(Duration.ofSeconds(1));
173176

174177
return new FileBearerTokenProvider(
175178
fileConfig.path(),
176179
refreshInterval,
177180
jwtExpirationRefresh,
178181
jwtExpirationBuffer,
179-
Duration.ofSeconds(5), // TODO: make configurable
182+
initialTokenWait,
183+
refreshRetryInterval,
180184
asyncExec,
181185
clock::instant);
182186
} else {

extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/token/FileBearerTokenProvider.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public class FileBearerTokenProvider implements BearerTokenProvider {
6868
private final AsyncExec asyncExec;
6969
private final CompletableFuture<String> initialTokenFuture = new CompletableFuture<>();
7070
private final long initialTokenWaitMillis;
71+
private final Duration refreshRetryInterval;
7172

7273
private volatile String cachedToken;
7374
private volatile Instant lastRefresh;
@@ -81,6 +82,9 @@ public class FileBearerTokenProvider implements BearerTokenProvider {
8182
* @param refreshInterval how often to check for token file changes (fallback for non-JWT tokens)
8283
* @param jwtExpirationRefresh whether to use JWT expiration for refresh timing
8384
* @param jwtExpirationBuffer buffer time before JWT expiration to refresh the token
85+
* @param initialTokenWait how long getToken waits for the first successful token refresh
86+
* @param refreshRetryInterval how long to wait before retrying a failed token refresh
87+
* @param asyncExec asynchronous executor used to schedule refresh attempts
8488
* @param clock clock instance for time operations
8589
* @throws IllegalStateException if the initial token cannot be loaded from the file
8690
*/
@@ -90,13 +94,15 @@ public FileBearerTokenProvider(
9094
boolean jwtExpirationRefresh,
9195
Duration jwtExpirationBuffer,
9296
Duration initialTokenWait,
97+
Duration refreshRetryInterval,
9398
AsyncExec asyncExec,
9499
Supplier<Instant> clock) {
95100
this.tokenFilePath = tokenFilePath;
96101
this.refreshInterval = refreshInterval;
97102
this.jwtExpirationRefresh = jwtExpirationRefresh;
98103
this.jwtExpirationBuffer = jwtExpirationBuffer;
99104
this.initialTokenWaitMillis = initialTokenWait.toMillis();
105+
this.refreshRetryInterval = refreshRetryInterval;
100106
this.clock = clock;
101107
this.asyncExec = asyncExec;
102108

@@ -155,7 +161,7 @@ private void refreshTokenAttempt() {
155161
}
156162
} else {
157163
// Token refresh did not succeed, retry soon
158-
delay = Duration.ofSeconds(1); // TODO: make configurable
164+
delay = refreshRetryInterval;
159165
}
160166
scheduleRefreshAttempt(delay);
161167
}

extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ public void testFactoryWithFileBasedTokenConfiguration() throws IOException {
123123
true,
124124
Duration.ofMinutes(1),
125125
Duration.ofSeconds(10),
126+
Duration.ofSeconds(1),
126127
asyncExec,
127128
Clock.systemUTC()::instant)) {
128129

extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/token/FileBearerTokenProviderTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public void testInitialRefresh() throws IOException {
6060
true,
6161
Duration.ofMinutes(1),
6262
Duration.ofMillis(1),
63+
Duration.ofSeconds(1),
6364
asyncExec,
6465
monotonicClock::currentInstant)) {
6566

@@ -122,6 +123,7 @@ public void testLoadTokenFromFileWithWhitespace() throws IOException {
122123
true,
123124
Duration.ofMinutes(1),
124125
Duration.ofMillis(1),
126+
Duration.ofSeconds(1),
125127
asyncExec,
126128
monotonicClock::currentInstant)) {
127129

@@ -134,6 +136,31 @@ public void testLoadTokenFromFileWithWhitespace() throws IOException {
134136
}
135137
}
136138

139+
@Test
140+
public void testRefreshRetryInterval() throws IOException {
141+
Path tokenFile = tempDir.resolve("token.txt");
142+
Files.writeString(tokenFile, "");
143+
144+
MutableMonotonicClock monotonicClock = new MutableMonotonicClock();
145+
MockAsyncExec asyncExec = new MockAsyncExec(monotonicClock);
146+
147+
try (FileBearerTokenProvider provider =
148+
new FileBearerTokenProvider(
149+
tokenFile,
150+
Duration.ofMinutes(5),
151+
true,
152+
Duration.ofMinutes(1),
153+
Duration.ofMillis(1),
154+
Duration.ofSeconds(42),
155+
asyncExec,
156+
monotonicClock::currentInstant)) {
157+
asyncExec.readyCallables().forEach(MockAsyncExec.Task::call);
158+
159+
assertThat(asyncExec.readyCount(monotonicClock.currentInstant().plusSeconds(41))).isZero();
160+
assertThat(asyncExec.readyCount(monotonicClock.currentInstant().plusSeconds(42))).isOne();
161+
}
162+
}
163+
137164
@Test
138165
public void testTokenRefresh() throws IOException {
139166
// Create a temporary token file
@@ -153,6 +180,7 @@ public void testTokenRefresh() throws IOException {
153180
false,
154181
Duration.ofMinutes(1),
155182
Duration.ofMillis(1),
183+
Duration.ofSeconds(1),
156184
asyncExec,
157185
monotonicClock::currentInstant)) {
158186

@@ -195,6 +223,7 @@ public void testNonExistentFileThrows() {
195223
true,
196224
Duration.ofMinutes(1),
197225
Duration.ofMillis(1),
226+
Duration.ofSeconds(1),
198227
asyncExec,
199228
monotonicClock::currentInstant)
200229
.close())
@@ -225,6 +254,7 @@ public void testJwtExpirationRefresh() throws IOException {
225254
true,
226255
Duration.ofSeconds(3),
227256
Duration.ofMillis(1),
257+
Duration.ofSeconds(1),
228258
asyncExec,
229259
monotonicClock::currentInstant)) {
230260
// run outstanding token-refresh task
@@ -269,6 +299,7 @@ public void testJwtExpirationRefreshDisabled() throws IOException {
269299
false,
270300
Duration.ofSeconds(1),
271301
Duration.ofMillis(1),
302+
Duration.ofSeconds(1),
272303
asyncExec,
273304
monotonicClock::currentInstant)) {
274305
// run outstanding token-refresh task
@@ -315,6 +346,7 @@ public void testNonJwtTokenWithJwtRefreshEnabled() throws IOException {
315346
true,
316347
Duration.ofSeconds(1),
317348
Duration.ofMillis(1),
349+
Duration.ofSeconds(1),
318350
asyncExec,
319351
monotonicClock::currentInstant)) {
320352
// run outstanding token-refresh task
@@ -359,6 +391,7 @@ public void testJwtExpirationTooSoon() throws IOException {
359391
true,
360392
Duration.ofSeconds(60),
361393
Duration.ofMillis(1),
394+
Duration.ofSeconds(1),
362395
asyncExec,
363396
monotonicClock::currentInstant)) {
364397
// run outstanding token-refresh task
@@ -388,6 +421,7 @@ public void testJwtWithoutExpirationClaim() throws IOException {
388421
true,
389422
Duration.ofSeconds(1),
390423
Duration.ofMillis(1),
424+
Duration.ofSeconds(1),
391425
asyncExec,
392426
monotonicClock::currentInstant)) {
393427
// run outstanding token-refresh task

runtime/defaults/src/main/resources/application.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ polaris.oidc.principal-roles-mapper.type=default
274274
# polaris.authorization.opa.auth.bearer.file-based.refresh-interval=PT5M
275275
# polaris.authorization.opa.auth.bearer.file-based.jwt-expiration-refresh=true
276276
# polaris.authorization.opa.auth.bearer.file-based.jwt-expiration-buffer=PT1M
277+
# polaris.authorization.opa.auth.bearer.file-based.initial-token-wait=PT5S
278+
# polaris.authorization.opa.auth.bearer.file-based.refresh-retry-interval=PT1S
277279

278280
# Ranger authorizer
279281
# polaris.authorization.type=ranger

site/content/in-dev/unreleased/configuration/config-sections/smallrye-polaris_authorization_opa.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ Beta Feature: OPA authorization is currently in Beta and is not a stable releas
3636
| `polaris.authorization.opa.auth.bearer.file-based.refresh-interval` | | `duration` | How often to refresh file-based bearer tokens (defaults to 5 minutes if not specified) |
3737
| `polaris.authorization.opa.auth.bearer.file-based.jwt-expiration-refresh` | | `boolean` | Whether to automatically detect JWT tokens and use their 'exp' field for refresh timing. If true and the token is a valid JWT with an 'exp' claim, the token will be refreshed based on the expiration time minus the buffer, rather than the fixed refresh interval. Defaults to true if not specified. |
3838
| `polaris.authorization.opa.auth.bearer.file-based.jwt-expiration-buffer` | | `duration` | Buffer time before JWT expiration to refresh the token. Only used when jwtExpirationRefresh is true and the token is a valid JWT. Defaults to 1 minute if not specified. |
39+
| `polaris.authorization.opa.auth.bearer.file-based.initial-token-wait` | | `duration` | How long to wait for the first token load before failing a request. Defaults to 5 seconds. |
40+
| `polaris.authorization.opa.auth.bearer.file-based.refresh-retry-interval` | | `duration` | How long to wait before retrying after a failed token refresh. Defaults to 1 second. |
3941
| `polaris.authorization.opa.http.timeout` | `PT2S` | `duration` | |
4042
| `polaris.authorization.opa.http.verify-ssl` | `true` | `boolean` | |
4143
| `polaris.authorization.opa.http.trust-store-path` | | `path` | |

0 commit comments

Comments
 (0)