|
12 | 12 | */
|
13 | 13 | package io.kubernetes.client.util;
|
14 | 14 |
|
| 15 | +import com.google.gson.JsonElement; |
| 16 | +import com.google.gson.JsonObject; |
| 17 | +import com.google.gson.JsonParseException; |
| 18 | +import com.google.gson.JsonParser; |
15 | 19 | import io.kubernetes.client.util.authenticators.Authenticator;
|
16 | 20 | import io.kubernetes.client.util.authenticators.AzureActiveDirectoryAuthenticator;
|
17 | 21 | import io.kubernetes.client.util.authenticators.GCPAuthenticator;
|
18 | 22 | import java.io.IOException;
|
| 23 | +import java.io.InputStream; |
| 24 | +import java.io.InputStreamReader; |
19 | 25 | import java.io.Reader;
|
20 | 26 | import java.nio.charset.StandardCharsets;
|
21 | 27 | import java.nio.file.FileSystems;
|
22 | 28 | import java.nio.file.Files;
|
23 | 29 | import java.nio.file.Paths;
|
24 | 30 | import java.util.ArrayList;
|
25 | 31 | import java.util.HashMap;
|
| 32 | +import java.util.List; |
26 | 33 | import java.util.Map;
|
27 | 34 | import org.apache.commons.codec.binary.Base64;
|
28 | 35 | import org.slf4j.Logger;
|
@@ -185,6 +192,7 @@ public String getPassword() {
|
185 | 192 | return getData(currentUser, "password");
|
186 | 193 | }
|
187 | 194 |
|
| 195 | + @SuppressWarnings("unchecked") |
188 | 196 | public String getAccessToken() {
|
189 | 197 | if (currentUser == null) {
|
190 | 198 | return null;
|
@@ -214,6 +222,60 @@ public String getAccessToken() {
|
214 | 222 | }
|
215 | 223 | }
|
216 | 224 | }
|
| 225 | + Object exec = currentUser.get("exec"); |
| 226 | + if (exec != null) { |
| 227 | + // TODO extract to helper method for clarity |
| 228 | + Map<String, Object> execMap = (Map<String, Object>) exec; |
| 229 | + // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins |
| 230 | + String apiVersion = (String) execMap.get("apiVersion"); |
| 231 | + if ("client.authentication.k8s.io/v1beta1".equals(apiVersion)) { // TODO or v1alpha1 is apparently identical and could be supported |
| 232 | + String command = (String) execMap.get("command"); |
| 233 | + List<Map<String, String>> env = (List) execMap.get("env"); |
| 234 | + List<String> args = (List) execMap.get("args"); |
| 235 | + // TODO relativize command to basedir of config file (requires KubeConfig to be given a basedir) |
| 236 | + List<String> argv = new ArrayList<>(); |
| 237 | + argv.add(command); |
| 238 | + if (args != null) { |
| 239 | + argv.addAll(args); |
| 240 | + } |
| 241 | + ProcessBuilder pb = new ProcessBuilder(argv); |
| 242 | + if (env != null) { |
| 243 | + // TODO apply |
| 244 | + } |
| 245 | + pb.redirectError(ProcessBuilder.Redirect.INHERIT); |
| 246 | + try { |
| 247 | + Process proc = pb.start(); |
| 248 | + JsonElement root = null; |
| 249 | + try (InputStream is = proc.getInputStream(); |
| 250 | + Reader r = new InputStreamReader(is, StandardCharsets.UTF_8)) { |
| 251 | + root = new JsonParser().parse(r); |
| 252 | + } catch (JsonParseException x) { |
| 253 | + log.error("Failed to parse output of " + command, x); |
| 254 | + } |
| 255 | + int r = proc.waitFor(); |
| 256 | + if (r == 0) { |
| 257 | + if (root != null) { |
| 258 | + // TODO verify .apiVersion and .kind = ExecCredential |
| 259 | + JsonObject status = root.getAsJsonObject().get("status").getAsJsonObject(); |
| 260 | + JsonElement token = status.get("token"); |
| 261 | + if (token != null) { |
| 262 | + return token.getAsString(); |
| 263 | + } |
| 264 | + // TODO handle clientCertificateData/clientKeyData (KubeconfigAuthentication is not yet set up for that to be dynamic) |
| 265 | + } |
| 266 | + } else { |
| 267 | + log.error("{} failed with exit code {}", command, r); |
| 268 | + } |
| 269 | + } catch (IOException | InterruptedException x) { |
| 270 | + log.error("Failed to run " + command, x); |
| 271 | + } |
| 272 | + // TODO cache tokens between calls, up to .status.expirationTimestamp |
| 273 | + // TODO a 401 is supposed to force a refresh, but KubeconfigAuthentication hard-codes AccessTokenAuthentication which does not support that |
| 274 | + // and anyway ClientBuilder only calls Authenticator.provide once per ApiClient; we would need to do it on every request |
| 275 | + } else { |
| 276 | + log.error("Unrecognized user.exec.apiVersion: {}", apiVersion); |
| 277 | + } |
| 278 | + } |
217 | 279 | if (currentUser.containsKey("token")) {
|
218 | 280 | return (String) currentUser.get("token");
|
219 | 281 | }
|
|
0 commit comments