github.com/castai/kvisor@v1.7.1-0.20240516114728-b3572a2607b5/cmd/linter/kubebench/kubernetes_version.go (about)

     1  package kubebench
     2  
     3  import (
     4  	"crypto/tls"
     5  	"encoding/json"
     6  	"encoding/pem"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"os"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/golang/glog"
    15  )
    16  
    17  type KubeVersion struct {
    18  	Major       string
    19  	Minor       string
    20  	baseVersion string
    21  	GitVersion  string
    22  }
    23  
    24  func (k *KubeVersion) BaseVersion() string {
    25  	if k.baseVersion != "" {
    26  		return k.baseVersion
    27  	}
    28  	// Some provides return the minor version like "15+"
    29  	minor := strings.Replace(k.Minor, "+", "", -1)
    30  	ver := fmt.Sprintf("%s.%s", k.Major, minor)
    31  	k.baseVersion = ver
    32  	return ver
    33  }
    34  
    35  func getKubeVersionFromRESTAPI() (*KubeVersion, error) {
    36  	glog.V(2).Info("Try to get version from Rest API")
    37  	k8sVersionURL := getKubernetesURL()
    38  	serviceaccount := "/var/run/secrets/kubernetes.io/serviceaccount"
    39  	cacertfile := fmt.Sprintf("%s/ca.crt", serviceaccount)
    40  	tokenfile := fmt.Sprintf("%s/token", serviceaccount)
    41  
    42  	tlsCert, err := loadCertificate(cacertfile)
    43  	if err != nil {
    44  		glog.V(2).Infof("Failed loading certificate Error: %s", err)
    45  		return nil, err
    46  	}
    47  
    48  	tb, err := ioutil.ReadFile(tokenfile)
    49  	if err != nil {
    50  		glog.V(2).Infof("Failed reading token file Error: %s", err)
    51  		return nil, err
    52  	}
    53  	token := strings.TrimSpace(string(tb))
    54  
    55  	data, err := getWebDataWithRetry(k8sVersionURL, token, tlsCert)
    56  	if err != nil {
    57  		glog.V(2).Infof("Failed to get data Error: %s", err)
    58  		return nil, err
    59  	}
    60  
    61  	k8sVersion, err := extractVersion(data)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	return k8sVersion, nil
    66  }
    67  
    68  // The idea of this function is so if Kubernetes DNS is not completely seetup and the
    69  // Container where kube-bench is running needs time for DNS configure.
    70  // Basically try 10 times, waiting 1 second until either it is successful or it fails.
    71  func getWebDataWithRetry(k8sVersionURL, token string, cacert *tls.Certificate) (data []byte, err error) {
    72  	tries := 0
    73  	// We retry a few times in case the DNS service has not had time to come up
    74  	for tries < 10 {
    75  		data, err = getWebData(k8sVersionURL, token, cacert)
    76  		if err == nil {
    77  			return
    78  		}
    79  		tries++
    80  		time.Sleep(1 * time.Second)
    81  	}
    82  
    83  	return
    84  }
    85  
    86  type VersionResponse struct {
    87  	Major        string
    88  	Minor        string
    89  	GitVersion   string
    90  	GitCommit    string
    91  	GitTreeState string
    92  	BuildDate    string
    93  	GoVersion    string
    94  	Compiler     string
    95  	Platform     string
    96  }
    97  
    98  func extractVersion(data []byte) (*KubeVersion, error) {
    99  	vrObj := &VersionResponse{}
   100  	glog.V(2).Info(fmt.Sprintf("vd: %s\n", string(data)))
   101  	err := json.Unmarshal(data, vrObj)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	glog.V(2).Info(fmt.Sprintf("vrObj: %#v\n", vrObj))
   106  
   107  	return &KubeVersion{
   108  		Major:      vrObj.Major,
   109  		Minor:      vrObj.Minor,
   110  		GitVersion: vrObj.GitVersion,
   111  	}, nil
   112  }
   113  
   114  func getWebData(srvURL, token string, cacert *tls.Certificate) ([]byte, error) {
   115  	glog.V(2).Info(fmt.Sprintf("getWebData srvURL: %s\n", srvURL))
   116  
   117  	tlsConf := &tls.Config{
   118  		Certificates:       []tls.Certificate{*cacert},
   119  		InsecureSkipVerify: true,
   120  	}
   121  	tr := &http.Transport{
   122  		TLSClientConfig: tlsConf,
   123  	}
   124  	client := &http.Client{Transport: tr}
   125  	req, err := http.NewRequest(http.MethodGet, srvURL, nil)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	authToken := fmt.Sprintf("Bearer %s", token)
   131  	req.Header.Set("Authorization", authToken)
   132  
   133  	resp, err := client.Do(req)
   134  	if err != nil {
   135  		glog.V(2).Info(fmt.Sprintf("HTTP ERROR: %v\n", err))
   136  		return nil, err
   137  	}
   138  	defer resp.Body.Close()
   139  
   140  	if resp.StatusCode != http.StatusOK {
   141  		glog.V(2).Info(fmt.Sprintf("URL:[%s], StatusCode:[%d] \n Headers: %#v\n", srvURL, resp.StatusCode, resp.Header))
   142  		err = fmt.Errorf("URL:[%s], StatusCode:[%d]", srvURL, resp.StatusCode)
   143  		return nil, err
   144  	}
   145  
   146  	return ioutil.ReadAll(resp.Body)
   147  }
   148  
   149  func loadCertificate(certFile string) (*tls.Certificate, error) {
   150  	cacert, err := ioutil.ReadFile(certFile)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	var tlsCert tls.Certificate
   156  	block, _ := pem.Decode(cacert)
   157  	if block == nil {
   158  		return nil, fmt.Errorf("unable to Decode certificate")
   159  	}
   160  
   161  	glog.V(2).Info("Loading CA certificate")
   162  	tlsCert.Certificate = append(tlsCert.Certificate, block.Bytes)
   163  	return &tlsCert, nil
   164  }
   165  
   166  func getKubernetesURL() string {
   167  	k8sVersionURL := "https://kubernetes.default.svc/version"
   168  
   169  	// The following provides flexibility to use
   170  	// K8S provided variables is situations where
   171  	// hostNetwork: true
   172  	if !isEmpty(os.Getenv("KUBE_BENCH_K8S_ENV")) {
   173  		k8sHost := os.Getenv("KUBERNETES_SERVICE_HOST")
   174  		k8sPort := os.Getenv("KUBERNETES_SERVICE_PORT_HTTPS")
   175  		if !isEmpty(k8sHost) && !isEmpty(k8sPort) {
   176  			return fmt.Sprintf("https://%s:%s/version", k8sHost, k8sPort)
   177  		}
   178  
   179  		glog.V(2).Info("KUBE_BENCH_K8S_ENV is set, but environment variables KUBERNETES_SERVICE_HOST or KUBERNETES_SERVICE_PORT_HTTPS are not set")
   180  	}
   181  
   182  	return k8sVersionURL
   183  }