diff --git a/pkg/client/clientcmd/loader.go b/pkg/client/clientcmd/loader.go index 6fd5c4fd9d..7d2224ec42 100644 --- a/pkg/client/clientcmd/loader.go +++ b/pkg/client/clientcmd/loader.go @@ -19,6 +19,7 @@ package clientcmd import ( "io/ioutil" "os" + "path/filepath" "github.com/ghodss/yaml" "github.com/imdario/mergo" @@ -59,13 +60,22 @@ func NewClientConfigLoadingRules() *ClientConfigLoadingRules { // This means that the first file to set CurrentContext will have its context preserved. It also means // that if two files specify a "red-user", only values from the first file's red-user are used. Even // non-conflicting entries from the second file's "red-user" are discarded. +// Relative paths inside of the .kubeconfig files are resolved against the .kubeconfig file's parent folder +// and only absolute file paths are returned. func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) { config := clientcmdapi.NewConfig() mergeConfigWithFile(config, rules.CommandLinePath) + resolveLocalPaths(rules.CommandLinePath, config) + mergeConfigWithFile(config, rules.EnvVarPath) + resolveLocalPaths(rules.EnvVarPath, config) + mergeConfigWithFile(config, rules.CurrentDirectoryPath) + resolveLocalPaths(rules.CurrentDirectoryPath, config) + mergeConfigWithFile(config, rules.HomeDirectoryPath) + resolveLocalPaths(rules.HomeDirectoryPath, config) return config, nil } @@ -86,6 +96,50 @@ func mergeConfigWithFile(startingConfig *clientcmdapi.Config, filename string) e return nil } +// resolveLocalPaths resolves all relative paths in the config object with respect to the parent directory of the filename +// this cannot be done directly inside of LoadFromFile because doing so there would make it impossible to load a file without +// modification of its contents. +func resolveLocalPaths(filename string, config *clientcmdapi.Config) error { + if len(filename) == 0 { + return nil + } + + configDir, err := filepath.Abs(filepath.Dir(filename)) + if err != nil { + return err + } + + resolvedClusters := make(map[string]clientcmdapi.Cluster) + for key, cluster := range config.Clusters { + cluster.CertificateAuthority = resolveLocalPath(configDir, cluster.CertificateAuthority) + resolvedClusters[key] = cluster + } + config.Clusters = resolvedClusters + + resolvedAuthInfos := make(map[string]clientcmdapi.AuthInfo) + for key, authInfo := range config.AuthInfos { + authInfo.AuthPath = resolveLocalPath(configDir, authInfo.AuthPath) + authInfo.ClientCertificate = resolveLocalPath(configDir, authInfo.ClientCertificate) + authInfo.ClientKey = resolveLocalPath(configDir, authInfo.ClientKey) + resolvedAuthInfos[key] = authInfo + } + config.AuthInfos = resolvedAuthInfos + + return nil +} + +// resolveLocalPath makes the path absolute with respect to the startingDir +func resolveLocalPath(startingDir, path string) string { + if len(path) == 0 { + return path + } + if filepath.IsAbs(path) { + return path + } + + return filepath.Join(startingDir, path) +} + // LoadFromFile takes a filename and deserializes the contents into Config object func LoadFromFile(filename string) (*clientcmdapi.Config, error) { config := &clientcmdapi.Config{} diff --git a/pkg/client/clientcmd/loader_test.go b/pkg/client/clientcmd/loader_test.go index dc3eb86bd6..018d4df809 100644 --- a/pkg/client/clientcmd/loader_test.go +++ b/pkg/client/clientcmd/loader_test.go @@ -20,6 +20,9 @@ import ( "fmt" "io/ioutil" "os" + "path" + "path/filepath" + "testing" "github.com/ghodss/yaml" @@ -71,6 +74,107 @@ var ( } ) +func TestResolveRelativePaths(t *testing.T) { + pathResolutionConfig1 := clientcmdapi.Config{ + AuthInfos: map[string]clientcmdapi.AuthInfo{ + "relative-user-1": {ClientCertificate: "relative/client/cert", ClientKey: "../relative/client/key", AuthPath: "../../relative/auth/path"}, + "absolute-user-1": {ClientCertificate: "/absolute/client/cert", ClientKey: "/absolute/client/key", AuthPath: "/absolute/auth/path"}, + }, + Clusters: map[string]clientcmdapi.Cluster{ + "relative-server-1": {CertificateAuthority: "../relative/ca"}, + "absolute-server-1": {CertificateAuthority: "/absolute/ca"}, + }, + } + pathResolutionConfig2 := clientcmdapi.Config{ + AuthInfos: map[string]clientcmdapi.AuthInfo{ + "relative-user-2": {ClientCertificate: "relative/client/cert2", ClientKey: "../relative/client/key2", AuthPath: "../../relative/auth/path2"}, + "absolute-user-2": {ClientCertificate: "/absolute/client/cert2", ClientKey: "/absolute/client/key2", AuthPath: "/absolute/auth/path2"}, + }, + Clusters: map[string]clientcmdapi.Cluster{ + "relative-server-2": {CertificateAuthority: "../relative/ca2"}, + "absolute-server-2": {CertificateAuthority: "/absolute/ca2"}, + }, + } + + configDir1, _ := ioutil.TempDir("", "") + configFile1 := path.Join(configDir1, ".kubeconfig") + configDir1, _ = filepath.Abs(configDir1) + defer os.Remove(configFile1) + configDir2, _ := ioutil.TempDir("", "") + configDir2, _ = ioutil.TempDir(configDir2, "") + configFile2 := path.Join(configDir2, ".kubeconfig") + configDir2, _ = filepath.Abs(configDir2) + defer os.Remove(configFile2) + + WriteToFile(pathResolutionConfig1, configFile1) + WriteToFile(pathResolutionConfig2, configFile2) + + loadingRules := ClientConfigLoadingRules{ + CommandLinePath: configFile1, + EnvVarPath: configFile2, + } + + mergedConfig, err := loadingRules.Load() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + foundClusterCount := 0 + for key, cluster := range mergedConfig.Clusters { + if key == "relative-server-1" { + foundClusterCount++ + matchStringArg(path.Join(configDir1, pathResolutionConfig1.Clusters["relative-server-1"].CertificateAuthority), cluster.CertificateAuthority, t) + } + if key == "relative-server-2" { + foundClusterCount++ + matchStringArg(path.Join(configDir2, pathResolutionConfig2.Clusters["relative-server-2"].CertificateAuthority), cluster.CertificateAuthority, t) + } + if key == "absolute-server-1" { + foundClusterCount++ + matchStringArg(pathResolutionConfig1.Clusters["absolute-server-1"].CertificateAuthority, cluster.CertificateAuthority, t) + } + if key == "absolute-server-2" { + foundClusterCount++ + matchStringArg(pathResolutionConfig2.Clusters["absolute-server-2"].CertificateAuthority, cluster.CertificateAuthority, t) + } + } + if foundClusterCount != 4 { + t.Errorf("Expected 4 clusters, found %v: %v", foundClusterCount, mergedConfig.Clusters) + } + + foundAuthInfoCount := 0 + for key, authInfo := range mergedConfig.AuthInfos { + if key == "relative-user-1" { + foundAuthInfoCount++ + matchStringArg(path.Join(configDir1, pathResolutionConfig1.AuthInfos["relative-user-1"].ClientCertificate), authInfo.ClientCertificate, t) + matchStringArg(path.Join(configDir1, pathResolutionConfig1.AuthInfos["relative-user-1"].ClientKey), authInfo.ClientKey, t) + matchStringArg(path.Join(configDir1, pathResolutionConfig1.AuthInfos["relative-user-1"].AuthPath), authInfo.AuthPath, t) + } + if key == "relative-user-2" { + foundAuthInfoCount++ + matchStringArg(path.Join(configDir2, pathResolutionConfig2.AuthInfos["relative-user-2"].ClientCertificate), authInfo.ClientCertificate, t) + matchStringArg(path.Join(configDir2, pathResolutionConfig2.AuthInfos["relative-user-2"].ClientKey), authInfo.ClientKey, t) + matchStringArg(path.Join(configDir2, pathResolutionConfig2.AuthInfos["relative-user-2"].AuthPath), authInfo.AuthPath, t) + } + if key == "absolute-user-1" { + foundAuthInfoCount++ + matchStringArg(pathResolutionConfig1.AuthInfos["absolute-user-1"].ClientCertificate, authInfo.ClientCertificate, t) + matchStringArg(pathResolutionConfig1.AuthInfos["absolute-user-1"].ClientKey, authInfo.ClientKey, t) + matchStringArg(pathResolutionConfig1.AuthInfos["absolute-user-1"].AuthPath, authInfo.AuthPath, t) + } + if key == "absolute-user-2" { + foundAuthInfoCount++ + matchStringArg(pathResolutionConfig2.AuthInfos["absolute-user-2"].ClientCertificate, authInfo.ClientCertificate, t) + matchStringArg(pathResolutionConfig2.AuthInfos["absolute-user-2"].ClientKey, authInfo.ClientKey, t) + matchStringArg(pathResolutionConfig2.AuthInfos["absolute-user-2"].AuthPath, authInfo.AuthPath, t) + } + } + if foundAuthInfoCount != 4 { + t.Errorf("Expected 4 users, found %v: %v", foundAuthInfoCount, mergedConfig.AuthInfos) + } + +} + func ExampleMergingSomeWithConflict() { commandLineFile, _ := ioutil.TempFile("", "") defer os.Remove(commandLineFile.Name())