github.com/oam-dev/cluster-gateway@v1.9.0/pkg/util/exec/exec.go (about) 1 package exec 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "os" 8 "os/exec" 9 "sync" 10 "time" 11 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/runtime" 14 "k8s.io/apimachinery/pkg/runtime/schema" 15 "k8s.io/apimachinery/pkg/runtime/serializer" 16 17 "k8s.io/client-go/pkg/apis/clientauthentication" 18 "k8s.io/client-go/pkg/apis/clientauthentication/install" 19 clientauthenticationv1 "k8s.io/client-go/pkg/apis/clientauthentication/v1" 20 clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" 21 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 22 ) 23 24 var ( 25 scheme = runtime.NewScheme() 26 27 codecs = serializer.NewCodecFactory(scheme) 28 29 apiVersions = map[string]schema.GroupVersion{ 30 clientauthenticationv1beta1.SchemeGroupVersion.String(): clientauthenticationv1beta1.SchemeGroupVersion, 31 clientauthenticationv1.SchemeGroupVersion.String(): clientauthenticationv1.SchemeGroupVersion, 32 } 33 34 credentials sync.Map 35 ) 36 37 func init() { 38 install.Install(scheme) 39 } 40 41 func IssueClusterCredential(name string, ec *clientcmdapi.ExecConfig) (*clientauthentication.ExecCredential, error) { 42 if name == "" { 43 return nil, errors.New("cluster name not provided") 44 } 45 46 value, found := credentials.Load(name) 47 if found { 48 cred, ok := value.(*clientauthentication.ExecCredential) 49 if !ok { 50 return nil, errors.New("failed to convert item in cache to ExecCredential") 51 } 52 53 now := &metav1.Time{Time: time.Now().Add(time.Minute)} // expires a minute early 54 55 if cred.Status != nil && cred.Status.ExpirationTimestamp.Before(now) { 56 credentials.Delete(name) 57 return IssueClusterCredential(name, ec) // credential expired, calling function again 58 } 59 60 return cred, nil // credential on cache still valid 61 } 62 63 cred, err := issueClusterCredential(ec) 64 if err != nil { 65 return nil, err 66 } 67 68 if cred.Status != nil && !cred.Status.ExpirationTimestamp.IsZero() { 69 credentials.Store(name, cred) // storing credential in cache 70 } 71 72 return cred, nil 73 } 74 75 func issueClusterCredential(ec *clientcmdapi.ExecConfig) (*clientauthentication.ExecCredential, error) { 76 if ec == nil { 77 return nil, errors.New("exec config not provided") 78 } 79 80 if ec.Command == "" { 81 return nil, errors.New("missing \"command\" property on exec config object") 82 } 83 84 command, err := exec.LookPath(ec.Command) 85 if err != nil { 86 return nil, unwrapExecCommandError(ec.Command, err) 87 } 88 89 cmd := exec.Command(command, ec.Args...) 90 cmd.Env = os.Environ() 91 92 for _, env := range ec.Env { 93 cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", env.Name, env.Value)) 94 } 95 96 var stderr, stdout bytes.Buffer 97 cmd.Stderr = &stderr 98 cmd.Stdout = &stdout 99 100 if err := cmd.Run(); err != nil { 101 return nil, unwrapExecCommandError(command, err) 102 } 103 104 ecgv, err := schema.ParseGroupVersion(ec.APIVersion) 105 if err != nil { 106 return nil, fmt.Errorf("failed to parse exec config API version: %v", err) 107 } 108 109 cred := &clientauthentication.ExecCredential{ 110 TypeMeta: metav1.TypeMeta{ 111 APIVersion: ec.APIVersion, 112 Kind: "ExecCredential", 113 }, 114 Spec: clientauthentication.ExecCredentialSpec{}, 115 } 116 117 gv, ok := apiVersions[ec.APIVersion] 118 if !ok { 119 return nil, fmt.Errorf("exec plugin: invalid apiVersion %q", ec.APIVersion) 120 } 121 122 _, gvk, err := codecs.UniversalDecoder(gv).Decode(stdout.Bytes(), nil, cred) 123 if err != nil { 124 return nil, fmt.Errorf("decoding stdout: %v", err) 125 } 126 127 if gvk.Group != ecgv.Group || gvk.Version != ecgv.Version { 128 return nil, fmt.Errorf("exec plugin is configured to use API version %s, plugin returned version %s", ecgv, schema.GroupVersion{Group: gvk.Group, Version: gvk.Version}) 129 } 130 131 if cred.Status == nil { 132 return nil, fmt.Errorf("exec plugin didn't return a status field") 133 } 134 135 if cred.Status.Token == "" && cred.Status.ClientCertificateData == "" && cred.Status.ClientKeyData == "" { 136 return nil, fmt.Errorf("exec plugin didn't return a token or cert/key pair") 137 } 138 139 if (cred.Status.ClientCertificateData == "") != (cred.Status.ClientKeyData == "") { 140 return nil, fmt.Errorf("exec plugin returned only certificate or key, not both") 141 } 142 143 return cred, nil 144 } 145 146 func unwrapExecCommandError(path string, err error) error { 147 switch err.(type) { 148 case *exec.Error: // Binary does not exist (see exec.Error). 149 return fmt.Errorf("exec: executable %s not found", path) 150 151 case *exec.ExitError: // Binary execution failed (see exec.Cmd.Run()). 152 e := err.(*exec.ExitError) 153 return fmt.Errorf("exec: executable %s failed with exit code %d", path, e.ProcessState.ExitCode()) 154 155 default: 156 return fmt.Errorf("exec: %v", err) 157 } 158 }