Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GCPAuthenticator support without gcloud #2007

Merged
merged 1 commit into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,12 @@
<artifactId>jsr305</artifactId>
<version>${jsr305.version}</version>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>1.3.0</version>
<optional>true</optional>
</dependency>


<!-- tests -->
Expand Down
5 changes: 5 additions & 0 deletions util/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<optional>true</optional>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>junit</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package io.kubernetes.client.util.authenticators;

import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.kubernetes.client.util.KubeConfig;
Expand Down Expand Up @@ -40,17 +42,25 @@ public class GCPAuthenticator implements Authenticator {
static final String EXPIRY = "expiry";
static final String CMD_ARGS = "cmd-args";
static final String CMD_PATH = "cmd-path";
static final String SCOPES = "scopes";
static final String[] DEFAULT_SCOPES =
new String[] {
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/userinfo.email"
};

private static final Logger log = LoggerFactory.getLogger(GCPAuthenticator.class);

private final ProcessBuilder pb;
private GoogleCredentials gc;

public GCPAuthenticator() {
this(new ProcessBuilder());
this(new ProcessBuilder(), null);
}

public GCPAuthenticator(ProcessBuilder pb) {
public GCPAuthenticator(ProcessBuilder pb, GoogleCredentials gc) {
this.pb = pb;
this.gc = gc;
}

@Override
Expand Down Expand Up @@ -81,8 +91,39 @@ public boolean isExpired(Map<String, Object> config) {

@Override
public Map<String, Object> refresh(Map<String, Object> config) {
if (!config.containsKey(CMD_ARGS) || !config.containsKey(CMD_PATH))
throw new RuntimeException("Could not refresh token");
if (isCmd(config)) {
return refreshCmd(config);
}
// Google Application Credentials-based refresh
// https://cloud.google.com/kubernetes-engine/docs/how-to/api-server-authentication#environments-without-gcloud
String[] scopes = parseScopes(config);
try {
if (this.gc == null) this.gc = GoogleCredentials.getApplicationDefault().createScoped(scopes);
AccessToken accessToken = gc.getAccessToken();
config.put(ACCESS_TOKEN, accessToken.getTokenValue());
config.put(EXPIRY, accessToken.getExpirationTime());
return config;
} catch (IOException e) {
throw new RuntimeException("The Application Default Credentials are not available.", e);
}
}

public String[] parseScopes(Map<String, Object> config) {
String scopes = (String) config.get(SCOPES);
if (scopes == null) {
return DEFAULT_SCOPES;
}
if (scopes.isEmpty()) {
return new String[] {};
}
return scopes.split(",");
}

private boolean isCmd(Map<String, Object> config) {
return config.containsKey(CMD_ARGS) && config.containsKey(CMD_PATH);
}

private Map<String, Object> refreshCmd(Map<String, Object> config) {
String cmdPath = (String) config.get(CMD_PATH);
String cmdArgs = (String) config.get(CMD_ARGS);
List<String> fullCmd =
Expand Down
43 changes: 41 additions & 2 deletions util/src/test/java/io/kubernetes/client/util/KubeConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import static org.junit.Assert.*;

import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;
import io.kubernetes.client.util.authenticators.Authenticator;
import io.kubernetes.client.util.authenticators.AzureActiveDirectoryAuthenticator;
import io.kubernetes.client.util.authenticators.GCPAuthenticator;
Expand All @@ -25,6 +27,8 @@
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.sql.Date;
import java.time.Instant;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
Expand Down Expand Up @@ -151,7 +155,7 @@ public void testGCPAuthProviderStringDate() {
}

@Test
public void testGCPAuthProviderExpiredToken() {
public void testGCPAuthProviderExpiredTokenWithinGCloud() {
String gcpConfigExpiredToken =
"apiVersion: v1\n"
+ "contexts:\n"
Expand Down Expand Up @@ -191,7 +195,7 @@ public void testGCPAuthProviderExpiredToken() {
fail("Unexpected exception: " + ex);
}

KubeConfig.registerAuthenticator(new GCPAuthenticator(mockPB));
KubeConfig.registerAuthenticator(new GCPAuthenticator(mockPB, null));
try {
KubeConfig kc = KubeConfig.loadKubeConfig(new StringReader(gcpConfigExpiredToken));
assertEquals("new-fake-token", kc.getAccessToken());
Expand All @@ -201,6 +205,41 @@ public void testGCPAuthProviderExpiredToken() {
}
}

@Test
public void testGCPAuthProviderExpiredTokenWithoutGCloud() {
String gcpConfigExpiredToken =
"apiVersion: v1\n"
+ "contexts:\n"
+ "- context:\n"
+ " user: gke-cluster\n"
+ " name: foo-context\n"
+ "current-context: foo-context\n"
+ "users:\n"
+ "- name: gke-cluster\n"
+ " user:\n"
+ " auth-provider:\n"
+ " config:\n"
+ " access-token: fake-token\n"
+ " expiry: 1970-01-01T00:00:00Z\n"
+ " name: gcp";

String fakeToken = "new-fake-token";
String fakeTokenExpiry = "2121-08-05T02:30:24Z";

GoogleCredentials mockGC = Mockito.mock(GoogleCredentials.class);
Mockito.when(mockGC.getAccessToken())
.thenReturn(new AccessToken(fakeToken, Date.from(Instant.parse(fakeTokenExpiry))));

KubeConfig.registerAuthenticator(new GCPAuthenticator(null, mockGC));
try {
KubeConfig kc = KubeConfig.loadKubeConfig(new StringReader(gcpConfigExpiredToken));
assertEquals(fakeToken, kc.getAccessToken());
} catch (Exception ex) {
ex.printStackTrace();
fail("Unexpected exception: " + ex);
}
}

@Test
public void testAzureAuthProvider() {
KubeConfig.registerAuthenticator(new AzureActiveDirectoryAuthenticator());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@

import static org.assertj.core.api.Fail.fail;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;

import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -43,18 +48,24 @@ public class GCPAuthenticatorTest {
add(cmdArgsSplit[2]);
}
});

private static final String fakeToken = "new-fake-token";
private static final String fakeTokenExpiry = "2121-08-05T02:30:24Z";
private static final String fakeExecResult =
"{\n"
+ " \"credential\": {\n"
+ " \"access_token\": \"new-fake-token\",\n"
+ " \"access_token\": \""
+ fakeToken
+ "\",\n"
+ " \"id_token\": \"id-fake-token\",\n"
+ " \"token_expiry\": \"2121-08-05T02:30:24Z\"\n"
+ " \"token_expiry\": \""
+ fakeTokenExpiry
+ "\"\n"
+ " }\n"
+ "}";

private final ProcessBuilder mockPB = Mockito.mock(ProcessBuilder.class);
private final GCPAuthenticator gcpAuthenticator = new GCPAuthenticator(mockPB);
private final GoogleCredentials mockGC = Mockito.mock(GoogleCredentials.class);
private final GCPAuthenticator gcpAuthenticator = new GCPAuthenticator(mockPB, mockGC);

@Before
public void setup() {
Expand Down Expand Up @@ -183,4 +194,15 @@ public void testRefreshLeadingWhitespaceInPathAndArgs() {
List<String> executedCommand = mockPB.command();
MatcherAssert.assertThat(executedCommand, is(expectedCommand));
}

@Test
public void testRefreshApplicationDefaultCredentials() {
Date fakeTokenExpiryDate = Date.from(Instant.parse(fakeTokenExpiry));
Mockito.when(mockGC.getAccessToken())
.thenReturn(new AccessToken(fakeToken, fakeTokenExpiryDate));
final Map<String, Object> config = new HashMap<String, Object>() {};
final Map<String, Object> result = gcpAuthenticator.refresh(config);
assertEquals(fakeToken, result.get(GCPAuthenticator.ACCESS_TOKEN));
assertEquals(fakeTokenExpiryDate, result.get(GCPAuthenticator.EXPIRY));
}
}