Skip to content

Commit

Permalink
backport: #4264 - Refresh token every minute
Browse files Browse the repository at this point in the history
Signed-off-by: Marc Nuri <[email protected]>
  • Loading branch information
Vlatombe authored and manusa committed Sep 28, 2022
1 parent 2900229 commit 029b085
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### 5.12-SNAPSHOT

#### Bugs
* Fix #2271: Support periodic refresh of access tokens before they expire
* Fix #3733: The authentication command from the .kube/config won't be discarded if no arguments are specified
* Fix #4365: backport of stopped future for informers to obtain the termination exception
* Fix #4383: bump snakeyaml from 1.28 to 1.33
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,52 +18,78 @@
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.http.BasicBuilder;
import io.fabric8.kubernetes.client.http.HttpClient;
import io.fabric8.kubernetes.client.http.HttpHeaders;
import io.fabric8.kubernetes.client.http.HttpResponse;
import io.fabric8.kubernetes.client.http.Interceptor;

import java.net.HttpURLConnection;
import java.time.Instant;
import java.time.temporal.ChronoUnit;

/**
* Interceptor for handling expired OIDC tokens.
*/
public class TokenRefreshInterceptor implements Interceptor {
public static final String NAME = "TOKEN";

public static final String NAME = "TOKEN";

private final Config config;
private HttpClient.Factory factory;


private Instant lastRefresh;

public TokenRefreshInterceptor(Config config, HttpClient.Factory factory) {
this.config = config;
this.lastRefresh = Instant.now();
this.factory = factory;
}


@Override
public void before(BasicBuilder headerBuilder, HttpHeaders headers) {
if (timeToRefresh()) {
refreshToken(headerBuilder);
}
}

@Override
public boolean afterFailure(BasicBuilder headerBuilder, HttpResponse<?> response) {
boolean resubmit = false;
if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
String currentContextName = null;
String newAccessToken = null;
return refreshToken(headerBuilder);
}
return false;
}

if (config.getCurrentContext() != null) {
currentContextName = config.getCurrentContext().getName();
}
Config newestConfig = Config.autoConfigure(currentContextName);
if (newestConfig.getAuthProvider() != null && newestConfig.getAuthProvider().getName().equalsIgnoreCase("oidc")) {
newAccessToken = OpenIDConnectionUtils.resolveOIDCTokenFromAuthConfig(newestConfig.getAuthProvider().getConfig(), factory.newBuilder());
} else {
newAccessToken = newestConfig.getOauthToken();
}
private boolean refreshToken(BasicBuilder headerBuilder) {
boolean resubmit = false;
String currentContextName = null;
if (config.getCurrentContext() != null) {
currentContextName = config.getCurrentContext().getName();
}
String newAccessToken;
Config newestConfig = Config.autoConfigure(currentContextName);
if (newestConfig.getAuthProvider() != null && newestConfig.getAuthProvider().getName().equalsIgnoreCase("oidc")) {
newAccessToken = OpenIDConnectionUtils.resolveOIDCTokenFromAuthConfig(newestConfig.getAuthProvider().getConfig(),
factory.newBuilder());
} else {
newAccessToken = newestConfig.getOauthToken();
}

if (newAccessToken != null) {
// Delete old Authorization header and append new one
headerBuilder
.setHeader("Authorization", "Bearer " + newAccessToken);
config.setOauthToken(newAccessToken);
resubmit = true;
}
if (newAccessToken != null) {
// Delete old Authorization header and append new one
headerBuilder
.setHeader("Authorization", "Bearer " + newAccessToken);
config.setOauthToken(newAccessToken);
resubmit = true;
}
return resubmit;
}

private boolean timeToRefresh() {
return lastRefresh.plus(1, ChronoUnit.MINUTES).isBefore(Instant.now());
}

// For testing only
void setLastRefresh(Instant lastRefresh) {
this.lastRefresh = lastRefresh;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Objects;

import static io.fabric8.kubernetes.client.Config.KUBERNETES_AUTH_SERVICEACCOUNT_TOKEN_FILE_SYSTEM_PROPERTY;
Expand Down Expand Up @@ -60,6 +62,31 @@ public void shouldAutoconfigureAfter401() throws IOException {
}
}

@Test
void shouldAutoconfigureAfter1Minute() throws Exception {
try {
// Prepare kubeconfig for autoconfiguration
File tempFile = Files.createTempFile("test", "kubeconfig").toFile();
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/token-refresh-interceptor/kubeconfig")),
Paths.get(tempFile.getPath()), StandardCopyOption.REPLACE_EXISTING);
System.setProperty(KUBERNETES_KUBECONFIG_FILE, tempFile.getAbsolutePath());

HttpRequest.Builder builder = Mockito.mock(HttpRequest.Builder.class, Mockito.RETURNS_SELF);

// Call
TokenRefreshInterceptor tokenRefreshInterceptor = new TokenRefreshInterceptor(Config.autoConfigure(null), null);
// Replace kubeconfig file
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/token-refresh-interceptor/kubeconfig.new")),
Paths.get(tempFile.getPath()), StandardCopyOption.REPLACE_EXISTING);
tokenRefreshInterceptor.setLastRefresh(Instant.now().minus(61, ChronoUnit.SECONDS));
tokenRefreshInterceptor.before(builder, null);
Mockito.verify(builder).setHeader("Authorization", "Bearer new token");
} finally {
// Remove any side effect
System.clearProperty(KUBERNETES_KUBECONFIG_FILE);
}
}

@Test
void shouldReloadInClusterServiceAccount() throws IOException {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority: testns/ca.pem
insecure-skip-tls-verify: true
server: https://172.28.128.4:8443
name: 172-28-128-4:8443
contexts:
- context:
cluster: 172-28-128-4:8443
namespace: testns
user: user/172-28-128-4:8443
name: testns/172-28-128-4:8443/user
current-context: testns/172-28-128-4:8443/user
kind: Config
preferences: {}
users:
- name: user/172-28-128-4:8443
user:
token: token
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: v1
clusters:
- cluster:
certificate-authority: testns/ca.pem
insecure-skip-tls-verify: true
server: https://172.28.128.4:8443
name: 172-28-128-4:8443
contexts:
- context:
cluster: 172-28-128-4:8443
namespace: testns
user: user/172-28-128-4:8443
name: testns/172-28-128-4:8443/user
current-context: testns/172-28-128-4:8443/user
kind: Config
preferences: {}
users:
- name: user/172-28-128-4:8443
user:
token: new token

0 comments on commit 029b085

Please sign in to comment.