From f75499d827bbdc40b0e4b53149e639332ca81b11 Mon Sep 17 00:00:00 2001 From: pb Date: Fri, 28 Feb 2025 14:07:45 +0100 Subject: [PATCH] Added new route to retrieve the host's kubeconfig with the execution's SA token --- controllers/admiralty.go | 125 ++++++++++++++++++++++++++++++++++- docs/admiralty_setup.puml | 4 +- infrastructure/kubernetes.go | 3 + models/kubeconfig.go | 56 ++++++++++++++++ 4 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 models/kubeconfig.go diff --git a/controllers/admiralty.go b/controllers/admiralty.go index 7a2c61d..6701fea 100644 --- a/controllers/admiralty.go +++ b/controllers/admiralty.go @@ -1,9 +1,12 @@ package controllers import ( + "encoding/base64" "encoding/json" "fmt" + "oc-datacenter/conf" "oc-datacenter/infrastructure" + "oc-datacenter/models" "slices" "time" @@ -20,7 +23,7 @@ type KubeInfo struct { KubeKey *string } -type Kubeconfig struct { +type RemoteKubeconfig struct { Data *string } @@ -30,6 +33,35 @@ type KubeUser struct { Token string } } + +type KubeconfigToken struct { + ApiVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Preferences string `yaml:"preferences"` + CurrentContext string `yaml:"current-context"` + Clusters []struct{ + Cluster struct{ + CA string `yaml:"certificate-authority-data"` + Server string `yaml:"server"` + + } `yaml:"cluster"` + Name string `yaml:"name"` + } `yaml:"clusters"` + Contexts []struct{ + Context struct{ + Cluster string `yaml:"cluster"` + User string `yaml:"user"` + } `yaml:"context"` + Name string `yaml:"name"` + } `yaml:"contexts"` + Users []struct{ + Name string `yaml:"name"` + User struct { + Token string `yaml:"token"` + } `yaml:"user"` + } `yaml:"users"` +} + // Operations about the admiralty objects of the datacenter type AdmiraltyController struct { beego.Controller @@ -250,7 +282,7 @@ func(c *AdmiraltyController) GetKubeSecret() { // @Success 200 // @router /secret/:dc_id/:execution [post] func (c *AdmiraltyController) CreateKubeSecret() { - var kubeconfig Kubeconfig + var kubeconfig RemoteKubeconfig var respData map[string]interface{} data := c.Ctx.Input.CopyBody(100000) @@ -388,4 +420,93 @@ func (c *AdmiraltyController) GetNodeReady(){ c.Data["json"] = map[string]bool{"ok": true} c.ServeJSON() +} + +// @name Get Admiralty Kubeconfig +// @description Retrieve a kubeconfig from the host with the token to authenticate as the SA from the namespace identified with execution id +// @Param dc_id path string true "which dc to contact" +// @Param execution path string true "execution id of the workflow" +// @Success 200 +// @router /kubeconfig/:dc_id/:execution [get] +func (c *AdmiraltyController) GetAdmiraltyKubeconfig() { + dc_id := c.Ctx.Input.Param(":dc_id") + execution := c.Ctx.Input.Param(":execution") + _ = dc_id + + serv, err := infrastructure.NewService() + if err != nil { + // change code to 500 + c.Ctx.Output.SetStatus(500) + c.Data["json"] = map[string]string{"error": err.Error()} + c.ServeJSON() + return + } + + generatedToken, err := serv.GenerateToken(c.Ctx.Request.Context(),execution,3600) + if err != nil { + fmt.Println("Couldn't generate a token for ns-", execution) + fmt.Println(err) + c.Ctx.Output.SetStatus(500) + c.Data["json"] = map[string]string{"error": err.Error()} + c.ServeJSON() + return + } + + kubeconfig, err := NewHostKubeWithToken(generatedToken) + if err != nil { + fmt.Println("Could not retrieve the Kubeconfig edited with token") + fmt.Println(err) + c.Ctx.Output.SetStatus(500) + c.Data["json"] = map[string]string{"error": err.Error()} + c.ServeJSON() + return + } + + c.Data["json"] = kubeconfig + c.ServeJSON() + return +} + + +func NewHostKubeWithToken(token string) (*models.KubeConfigValue, error){ + if len(token) == 0 { + return nil, fmt.Errorf("You didn't provide a token to be inserted in the Kubeconfig") + } + + encodedCA := base64.StdEncoding.EncodeToString([]byte(conf.GetConfig().KubeCA)) + + hostKube := models.KubeConfigValue{ + APIVersion: "v1", + CurrentContext: "default", + Kind: "Config", + Preferences: struct{}{}, + Clusters: []models.KubeconfigNamedCluster{ + { + Name: "default", + Cluster: models.KubeconfigCluster{ + Server: conf.GetConfig().KubeHost, + CertificateAuthorityData: encodedCA, + }, + }, + }, + Contexts: []models.KubeconfigNamedContext{ + { + Name: "default", + Context: models.KubeconfigContext{ + Cluster: "default", + User: "default", + }, + }, + }, + Users: []models.KubeconfigUser{ + models.KubeconfigUser{ + Name: "default", + User: models.KubeconfigUserKeyPair{ + Token: token, + }, + }, + }, + } + + return &hostKube, nil } \ No newline at end of file diff --git a/docs/admiralty_setup.puml b/docs/admiralty_setup.puml index 1638a32..cf418dd 100644 --- a/docs/admiralty_setup.puml +++ b/docs/admiralty_setup.puml @@ -9,11 +9,9 @@ workflow --> locdc : POST /booking/ {booking object} locdc --> locdc : create Namespace + ServiceAccount monitord --> monitord : retrieves a Workflow to execute monitord --> monitord : workflow needs repartited execution -' monitord --> locdc : POST /admiralty/setup/:executions_id monitord --> rocdc : POST /????? (route that use the same \nmethods as /booking/ to create NS & SA) monitord --> rocdc : POST /admiralty/source -rocdc --> rocdc : create token for SA in NS -rocdc --> rocdc : edit +monitord --> rodc : GET /admiralty/token/:execution_id rocdc -> monitord : base64 encoded edited kubeconfig with token (**how to make it secure** ???) monitord --> locdc : POST /admiralty/secret/:execution_id monitord --> locdc : POST /admiralty/target/:execution_id diff --git a/infrastructure/kubernetes.go b/infrastructure/kubernetes.go index 327dfc3..07a0a03 100644 --- a/infrastructure/kubernetes.go +++ b/infrastructure/kubernetes.go @@ -174,6 +174,9 @@ func (k *KubernetesService) DeleteNamespace(ctx context.Context, ns string) erro return nil } +// Returns the string representing the token generated for the serviceAccount +// in the namespace identified by the value `ns` with the name sa-`ns`, which is valid for +// `duration` seconds func (k *KubernetesService) GenerateToken(ctx context.Context, ns string, duration int) (string, error) { // Define TokenRequest (valid for 1 hour) d := int64(duration) diff --git a/models/kubeconfig.go b/models/kubeconfig.go new file mode 100644 index 0000000..5c1d652 --- /dev/null +++ b/models/kubeconfig.go @@ -0,0 +1,56 @@ +package models + +// KubeConfigValue is a struct used to create a kubectl configuration YAML file. +type KubeConfigValue struct { + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Clusters []KubeconfigNamedCluster `yaml:"clusters"` + Users []KubeconfigUser `yaml:"users"` + Contexts []KubeconfigNamedContext `yaml:"contexts"` + CurrentContext string `yaml:"current-context"` + Preferences struct{} `yaml:"preferences"` +} + +// KubeconfigUser is a struct used to create a kubectl configuration YAML file +type KubeconfigUser struct { + Name string `yaml:"name"` + User KubeconfigUserKeyPair `yaml:"user"` + +} + +// KubeconfigUserKeyPair is a struct used to create a kubectl configuration YAML file +type KubeconfigUserKeyPair struct { + Token string `yaml:"token"` +} + +// KubeconfigAuthProvider is a struct used to create a kubectl authentication provider +type KubeconfigAuthProvider struct { + Name string `yaml:"name"` + Config map[string]string `yaml:"config"` +} + +// KubeconfigNamedCluster is a struct used to create a kubectl configuration YAML file +type KubeconfigNamedCluster struct { + Name string `yaml:"name"` + Cluster KubeconfigCluster `yaml:"cluster"` +} + +// KubeconfigCluster is a struct used to create a kubectl configuration YAML file +type KubeconfigCluster struct { + Server string `yaml:"server"` + CertificateAuthorityData string `yaml:"certificate-authority-data"` + CertificateAuthority string `yaml:"certificate-authority"` +} + +// KubeconfigNamedContext is a struct used to create a kubectl configuration YAML file +type KubeconfigNamedContext struct { + Name string `yaml:"name"` + Context KubeconfigContext `yaml:"context"` +} + +// KubeconfigContext is a struct used to create a kubectl configuration YAML file +type KubeconfigContext struct { + Cluster string `yaml:"cluster"` + Namespace string `yaml:"namespace,omitempty"` + User string `yaml:"user"` +} \ No newline at end of file