From e293394235082df0b5f2cf037a43d13c6a1f3787 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Fri, 11 Dec 2020 16:06:46 -0500 Subject: [PATCH] Allow passing in kubeconfig contents during runtime (#275) e.g. for receptorctl, --param secret_kube_config="apiVersion: v1..." if the value for --param starts with @, it is interpreted as a file. In this case the file is read and the contents are passed to receptor e.g. --param secret_kube_config=@/home/sfoster/.kube/config Note: the @ indicating a filename works for any --param, not just secrete_kube_config --- pkg/workceptor/kubernetes.go | 90 ++++++--------------- receptorctl/receptorctl/socket_interface.py | 13 ++- 2 files changed, 35 insertions(+), 68 deletions(-) diff --git a/pkg/workceptor/kubernetes.go b/pkg/workceptor/kubernetes.go index 20789a131..abb765592 100644 --- a/pkg/workceptor/kubernetes.go +++ b/pkg/workceptor/kubernetes.go @@ -53,18 +53,14 @@ type kubeUnit struct { // kubeExtraData is the content of the ExtraData JSON field for a Kubernetes worker type kubeExtraData struct { - Image string - Command string - Params string - KubeHost string - KubeAPIPath string - KubeNamespace string - KubeUsername string - KubePassword string - KubeBearerToken string - KubeVerifyTLS bool - KubeTLSCAData string - PodName string + Image string + Command string + Params string + KubeNamespace string + KubeVerifyTLS bool + KubeTLSCAData string + KubeConfig string + PodName string } // ErrPodCompleted is returned when pod has already completed before we could attach @@ -512,7 +508,7 @@ func (kw *kubeUnit) connectUsingKubeconfig() error { if kw.kubeConfig != "" { clr.ExplicitPath = kw.kubeConfig } - ked := kw.Status().ExtraData.(*kubeExtraData) + ked := kw.UnredactedStatus().ExtraData.(*kubeExtraData) if ked.KubeNamespace == "" { c, err := clr.Load() if err != nil { @@ -522,7 +518,11 @@ func (kw *kubeUnit) connectUsingKubeconfig() error { sfd.ExtraData.(*kubeExtraData).KubeNamespace = c.Contexts[c.CurrentContext].Namespace }) } - kw.config, err = clientcmd.BuildConfigFromFlags("", clr.GetDefaultFilename()) + if ked.KubeConfig != "" { + kw.config, err = clientcmd.RESTConfigFromKubeConfig([]byte(ked.KubeConfig)) + } else { + kw.config, err = clientcmd.BuildConfigFromFlags("", clr.GetDefaultFilename()) + } if err != nil { return err } @@ -538,32 +538,12 @@ func (kw *kubeUnit) connectUsingIncluster() error { return nil } -func (kw *kubeUnit) connectUsingParams() error { - ked := kw.UnredactedStatus().ExtraData.(*kubeExtraData) - kw.config = &rest.Config{ - Host: ked.KubeHost, - APIPath: ked.KubeAPIPath, - Username: ked.KubeUsername, - Password: ked.KubePassword, - BearerToken: ked.KubeBearerToken, - TLSClientConfig: rest.TLSClientConfig{ - Insecure: !ked.KubeVerifyTLS, - }, - } - if ked.KubeTLSCAData != "" { - kw.config.TLSClientConfig.CAData = []byte(ked.KubeTLSCAData) - } - return nil -} - func (kw *kubeUnit) connectToKube() error { var err error if kw.authMethod == "kubeconfig" { err = kw.connectUsingKubeconfig() } else if kw.authMethod == "incluster" { err = kw.connectUsingIncluster() - } else if kw.authMethod == "params" { - err = kw.connectUsingParams() } else { return fmt.Errorf("unknown auth method %s", kw.authMethod) } @@ -608,13 +588,8 @@ func (kw *kubeUnit) SetFromParams(params map[string]string) error { {name: "kube_command", permission: kw.allowRuntimeCommand, setter: setString(&ked.Command)}, {name: "kube_image", permission: kw.allowRuntimeCommand, setter: setString(&ked.Image)}, {name: "kube_params", permission: kw.allowRuntimeParams, setter: setString(&userParams)}, - {name: "kube_config", permission: kw.allowRuntimeAuth, setter: setString(&kw.kubeConfig)}, {name: "kube_namespace", permission: kw.allowRuntimeAuth, setter: setString(&ked.KubeNamespace)}, - {name: "kube_host", permission: kw.allowRuntimeAuth, setter: setString(&ked.KubeHost)}, - {name: "kube_api_path", permission: kw.allowRuntimeAuth, setter: setString(&ked.KubeAPIPath)}, - {name: "kube_username", permission: kw.allowRuntimeAuth, setter: setString(&ked.KubeUsername)}, - {name: "secret_kube_password", permission: kw.allowRuntimeAuth, setter: setString(&ked.KubePassword)}, - {name: "secret_kube_bearer_token", permission: kw.allowRuntimeAuth, setter: setString(&ked.KubeBearerToken)}, + {name: "secret_kube_config", permission: kw.allowRuntimeAuth, setter: setString(&ked.KubeConfig)}, {name: "kube_verify_tls", permission: kw.allowRuntimeTLS, setter: setBool(&ked.KubeVerifyTLS)}, {name: "kube_tls_ca", permission: kw.allowRuntimeTLS, setter: setString(&ked.KubeTLSCAData)}, } @@ -640,8 +615,7 @@ func (kw *kubeUnit) Status() *StatusFileData { status := kw.UnredactedStatus() ed, ok := status.ExtraData.(*kubeExtraData) if ok { - ed.KubePassword = "" - ed.KubeBearerToken = "" + ed.KubeConfig = "" } return status } @@ -747,13 +721,8 @@ type WorkKubeCfg struct { Image string `description:"Container image to use for the worker pod"` Command string `description:"Command to run in the container (overrides entrypoint)"` Params string `description:"Command-line parameters to pass to the entrypoint"` - AuthMethod string `description:"One of: kubeconfig, incluster, params" default:"incluster"` + AuthMethod string `description:"One of: kubeconfig, incluster" default:"incluster"` KubeConfig string `description:"Kubeconfig filename (for authmethod=kubeconfig)"` - KubeHost string `description:"k8s API hostname (for authmethod=params)"` - KubeAPIPath string `description:"k8s API path (for authmethod=params)"` - KubeUsername string `description:"k8s API username (for authmethod=params)"` - KubePassword string `description:"k8s API password (for authmethod=params)"` - KubeBearerToken string `description:"k8s API bearer token (for authmethod=params)"` KubeVerifyTLS bool `description:"verify server TLS certificate/hostname" default:"true"` KubeTLSCAData string `description:"CA certificate PEM data to verify against"` AllowRuntimeAuth bool `description:"Allow passing API parameters at runtime" default:"false"` @@ -770,16 +739,11 @@ func (cfg WorkKubeCfg) newWorker(w *Workceptor, unitID string, workType string) BaseWorkUnit: BaseWorkUnit{ status: StatusFileData{ ExtraData: &kubeExtraData{ - Image: cfg.Image, - Command: cfg.Command, - KubeHost: cfg.KubeHost, - KubeAPIPath: cfg.KubeAPIPath, - KubeNamespace: cfg.Namespace, - KubeUsername: cfg.KubeUsername, - KubePassword: cfg.KubePassword, - KubeBearerToken: cfg.KubeBearerToken, - KubeVerifyTLS: cfg.KubeVerifyTLS, - KubeTLSCAData: cfg.KubeTLSCAData, + Image: cfg.Image, + Command: cfg.Command, + KubeNamespace: cfg.Namespace, + KubeVerifyTLS: cfg.KubeVerifyTLS, + KubeTLSCAData: cfg.KubeTLSCAData, }, }, }, @@ -801,7 +765,7 @@ func (cfg WorkKubeCfg) newWorker(w *Workceptor, unitID string, workType string) // Prepare inspects the configuration for validity func (cfg WorkKubeCfg) Prepare() error { lcAuth := strings.ToLower(cfg.AuthMethod) - if lcAuth != "kubeconfig" && lcAuth != "incluster" && lcAuth != "params" { + if lcAuth != "kubeconfig" && lcAuth != "incluster" { return fmt.Errorf("invalid AuthMethod: %s", cfg.AuthMethod) } if cfg.Namespace == "" && !(lcAuth == "kubeconfig" || cfg.AllowRuntimeAuth) { @@ -816,14 +780,6 @@ func (cfg WorkKubeCfg) Prepare() error { return fmt.Errorf("error accessing kubeconfig file: %s", err) } } - if lcAuth == "params" && !cfg.AllowRuntimeAuth { - if cfg.KubeHost == "" { - return fmt.Errorf("when AuthMethod=params, must provide KubeHost") - } - if (cfg.KubeUsername == "" || cfg.KubePassword == "") && cfg.KubeBearerToken == "" { - return fmt.Errorf("when AuthMethod=params, must provide either KubeBearerToken or KubeUsername and KubePassword") - } - } if cfg.KubeTLSCAData != "" { block, _ := pem.Decode([]byte(cfg.KubeTLSCAData)) if block == nil || block.Type != "BEGIN CERTIFICATE" { diff --git a/receptorctl/receptorctl/socket_interface.py b/receptorctl/receptorctl/socket_interface.py index b17723837..7545e1c04 100644 --- a/receptorctl/receptorctl/socket_interface.py +++ b/receptorctl/receptorctl/socket_interface.py @@ -161,7 +161,18 @@ def submit_work(self, worktype, payload, node=None, tlsclient=None, ttl=None, pa if params: for k,v in params.items(): if k not in commandMap: - commandMap[k] = v + if v[0] == '@' and v[:2] != '@@': + fname = v[1:] + if not os.path.exists(fname): + raise FileNotFoundError("{} does not exist".format(fname)) + try: + with open(fname, 'r') as f: + v_contents = f.read() + except: + raise OSError("could not read from file {}".format(fname)) + commandMap[k] = v_contents + else: + commandMap[k] = v else: raise RuntimeError(f"Duplicate or illegal parameter {k}") commandJson = json.dumps(commandMap)