Skip to content

Commit

Permalink
feat(*): add flags/env for kube api tls overrides
Browse files Browse the repository at this point in the history
Add a new flags and associated environment variables to override
 the TLS Settings used when constructing the Kube Client.

- `--kube-insecure-skip-tls-verify | HELM_KUBEINSECURE_SKIP_TLS_VERIFY`: if true, the kube api server's certificate will not be checked for validity. This will make your HTTPS connections insecure
- `--kube-tls-server-name | HELM_KUBETLS_SERVER_NAME`: server name to use for kube api server certificate validation. If it is not provided, the hostname used to contact the server is used

Signed-off-by: Justen Walker <justen.walker+github@gmail.com>
  • Loading branch information
justenwalker committed May 20, 2022
1 parent 823d929 commit 687852e
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 55 deletions.
2 changes: 1 addition & 1 deletion cmd/helm/load_plugins.go
Expand Up @@ -154,7 +154,7 @@ func callPluginExecutable(pluginName string, main string, argv []string, out io.
func manuallyProcessArgs(args []string) ([]string, []string) {
known := []string{}
unknown := []string{}
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config"}
kvargs := []string{"--kube-context", "--namespace", "-n", "--kubeconfig", "--kube-apiserver", "--kube-token", "--kube-as-user", "--kube-as-group", "--kube-ca-file", "--registry-config", "--repository-cache", "--repository-config", "--insecure-skip-tls-verify", "--tls-server-name"}
knownArg := func(a string) bool {
for _, pre := range kvargs {
if strings.HasPrefix(a, pre+"=") {
Expand Down
2 changes: 2 additions & 0 deletions cmd/helm/root.go
Expand Up @@ -67,6 +67,8 @@ Environment variables:
| $HELM_KUBEASUSER | set the Username to impersonate for the operation. |
| $HELM_KUBECONTEXT | set the name of the kubeconfig context. |
| $HELM_KUBETOKEN | set the Bearer KubeToken used for authentication. |
| $HELM_KUBEINSECURE_SKIP_TLS_VERIFY | indicate if the Kubernetes API server's certificate validation should be skipped (insecure) |
| $HELM_KUBETLS_SERVER_NAME | set the server name used to validate the Kubernetes API server certificate |
| $HELM_BURST_LIMIT | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable)|
Helm stores cache, configuration, and data based on the following configuration order:
Expand Down
2 changes: 2 additions & 0 deletions cmd/helm/testdata/output/env-comp.txt
Expand Up @@ -9,6 +9,8 @@ HELM_KUBEASGROUPS
HELM_KUBEASUSER
HELM_KUBECAFILE
HELM_KUBECONTEXT
HELM_KUBEINSECURE_SKIP_TLS_VERIFY
HELM_KUBETLS_SERVER_NAME
HELM_KUBETOKEN
HELM_MAX_HISTORY
HELM_NAMESPACE
Expand Down
64 changes: 45 additions & 19 deletions pkg/cli/environment.go
Expand Up @@ -60,6 +60,12 @@ type EnvSettings struct {
KubeAPIServer string
// Custom certificate authority file.
KubeCaFile string
// KubeInsecureSkipTLSVerify indicates if server's certificate will not be checked for validity.
// This makes the HTTPS connections insecure
KubeInsecureSkipTLSVerify bool
// KubeTLSServerName overrides the name to use for server certificate validation.
// If it is not provided, the hostname used to contact the server is used
KubeTLSServerName string
// Debug indicates whether or not Helm is running in Debug mode.
Debug bool
// RegistryConfig is the path to the registry config file.
Expand All @@ -78,19 +84,21 @@ type EnvSettings struct {

func New() *EnvSettings {
env := &EnvSettings{
namespace: os.Getenv("HELM_NAMESPACE"),
MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory),
KubeContext: os.Getenv("HELM_KUBECONTEXT"),
KubeToken: os.Getenv("HELM_KUBETOKEN"),
KubeAsUser: os.Getenv("HELM_KUBEASUSER"),
KubeAsGroups: envCSV("HELM_KUBEASGROUPS"),
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
KubeCaFile: os.Getenv("HELM_KUBECAFILE"),
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")),
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")),
RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")),
BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit),
namespace: os.Getenv("HELM_NAMESPACE"),
MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory),
KubeContext: os.Getenv("HELM_KUBECONTEXT"),
KubeToken: os.Getenv("HELM_KUBETOKEN"),
KubeAsUser: os.Getenv("HELM_KUBEASUSER"),
KubeAsGroups: envCSV("HELM_KUBEASGROUPS"),
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
KubeCaFile: os.Getenv("HELM_KUBECAFILE"),
KubeTLSServerName: os.Getenv("HELM_KUBETLS_SERVER_NAME"),
KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false),
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")),
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")),
RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")),
BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit),
}
env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG"))

Expand All @@ -103,6 +111,8 @@ func New() *EnvSettings {
CAFile: &env.KubeCaFile,
KubeConfig: &env.KubeConfig,
Impersonate: &env.KubeAsUser,
Insecure: &env.KubeInsecureSkipTLSVerify,
TLSServerName: &env.KubeTLSServerName,
ImpersonateGroup: &env.KubeAsGroups,
WrapConfigFn: func(config *rest.Config) *rest.Config {
config.Burst = env.BurstLimit
Expand All @@ -122,6 +132,8 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
fs.StringArrayVar(&s.KubeAsGroups, "kube-as-group", s.KubeAsGroups, "group to impersonate for the operation, this flag can be repeated to specify multiple groups.")
fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server")
fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection")
fs.StringVar(&s.KubeTLSServerName, "kube-tls-server-name", s.KubeTLSServerName, "server name to use for Kubernetes API server certificate validation. If it is not provided, the hostname used to contact the server is used")
fs.BoolVar(&s.KubeInsecureSkipTLSVerify, "kube-insecure-skip-tls-verify", s.KubeInsecureSkipTLSVerify, "if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output")
fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file")
fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs")
Expand All @@ -136,6 +148,18 @@ func envOr(name, def string) string {
return def
}

func envBoolOr(name string, def bool) bool {
if name == "" {
return def
}
envVal := envOr(name, strconv.FormatBool(def))
ret, err := strconv.ParseBool(envVal)
if err != nil {
return def
}
return ret
}

func envIntOr(name string, def int) int {
if name == "" {
return def
Expand Down Expand Up @@ -172,12 +196,14 @@ func (s *EnvSettings) EnvVars() map[string]string {
"HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit),

// broken, these are populated from helm flags and not kubeconfig.
"HELM_KUBECONTEXT": s.KubeContext,
"HELM_KUBETOKEN": s.KubeToken,
"HELM_KUBEASUSER": s.KubeAsUser,
"HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","),
"HELM_KUBEAPISERVER": s.KubeAPIServer,
"HELM_KUBECAFILE": s.KubeCaFile,
"HELM_KUBECONTEXT": s.KubeContext,
"HELM_KUBETOKEN": s.KubeToken,
"HELM_KUBEASUSER": s.KubeAsUser,
"HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","),
"HELM_KUBEAPISERVER": s.KubeAPIServer,
"HELM_KUBECAFILE": s.KubeCaFile,
"HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify),
"HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName,
}
if s.KubeConfig != "" {
envvars["KUBECONFIG"] = s.KubeConfig
Expand Down
161 changes: 126 additions & 35 deletions pkg/cli/environment_test.go
Expand Up @@ -48,13 +48,15 @@ func TestEnvSettings(t *testing.T) {
envvars map[string]string

// expected values
ns, kcontext string
debug bool
maxhistory int
kubeAsUser string
kubeAsGroups []string
kubeCaFile string
burstLimit int
ns, kcontext string
debug bool
maxhistory int
kubeAsUser string
kubeAsGroups []string
kubeCaFile string
kubeInsecure bool
kubeTLSServer string
burstLimit int
}{
{
name: "defaults",
Expand All @@ -63,38 +65,44 @@ func TestEnvSettings(t *testing.T) {
burstLimit: defaultBurstLimit,
},
{
name: "with flags set",
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100",
ns: "myns",
debug: true,
maxhistory: defaultMaxHistory,
burstLimit: 100,
kubeAsUser: "poro",
kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
kubeCaFile: "/tmp/ca.crt",
name: "with flags set",
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org",
ns: "myns",
debug: true,
maxhistory: defaultMaxHistory,
burstLimit: 100,
kubeAsUser: "poro",
kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
kubeCaFile: "/tmp/ca.crt",
kubeTLSServer: "example.org",
kubeInsecure: true,
},
{
name: "with envvars set",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150"},
ns: "yourns",
maxhistory: 5,
burstLimit: 150,
debug: true,
kubeAsUser: "pikachu",
kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"},
kubeCaFile: "/tmp/ca.crt",
name: "with envvars set",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org"},
ns: "yourns",
maxhistory: 5,
burstLimit: 150,
debug: true,
kubeAsUser: "pikachu",
kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"},
kubeCaFile: "/tmp/ca.crt",
kubeTLSServer: "example.org",
kubeInsecure: true,
},
{
name: "with flags and envvars set",
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200"},
ns: "myns",
debug: true,
maxhistory: 5,
burstLimit: 175,
kubeAsUser: "poro",
kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
kubeCaFile: "/my/ca.crt",
name: "with flags and envvars set",
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org"},
ns: "myns",
debug: true,
maxhistory: 5,
burstLimit: 175,
kubeAsUser: "poro",
kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
kubeCaFile: "/my/ca.crt",
kubeTLSServer: "example.org",
kubeInsecure: true,
},
}

Expand Down Expand Up @@ -136,6 +144,89 @@ func TestEnvSettings(t *testing.T) {
if tt.burstLimit != settings.BurstLimit {
t.Errorf("expected BurstLimit %d, got %d", tt.burstLimit, settings.BurstLimit)
}
if tt.kubeInsecure != settings.KubeInsecureSkipTLSVerify {
t.Errorf("expected kubeInsecure %t, got %t", tt.kubeInsecure, settings.KubeInsecureSkipTLSVerify)
}
if tt.kubeTLSServer != settings.KubeTLSServerName {
t.Errorf("expected kubeTLSServer %q, got %q", tt.kubeTLSServer, settings.KubeTLSServerName)
}
})
}
}

func TestEnvOrBool(t *testing.T) {
const envName = "TEST_ENV_OR_BOOL"
tests := []struct {
name string
env string
val string
def bool
expected bool
}{
{
name: "unset with default false",
def: false,
expected: false,
},
{
name: "unset with default true",
def: true,
expected: true,
},
{
name: "blank env with default false",
env: envName,
def: false,
expected: false,
},
{
name: "blank env with default true",
env: envName,
def: true,
expected: true,
},
{
name: "env true with default false",
env: envName,
val: "true",
def: false,
expected: true,
},
{
name: "env false with default true",
env: envName,
val: "false",
def: true,
expected: false,
},
{
name: "env fails parsing with default true",
env: envName,
val: "NOT_A_BOOL",
def: true,
expected: true,
},
{
name: "env fails parsing with default false",
env: envName,
val: "NOT_A_BOOL",
def: false,
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.env != "" {
t.Cleanup(func() {
os.Unsetenv(tt.env)
})
os.Setenv(tt.env, tt.val)
}
actual := envBoolOr(tt.env, tt.def)
if actual != tt.expected {
t.Errorf("expected result %t, got %t", tt.expected, actual)
}
})
}
}
Expand Down

0 comments on commit 687852e

Please sign in to comment.