From dacff5a4dfaa12d2313fc3dc2867e2ef72a3ef9b Mon Sep 17 00:00:00 2001 From: Andre Dietisheim Date: Wed, 21 Aug 2024 19:54:20 +0200 Subject: [PATCH] update token in file listed in KUBECONFIG env var (#6240) Signed-off-by: Andre Dietisheim --- .../io/fabric8/kubernetes/client/Config.java | 133 +++++++++++++----- .../client/utils/OpenIDConnectionUtils.java | 3 +- .../fabric8/kubernetes/client/ConfigTest.java | 25 ++++ .../test-kubeconfig-onlycurrentcontext | 7 + 4 files changed, 130 insertions(+), 38 deletions(-) create mode 100644 kubernetes-client-api/src/test/resources/test-kubeconfig-onlycurrentcontext diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/Config.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/Config.java index 10b1c09f633..cdd01f2e8bb 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/Config.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/Config.java @@ -119,7 +119,9 @@ public class Config { public static final String KUBERNETES_NAMESPACE_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"; public static final String KUBERNETES_NAMESPACE_FILE = "kubenamespace"; public static final String KUBERNETES_NAMESPACE_SYSTEM_PROPERTY = "kubernetes.namespace"; + @Deprecated public static final String KUBERNETES_KUBECONFIG_FILE = "kubeconfig"; + public static final String KUBERNETES_KUBECONFIG_FILES = "kubeconfig"; public static final String KUBERNETES_SERVICE_HOST_PROPERTY = "KUBERNETES_SERVICE_HOST"; public static final String KUBERNETES_SERVICE_PORT_PROPERTY = "KUBERNETES_SERVICE_PORT"; public static final String KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token"; @@ -854,40 +856,41 @@ private static boolean tryKubeConfig(Config config, String context) { if (!Utils.getSystemPropertyOrEnvVar(KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, true)) { return false; } - List kubeConfigFilenames = Arrays.asList(getKubeconfigFilenames()); - if (kubeConfigFilenames.isEmpty()) { + String[] kubeConfigFilenames = getKubeconfigFilenames(); + if (kubeConfigFilenames == null + || kubeConfigFilenames.length == 0) { return false; } - List allKubeConfigFiles = kubeConfigFilenames.stream() - .map(File::new) - .collect(Collectors.toList()); - File mainKubeConfig = allKubeConfigFiles.get(0); - io.fabric8.kubernetes.api.model.Config kubeConfig = createKubeconfig(mainKubeConfig); + List allFiles = Arrays.stream(kubeConfigFilenames) + .map(File::new) + .collect(Collectors.toList()); + File mainFile = allFiles.get(0); + io.fabric8.kubernetes.api.model.Config kubeConfig = createKubeconfig(mainFile); if (kubeConfig == null) { return false; } - config.file = mainKubeConfig; - config.files = allKubeConfigFiles; + config.file = mainFile; + config.files = allFiles; - List additionalConfigs = config.files.subList(1, allKubeConfigFiles.size()); + List additionalConfigs = config.files.subList(1, allFiles.size()); addAdditionalConfigs(kubeConfig, additionalConfigs); - return loadFromKubeconfig(config, context, mainKubeConfig); + return loadFromKubeconfig(config, context, kubeConfig); } private static void addAdditionalConfigs(io.fabric8.kubernetes.api.model.Config kubeConfig, List files) { if (files == null - || files.isEmpty()) { + || files.isEmpty()) { return; } files.stream() - .map(Config::createKubeconfig) - .filter(Objects::nonNull) - .forEach(additionalConfig -> { - addTo(additionalConfig.getContexts(), kubeConfig::getContexts, kubeConfig::setContexts); - addTo(additionalConfig.getClusters(), kubeConfig::getClusters, kubeConfig::setClusters); - addTo(additionalConfig.getUsers(), kubeConfig::getUsers, kubeConfig::setUsers); - }); + .map(Config::createKubeconfig) + .filter(Objects::nonNull) + .forEach(additionalConfig -> { + addTo(additionalConfig.getContexts(), kubeConfig::getContexts, kubeConfig::setContexts); + addTo(additionalConfig.getClusters(), kubeConfig::getClusters, kubeConfig::setClusters); + addTo(additionalConfig.getUsers(), kubeConfig::getUsers, kubeConfig::setUsers); + }); } private static io.fabric8.kubernetes.api.model.Config createKubeconfig(File file) { @@ -934,11 +937,13 @@ public static String getKubeconfigFilename() { public static String[] getKubeconfigFilenames() { String[] fileNames = null; - String fileName = Utils.getSystemPropertyOrEnvVar(KUBERNETES_KUBECONFIG_FILE); - - fileNames = fileName.split(File.pathSeparator); - if (fileNames.length == 0) { - fileNames = new String[] { new File(getHomeDir(), ".kube" + File.separator + "config").toString() }; + String fileName = Utils.getSystemPropertyOrEnvVar(KUBERNETES_KUBECONFIG_FILES); + if (fileName != null + && !fileName.isEmpty()) { + fileNames = fileName.split(File.pathSeparator); + if (fileNames.length == 0) { + fileNames = new String[]{new File(getHomeDir(), ".kube" + File.separator + "config").toString()}; + } } return fileNames; } @@ -954,21 +959,20 @@ private static String getKubeconfigContents(File kubeConfigFile) { return kubeconfigContents; } - private static boolean loadFromKubeconfig(Config config, String context, File kubeConfigFile) { - String contents = getKubeconfigContents(kubeConfigFile); - if (contents == null) { - return false; - } - return loadFromKubeconfig(config, context, contents); - } - // Note: kubeconfigPath is optional // It is only used to rewrite relative tls asset paths inside kubeconfig when a file is passed, and in the case that // the kubeconfig references some assets via relative paths. private static boolean loadFromKubeconfig(Config config, String context, String kubeconfigContents) { + if (kubeconfigContents != null && !kubeconfigContents.isEmpty()) { + return loadFromKubeconfig(config, context, KubeConfigUtils.parseConfigFromString(kubeconfigContents)); + } else { + return false; + } + } + + private static boolean loadFromKubeconfig(Config config, String context, io.fabric8.kubernetes.api.model.Config kubeConfig) { try { - if (kubeconfigContents != null && !kubeconfigContents.isEmpty()) { - io.fabric8.kubernetes.api.model.Config kubeConfig = KubeConfigUtils.parseConfigFromString(kubeconfigContents); + if (kubeConfig != null) { mergeKubeConfigContents(config, context, kubeConfig); return true; } @@ -1144,6 +1148,7 @@ protected static String getCommandWithFullyQualifiedPath(String command, String private static Context setCurrentContext(String context, Config config, io.fabric8.kubernetes.api.model.Config kubeConfig) { if (context != null) { + // override existing current-context kubeConfig.setCurrentContext(context); } Context currentContext = null; @@ -1734,17 +1739,73 @@ public NamedContext getCurrentContext() { public void setCurrentContext(NamedContext context) { this.currentContext = context; } - /** * * Returns the path to the file that this configuration was loaded from. Returns {@code null} if no file was used. + * @deprecated use {@link #getFiles} instead. * - * @return the path to the kubeConfig file + * @return the kubeConfig file */ + @Deprecated public File getFile() { return file; } + /** + * Returns the kube config files that are used to configure this client. + * Returns the files that are listed in the KUBERNETES_KUBECONFIG_FILES env or system variables. + * Returns the default kube config file if it's not set'. + * + * @return + */ + public List getFiles() { + return files; + } + + public KubeConfigFile getFile(String username) { + if (username == null + || username.isEmpty()) { + return null; + } + return Arrays.stream(getKubeconfigFilenames()) + .map(filename -> { + try { + return new KubeConfigFile(file, KubeConfigUtils.parseConfig(file)); + } catch (IOException e) { + return null; + } + }) + .filter(entry -> entry != null + && entry.getConfig() != null + && hasAuthInfo(username, entry.getConfig()) + ) + .findFirst() + .orElse(null); + } + + private boolean hasAuthInfo(String username, io.fabric8.kubernetes.api.model.Config kubeConfig) { + return kubeConfig.getUsers().stream() + .anyMatch(namedAuthInfo -> username.equals(namedAuthInfo.getUser().getUsername())); + } + + public static class KubeConfigFile { + private final File file; + private final io.fabric8.kubernetes.api.model.Config config; + + private KubeConfigFile(File file, io.fabric8.kubernetes.api.model.Config config) { + this.file = file; + this.config = config; + } + + public File getFile() { + return file; + } + + public io.fabric8.kubernetes.api.model.Config getConfig() { + return config; + } + } + @JsonIgnore public Readiness getReadiness() { return Readiness.getInstance(); diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtils.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtils.java index c2ae79374a8..d08fff123fe 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtils.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtils.java @@ -21,7 +21,6 @@ import io.fabric8.kubernetes.api.model.AuthProviderConfig; import io.fabric8.kubernetes.api.model.NamedAuthInfo; import io.fabric8.kubernetes.client.Config; -import io.fabric8.kubernetes.client.Config.KubeConfigFile; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.http.HttpClient; import io.fabric8.kubernetes.client.http.HttpRequest; @@ -202,7 +201,7 @@ private static void persistOAuthTokenToFile(Config currentConfig, String token, if (currentConfig.getFile() != null && currentConfig.getCurrentContext() != null) { try { final String userName = currentConfig.getCurrentContext().getContext().getUser(); - KubeConfigFile kubeConfigFile = currentConfig.getFile(userName); + Config.KubeConfigFile kubeConfigFile = currentConfig.getFile(userName); if (kubeConfigFile == null) { LOGGER.warn("oidc: failure while persisting new tokens into KUBECONFIG: file for user {} not found", userName); return; diff --git a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/ConfigTest.java b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/ConfigTest.java index ad069d389de..cb0106b1849 100644 --- a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/ConfigTest.java +++ b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/ConfigTest.java @@ -1230,4 +1230,29 @@ void givenEmptyKubeConfig_whenConfigCreated_thenShouldNotProduceNPE() throws URI System.clearProperty("kubeconfig"); } } + + @Test + void givenKubeConfigWithSeveralFiles_whenAutoconfigured_thenShouldUseCurrentContextSetInFirstFile() throws URISyntaxException { + try { + // Given + System.setProperty(Config.KUBERNETES_KUBECONFIG_FILES, + // provides contexts, clusters, users + new File(Objects.requireNonNull(getClass().getResource( + "/test-kubeconfig-onlycurrentcontext")).toURI()).getAbsolutePath() + File.pathSeparator + + // only has current-context + new File(Objects.requireNonNull(getClass().getResource( + "/test-kubeconfig")).toURI()).getAbsolutePath() + ); + + // When + Config config = Config.autoConfigure(null); // dont override current context + + // Then + assertThat(config.getCurrentContext()).isNotNull(); + assertThat(config.getCurrentContext().getName()).isEqualTo("production/172-28-128-4:8443/root"); + } finally { + System.clearProperty(Config.KUBERNETES_KUBECONFIG_FILES); + } + } + } diff --git a/kubernetes-client-api/src/test/resources/test-kubeconfig-onlycurrentcontext b/kubernetes-client-api/src/test/resources/test-kubeconfig-onlycurrentcontext new file mode 100644 index 00000000000..dc9a9504c8b --- /dev/null +++ b/kubernetes-client-api/src/test/resources/test-kubeconfig-onlycurrentcontext @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Config +clusters: null +contexts: null +current-context: production/172-28-128-4:8443/root +preferences: {} +users: null \ No newline at end of file