// DefaultClientConfig creates a clientcmd.ClientConfig with the following hierarchy: // 1. Use the kubeconfig builder. The number of merges and overrides here gets a little crazy. Stay with me. // 1. Merge together the kubeconfig itself. This is done with the following hierarchy rules: // 1. CommandLineLocation - this parsed from the command line, so it must be late bound. If you specify this, // then no other kubeconfig files are merged. This file must exist. // 2. If $KUBECONFIG is set, then it is treated as a list of files that should be merged. // 3. HomeDirectoryLocation // Empty filenames are ignored. Files with non-deserializable content produced errors. // The first file to set a particular value or map key wins and the value or map key is never changed. // 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. // 2. Determine the context to use based on the first hit in this chain // 1. command line argument - again, parsed from the command line, so it must be late bound // 2. CurrentContext from the merged kubeconfig file // 3. Empty is allowed at this stage // 3. Determine the cluster info and auth info to use. At this point, we may or may not have a context. They // are built based on the first hit in this chain. (run it twice, once for auth, once for cluster) // 1. command line argument // 2. If context is present, then use the context value // 3. Empty is allowed // 4. Determine the actual cluster info to use. At this point, we may or may not have a cluster info. Build // each piece of the cluster info based on the chain: // 1. command line argument // 2. If cluster info is present and a value for the attribute is present, use it. // 3. If you don't have a server location, bail. // 5. Auth info is build using the same rules as cluster info, EXCEPT that you can only have one authentication // technique per auth info. The following conditions result in an error: // 1. If there are two conflicting techniques specified from the command line, fail. // 2. If the command line does not specify one, and the auth info has conflicting techniques, fail. // 3. If the command line specifies one and the auth info specifies another, honor the command line technique. // 2. Use default values and potentially prompt for auth information // // However, if it appears that we're running in a kubernetes cluster // container environment, then run with the auth info kubernetes mounted for // us. Specifically: // The env vars KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT are // set, and the file /var/run/secrets/kubernetes.io/serviceaccount/token // exists and is not a directory. func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig { loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() flags.StringVar(&loadingRules.ExplicitPath, "kubeconfig", "", "Path to the kubeconfig file to use for CLI requests.") overrides := &clientcmd.ConfigOverrides{} flagNames := clientcmd.RecommendedConfigOverrideFlags("") // short flagnames are disabled by default. These are here for compatibility with existing scripts flagNames.ClusterOverrideFlags.APIServer.ShortName = "s" clientcmd.BindOverrideFlags(overrides, flags, flagNames) clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin) return clientConfig }
func NewDefaultPathOptions() *PathOptions { ret := &PathOptions{ GlobalFile: clientcmd.RecommendedHomeFile, EnvVar: clientcmd.RecommendedConfigPathEnvVar, ExplicitFileFlag: clientcmd.RecommendedConfigPathFlag, GlobalFileSubpath: clientcmd.RecommendedHomeFileName, LoadingRules: clientcmd.NewDefaultClientConfigLoadingRules(), } ret.LoadingRules.DoNotResolvePaths = true return ret }
// NewEnviromentConfig sets up the initial config from environment variables func NewEnviromentConfig() (*Config, error) { config := NewDefaultConfig() home := os.Getenv("GIT_HOME") if len(home) == 0 { return nil, fmt.Errorf("GIT_HOME is required") } abs, err := filepath.Abs(home) if err != nil { return nil, fmt.Errorf("can't make %q absolute: %v", home, err) } if stat, err := os.Stat(abs); err != nil || !stat.IsDir() { return nil, fmt.Errorf("GIT_HOME must be an existing directory: %v", err) } config.Home = home if publicURL := os.Getenv("PUBLIC_URL"); len(publicURL) > 0 { valid, err := url.Parse(publicURL) if err != nil { return nil, fmt.Errorf("PUBLIC_URL must be a valid URL: %v", err) } config.URL = valid } gitpath := os.Getenv("GIT_PATH") if len(gitpath) == 0 { path, err := exec.LookPath("git") if err != nil { return nil, fmt.Errorf("could not find 'git' in PATH; specify GIT_PATH or set your PATH") } gitpath = path } config.GitBinary = gitpath config.AllowPush = os.Getenv("ALLOW_GIT_PUSH") != "no" config.AllowHooks = os.Getenv("ALLOW_GIT_HOOKS") != "no" config.AllowLazyCreate = os.Getenv("ALLOW_LAZY_CREATE") != "no" if hookpath := os.Getenv("HOOK_PATH"); len(hookpath) != 0 { path, err := filepath.Abs(hookpath) if err != nil { return nil, fmt.Errorf("HOOK_PATH was set but cannot be made absolute: %v", err) } if stat, err := os.Stat(path); err != nil || !stat.IsDir() { return nil, fmt.Errorf("HOOK_PATH must be an existing directory if set: %v", err) } config.HookDirectory = path } allowAnonymousGet := os.Getenv("ALLOW_ANON_GIT_PULL") == "yes" serverAuth := os.Getenv("REQUIRE_SERVER_AUTH") gitAuth := os.Getenv("REQUIRE_GIT_AUTH") if len(serverAuth) > 0 && len(gitAuth) > 0 { return nil, fmt.Errorf("only one of REQUIRE_SERVER_AUTH or REQUIRE_GIT_AUTH may be specified") } if len(serverAuth) > 0 { namespace := os.Getenv("AUTH_NAMESPACE") if len(namespace) == 0 { return nil, fmt.Errorf("when REQUIRE_SERVER_AUTH is set, AUTH_NAMESPACE must also be specified") } if serverAuth == "-" { serverAuth = "" } rules := clientcmd.NewDefaultClientConfigLoadingRules() rules.ExplicitPath = serverAuth kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, &clientcmd.ConfigOverrides{}) cfg, err := kubeconfig.ClientConfig() if err != nil { return nil, fmt.Errorf("could not create a client for REQUIRE_SERVER_AUTH: %v", err) } osc, err := client.New(cfg) if err != nil { return nil, fmt.Errorf("could not create a client for REQUIRE_SERVER_AUTH: %v", err) } config.AuthMessage = fmt.Sprintf("Authenticating against %s allow-push=%t anon-pull=%t", cfg.Host, config.AllowPush, allowAnonymousGet) config.AuthenticatorFn = auth.Authenticator(func(info auth.AuthInfo) (bool, error) { if !info.Push && allowAnonymousGet { return true, nil } req := &authapi.LocalSubjectAccessReview{ Action: authapi.AuthorizationAttributes{ Verb: "get", Resource: "pods", }, } if info.Push { if !config.AllowPush { return false, nil } req.Action.Verb = "create" } res, err := osc.ImpersonateLocalSubjectAccessReviews(namespace, info.Password).Create(req) if err != nil { return false, err } //log.Printf("debug: server response allowed=%t message=%s", res.Allowed, res.Reason) return res.Allowed, nil }) } if len(gitAuth) > 0 { parts := strings.Split(gitAuth, ":") if len(parts) != 2 { return nil, fmt.Errorf("REQUIRE_GIT_AUTH must be a username and password separated by a ':'") } config.AuthMessage = fmt.Sprintf("Authenticating against username/password allow-push=%t", config.AllowPush) username, password := parts[0], parts[1] config.AuthenticatorFn = auth.Authenticator(func(info auth.AuthInfo) (bool, error) { if info.Push { if !config.AllowPush { return false, nil } if allowAnonymousGet { return true, nil } } if info.Username != username || info.Password != password { return false, nil } return true, nil }) } if value := os.Getenv("GIT_LISTEN"); len(value) > 0 { config.Listen = value } config.CleanBeforeClone = os.Getenv("GIT_FORCE_CLEAN") == "yes" clones := make(map[string]Clone) for _, env := range os.Environ() { if !strings.HasPrefix(env, initialClonePrefix) { continue } parts := strings.SplitN(env, "=", 2) if len(parts) != 2 { continue } key, value := parts[0], parts[1] part := key[len(initialClonePrefix):] if len(part) == 0 { continue } if len(value) == 0 { return nil, fmt.Errorf("%s must not have an empty value", key) } defaultName := strings.Replace(strings.ToLower(part), "_", "-", -1) values := strings.Split(value, ";") var uri, name string switch len(values) { case 1: uri, name = values[0], "" case 2: uri, name = values[0], values[1] if len(name) == 0 { return nil, fmt.Errorf("%s name may not be empty", key) } default: return nil, fmt.Errorf("%s may only have two segments (<url> or <url>;<name>)", key) } url, err := git.ParseRepository(uri) if err != nil { return nil, fmt.Errorf("%s is not a valid repository URI: %v", key, err) } switch url.Scheme { case "http", "https", "git", "ssh": default: return nil, fmt.Errorf("%s %q must be a http, https, git, or ssh URL", key, uri) } if len(name) == 0 { if n, ok := git.NameFromRepositoryURL(url); ok { name = n + ".git" } } if len(name) == 0 { name = defaultName + ".git" } if invalidCloneNameChars.MatchString(name) { return nil, fmt.Errorf("%s name %q must be only letters, numbers, dashes, or underscores", key, name) } if _, ok := reservedNames[name]; ok { return nil, fmt.Errorf("%s name %q is reserved (%v)", key, name, reservedNames) } clones[name] = Clone{ URL: *url, } } config.InitialClones = clones return config, nil }