-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
GCP authentication does not support refreshing tokens #290
Comments
There's no way to fix this right now, someone needs to implement the token refresh. |
This breaks using the client. I had started on an integration then noticed the calls stopped working. Maybe mark the issue as contributions welcome if this isn't on the roadmap. val client = Config.defaultClient()
Configuration.setDefaultApiClient(client)
val api = CoreV1Api(client)
// kubectl get service --field-selector metadata.name=esp-my-pod
val fieldSelector = "metadata.name=esp-my-pod"
val result = api.listServiceForAllNamespaces(
null,
fieldSelector,
null,
"",
1,
null,
null,
2 * 60,
false) Exception in thread "main" java.lang.IllegalStateException: Unimplemented
at io.kubernetes.client.util.authenticators.GCPAuthenticator.refresh(GCPAuthenticator.java:49)
at io.kubernetes.client.util.KubeConfig.getAccessToken(KubeConfig.java:188)
at io.kubernetes.client.util.credentials.KubeconfigAuthentication.<init>(KubeconfigAuthentication.java:33)
at io.kubernetes.client.util.ClientBuilder.kubeconfig(ClientBuilder.java:165)
at io.kubernetes.client.util.ClientBuilder.standard(ClientBuilder.java:80)
at io.kubernetes.client.util.Config.defaultClient(Config.java:104)
at ParseProto.getIp(ParseProto.kt:59)
at ParseProto.main(ParseProto.kt:102) |
Does this mean that this java client cannot authenticate using Tokens and one of the remaining methods like OAuth or basic auth must be used ? |
This is strange, I don't want to use http basic auth and it is not clear to me how I can set up OAuth. There is even no |
There actually is a way that doesn't throw the
However there is another error #163 |
One design issue with this, at least as of 52b65f8, is that the io.kubernetes.client.util.credentials.Authentication interface is only asked to provide authentication for an ApiClient at construction time - when ClientBuilder is first constructing the ApiClient. To support tokens that change during the lifetime of an ApiClient instance (as with a GCP token that needs to be refreshed periodically), it seems like the design would have to change to have ApiClient periodically ask the Authentication (or some other interface) for a new or current token. Would a patch to implement such a change be welcomed? |
I would suggest that #238 be the way forward. Although Google Cloud does not yet advertise it, you can authenticate to GKE from users:
- name: gcp
user:
exec:
apiVersion: "client.authentication.k8s.io/v1beta1"
command: "sh"
args:
- "-c"
- |
gcloud config config-helper --format=json | jq '{"apiVersion": "client.authentication.k8s.io/v1beta1", "kind": "ExecCredential", "status": {"token": .credential.access_token, "expirationTimestamp": .credential.token_expiry}}' |
@mattnworb the trouble is that |
maybe I misunderstand the issue, but how is anyone using this library with either GKE or AWS? They both use short-lived tokens as I recall. This makes it impossible for me to create a working product that can talk to the kubernetes cluster on GKE. |
@hmeerlo we have an application that has to connect to the API of several GKE clusters. We have a scheduled thread that periodically executes something like the following: String newToken = ...; // fetch up-to-date token for GCP Service Account that is making the call
for (ApiClient apiClient : apiClients) {
client.setApiKey(newToken);
} I would much preferred of course if this refresh logic could be in the kubernetes-client library itself, but having the ability to change the apiKey of an already-constructed ApiClient at least unblocks things. |
@hmeerlo We're doing something of the same as @mattnworb. Wrapped the ApiClient in another service that checks that the token is valid before returning it to the calling code. Basically a singleton pattern with a refresh mechanism on the side. @mattnworb How are you refreshing the tokens? We're using a service-account.json file but resorted to putting it on the file system and having gcloud binary do calls to generate a token and then reading this from file. Feels sub-optimal to say the least. |
@mattnworb @haugene thanks for your insights, at least there is some light at the end of the tunnel :-) I agree with @haugene that some insight in how you do this would be helpful |
@haugene we use |
I am using it like this. package kubernetes.gcp;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;
import io.kubernetes.client.util.KubeConfig;
import io.kubernetes.client.util.authenticators.Authenticator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.time.Instant;
import java.util.Date;
import java.util.Map;
public class ReplacedGCPAuthenticator implements Authenticator {
private static final Logger log;
private static final String ACCESS_TOKEN = "access-token";
private static final String EXPIRY = "expiry";
static {
log = LoggerFactory.getLogger(io.kubernetes.client.util.authenticators.GCPAuthenticator.class);
}
private final GoogleCredentials credentials;
public ReplacedGCPAuthenticator(GoogleCredentials credentials) {
this.credentials = credentials;
}
public String getName() {
return "gcp";
}
public String getToken(Map<String, Object> config) {
return (String) config.get("access-token");
}
public boolean isExpired(Map<String, Object> config) {
Object expiryObj = config.get("expiry");
Instant expiry = null;
if (expiryObj instanceof Date) {
expiry = ((Date) expiryObj).toInstant();
} else if (expiryObj instanceof Instant) {
expiry = (Instant) expiryObj;
} else {
if (!(expiryObj instanceof String)) {
throw new RuntimeException("Unexpected object type: " + expiryObj.getClass());
}
expiry = Instant.parse((String) expiryObj);
}
return expiry != null && expiry.compareTo(Instant.now()) <= 0;
}
public Map<String, Object> refresh(Map<String, Object> config) {
try {
AccessToken accessToken = this.credentials.refreshAccessToken();
config.put(ACCESS_TOKEN, accessToken.getTokenValue());
config.put(EXPIRY, accessToken.getExpirationTime());
} catch (IOException e) {
throw new RuntimeException(e);
}
return config;
}
} Running in. //GoogleCredentials.fromStream(--something credential.json filestream--)
KubeConfig.registerAuthenticator(new ReplacedGCPAuthenticator(GoogleCredentials.getApplicationDefault()));
ApiClient client = Config.defaultClient();
Configuration.setDefaultApiClient(client);
CoreV1Api api = new CoreV1Api();
V1PodList list = api.listNamespacedPod("default", null, null, null, null, null, null, null, 30, Boolean.FALSE);
for (V1Pod item : list.getItems()) {
System.out.println(item.getMetadata().getName());
} |
Issues go stale after 90d of inactivity. If this issue is safe to close now please do so with Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
/remove-lifecycle stale |
@jhbae200 how do you think about sending the patch as a PR in the repo? |
Issues go stale after 90d of inactivity. If this issue is safe to close now please do so with Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
This issue still causes problems with running the client past authentication token revocation. I guess we will have to continue spamming this issue every 90 days to keep it from going stale... /remove-lifecycle stale |
Issues go stale after 90d of inactivity. If this issue is safe to close now please do so with Send feedback to sig-testing, kubernetes/test-infra and/or fejta. |
/remove-lifecycle stale What an annoying bot. Please stop closing issues that are still open, and affect many users! |
FYI: for out-of-cluster refresh token to work, the command with args is kept in the
I was trying to figure out it doesn't work with and out-of-cluster setup:
|
/lifecycle frozen |
I have the same problem here:
So what is the solution? |
@jhbae200 @brendandburns I'm facing this issue and have the requirement to authenticate against GKE from a Cloud Function (run this client in a Cloud Function). This means that What I did is implementing something very similar to #290 (comment), what would be the reason of not making that solution the proper |
I just did a PR supporting that feature. |
@jhbae200 thanks a lot for your quick response. Your PR implements that piece, but that does not support the 'full' flow from out-of-cluster auth perspective IMO. Let me elaborate a bit more. Following on the GCP docs example you link, this block in
with
But this Kube Java Client does not support that functionality out-of-the-box without having Wouldn't this functionality (have an automated way of completing a KubeConfig with token/expiry) be desirable in order to have I would be happy to try and attempt a further patch with some guideline if there's a desire to include this. Let me know your thoughts. |
If you want to use oauth2.0 (access token, refresh token), kubeconfig already supports oidc. |
@jhbae200 Yes, I mean, this could be done via OIDC or appended to KubeConfig. In both ways this setup would avoid having to have |
@jhbae200 I see you have a PR opened for this already, that was quick! I can maybe remove my hacky code pretty soon! Thanks! |
hello, w.r.t to gcloud based authentication in version 14.0.0: Now ~/.kube/config looks like this:
which is expected, so far so good. Now running my app results in:
Only after querying the cluster for example using 'kubectl get nodes' kubeconfig gets primed:
and subsequent executions of my app work even past expiration time of the token in kubeconfig (seems like the refresh works). Oddly enough, providing some expired token in the fields "access-token" and "expiry" works around this problem as well. Is this an issue or I could do better? |
I observe this issue too. Seems like a bug if the authenticator throws an exception if the "expiry" key is missing from the config when it sometimes is missing. |
Looks like the support without |
This relates to #143.
GCPAuthenticator does not implement the refresh() method.
How can the client be used without manually refreshing the access token if it is expired?
for reference, initializing the client (i.e. running the example) with an expired token will result in:
Caused by: java.lang.IllegalStateException: Unimplemented at io.kubernetes.client.util.authenticators.GCPAuthenticator.refresh(GCPAuthenticator.java:49) at io.kubernetes.client.util.KubeConfig.getAccessToken(KubeConfig.java:188) at io.kubernetes.client.util.credentials.KubeconfigAuthentication.<init>(KubeconfigAuthentication.java:33) at io.kubernetes.client.util.ClientBuilder.kubeconfig(ClientBuilder.java:165) at io.kubernetes.client.util.ClientBuilder.standard(ClientBuilder.java:80) at io.kubernetes.client.util.Config.defaultClient(Config.java:104) at serivces.KubernetesService.<clinit>(KubernetesService.groovy:23) ... 2 more
The text was updated successfully, but these errors were encountered: