Skip to content

Commit

Permalink
Merge pull request #967 from zshihang/master
Browse files Browse the repository at this point in the history
reload service account token after expiry
  • Loading branch information
k8s-ci-robot authored Jun 5, 2020
2 parents 0197ea7 + 41844ea commit 913ecb9
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public static void main(String[] args) throws IOException, ApiException {
// 4. master endpoints(ip, port) from pre-set environment variables
ApiClient client = ClientBuilder.cluster().build();

// if you prefer not to refresh service account token, please use:
// ApiClient client = ClientBuilder.oldCluster().build();

// set the global default api-client to the in-cluster one from above
Configuration.setDefaultApiClient(client);

Expand Down
25 changes: 23 additions & 2 deletions util/src/main/java/io/kubernetes/client/util/ClientBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.kubernetes.client.util.credentials.AccessTokenAuthentication;
import io.kubernetes.client.util.credentials.Authentication;
import io.kubernetes.client.util.credentials.KubeconfigAuthentication;
import io.kubernetes.client.util.credentials.TokenFileAuthentication;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
Expand Down Expand Up @@ -190,12 +191,12 @@ private static File findConfigInHomeDir() {
}

/**
* Creates a builder which is pre-configured from the cluster configuration.
* [DEPRECATED] Creates a builder which is pre-configured from the cluster configuration.
*
* @return <tt>ClientBuilder</tt> configured from the cluster configuration.
* @throws IOException if the Service Account Token Path or CA Path is not readable.
*/
public static ClientBuilder cluster() throws IOException {
public static ClientBuilder oldCluster() throws IOException {
final ClientBuilder builder = new ClientBuilder();

final String host = System.getenv(ENV_SERVICE_HOST);
Expand All @@ -211,6 +212,26 @@ public static ClientBuilder cluster() throws IOException {
return builder;
}

/**
* Creates a builder which is pre-configured from the cluster configuration.
*
* @return <tt>ClientBuilder</tt> configured from the cluster configuration where service account
* token will be reloaded.
* @throws IOException if the Service Account Token Path or CA Path is not readable.
*/
public static ClientBuilder cluster() throws IOException {
final ClientBuilder builder = new ClientBuilder();

final String host = System.getenv(ENV_SERVICE_HOST);
final String port = System.getenv(ENV_SERVICE_PORT);
builder.setBasePath(host, port);

builder.setCertificateAuthority(Files.readAllBytes(Paths.get(SERVICEACCOUNT_CA_PATH)));
builder.setAuthentication(new TokenFileAuthentication(SERVICEACCOUNT_TOKEN_PATH));

return builder;
}

protected ClientBuilder setBasePath(String host, String port) {
try {
Integer iPort = Integer.valueOf(port);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.kubernetes.client.util.credentials;

import io.kubernetes.client.openapi.ApiClient;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Instant;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

// TODO: prefer OpenAPI backed Auentication once it is available. see details in
// https://github.com/OpenAPITools/openapi-generator/pull/6036. currently, the
// workaround is to hijack the http request.
public class TokenFileAuthentication implements Authentication, Interceptor {
private String file;
private String token;
private Instant expiry;

public TokenFileAuthentication(String file) {
this.expiry = Instant.MIN;
this.file = file;
}

private String getToken() {
if (Instant.now().isAfter(this.expiry)) {
try {
this.token =
new String(Files.readAllBytes(Paths.get(this.file)), Charset.defaultCharset()).trim();
expiry = Instant.now().plusSeconds(60);
} catch (IOException ie) {
throw new RuntimeException("Cannot read file: " + this.file);
}
}

return this.token;
}

public void setExpiry(Instant expiry) {
this.expiry = expiry;
}

public void setFile(String file) {
this.file = file;
}

@Override
public void provide(ApiClient client) {
OkHttpClient withInterceptor = client.getHttpClient().newBuilder().addInterceptor(this).build();
client.setHttpClient(withInterceptor);
}

@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = chain.request();
Request newRequest;
newRequest = request.newBuilder().header("Authorization", "Bearer " + getToken()).build();
return chain.proceed(newRequest);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.kubernetes.client.util.credentials;

import static com.github.tomakehurst.wiremock.client.WireMock.*;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.google.common.io.Resources;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import java.io.IOException;
import java.time.Instant;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

public class TokenFileAuthenticationTest {
private static final String SERVICEACCOUNT_TOKEN1_PATH =
Resources.getResource("token1").getPath();
private static final String SERVICEACCOUNT_TOKEN2_PATH =
Resources.getResource("token2").getPath();
private static final int PORT = 8089;
private TokenFileAuthentication auth;

@Rule public WireMockRule wireMockRule = new WireMockRule(PORT);

@Before
public void setup() throws IOException {
final ApiClient client = new ApiClient();
client.setBasePath("http://localhost:" + PORT);
this.auth = new TokenFileAuthentication(SERVICEACCOUNT_TOKEN1_PATH);
this.auth.provide(client);
Configuration.setDefaultApiClient(client);
}

@Test
public void testTokenProvided() throws IOException, ApiException {
stubFor(
get(urlPathEqualTo("/api/v1/pods")).willReturn(okForContentType("application/json", "{}")));
CoreV1Api api = new CoreV1Api();

api.listPodForAllNamespaces(null, null, null, null, null, null, null, null, null);
WireMock.verify(
1,
getRequestedFor(urlPathEqualTo("/api/v1/pods"))
.withHeader("Authorization", equalTo("Bearer token1")));

this.auth.setFile(SERVICEACCOUNT_TOKEN2_PATH);
api.listPodForAllNamespaces(null, null, null, null, null, null, null, null, null);
WireMock.verify(
2,
getRequestedFor(urlPathEqualTo("/api/v1/pods"))
.withHeader("Authorization", equalTo("Bearer token1")));

this.auth.setExpiry(Instant.now().minusSeconds(1));
api.listPodForAllNamespaces(null, null, null, null, null, null, null, null, null);
WireMock.verify(
1,
getRequestedFor(urlPathEqualTo("/api/v1/pods"))
.withHeader("Authorization", equalTo("Bearer token2")));
}
}
1 change: 1 addition & 0 deletions util/src/test/resources/token1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
token1
1 change: 1 addition & 0 deletions util/src/test/resources/token2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
token2

0 comments on commit 913ecb9

Please sign in to comment.