Uniformisation and verification of admiralty link with nodes + token

This commit is contained in:
pb 2025-02-27 17:00:36 +01:00
parent 44abc073c4
commit 74ac2b6d9c
5 changed files with 176 additions and 32 deletions

View File

@ -5,8 +5,12 @@ import (
"fmt" "fmt"
"oc-datacenter/infrastructure" "oc-datacenter/infrastructure"
"slices" "slices"
"time"
beego "github.com/beego/beego/v2/server/web" beego "github.com/beego/beego/v2/server/web"
jwt "github.com/golang-jwt/jwt/v5"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/yaml"
) )
type KubeInfo struct { type KubeInfo struct {
@ -20,6 +24,12 @@ type Kubeconfig struct {
Data *string Data *string
} }
type KubeUser struct {
Name string
User struct {
Token string
}
}
// Operations about the admiralty objects of the datacenter // Operations about the admiralty objects of the datacenter
type AdmiraltyController struct { type AdmiraltyController struct {
beego.Controller beego.Controller
@ -76,9 +86,9 @@ func (c *AdmiraltyController) GetOneTarget() {
// @Description Create an Admiralty Source on remote cluster // @Description Create an Admiralty Source on remote cluster
// @Param dc_id path string true "which dc to contact" // @Param dc_id path string true "which dc to contact"
// @Param execution path string true "execution id of the workflow" // @Param execution path string true "execution id of the workflow"
// @Param serviceAccount body controllers.KubeInfo true "url and serviceAccount to use with the source formatted as json object" // @Param kubeconfigInfo body controllers.KubeInfo true "url and serviceAccount to use with the source formatted as json object"
// @Success 200 // @Success 200
// @router /sources/:dc_id/:execution [post] // @router /source/:dc_id/:execution [post]
func (c *AdmiraltyController) CreateSource() { func (c *AdmiraltyController) CreateSource() {
var data KubeInfo var data KubeInfo
json.Unmarshal(c.Ctx.Input.CopyBody(10000000),&data) json.Unmarshal(c.Ctx.Input.CopyBody(10000000),&data)
@ -114,7 +124,7 @@ func (c *AdmiraltyController) CreateSource() {
return return
} }
res, err := serv.CreateAdmiraltySource(execution) res, err := serv.CreateAdmiraltySource(c.Ctx.Request.Context(),execution)
if err != nil { if err != nil {
// change code to 500 // change code to 500
c.Ctx.Output.SetStatus(500) c.Ctx.Output.SetStatus(500)
@ -152,7 +162,7 @@ func (c *AdmiraltyController) CreateAdmiraltyTarget(){
return return
} }
resp, err := serv.CreateAdmiraltyTarget(execution) resp, err := serv.CreateAdmiraltyTarget(c.Ctx.Request.Context(),execution)
if err != nil { if err != nil {
// change code to 500 // change code to 500
c.Ctx.Output.SetStatus(500) c.Ctx.Output.SetStatus(500)
@ -204,7 +214,7 @@ func(c *AdmiraltyController) GetKubeSecret() {
return return
} }
resp, err := serv.GetKubeconfigSecret(execution) resp, err := serv.GetKubeconfigSecret(c.Ctx.Request.Context(),execution)
if err != nil { if err != nil {
// change code to 500 // change code to 500
c.Ctx.Output.SetStatus(500) c.Ctx.Output.SetStatus(500)
@ -268,7 +278,7 @@ func (c *AdmiraltyController) CreateKubeSecret() {
return return
} }
resp, err := serv.CreateKubeconfigSecret(*kubeconfig.Data,execution) resp, err := serv.CreateKubeconfigSecret(c.Ctx.Request.Context(),*kubeconfig.Data,execution)
if err != nil { if err != nil {
// change code to 500 // change code to 500
c.Ctx.Output.SetStatus(500) c.Ctx.Output.SetStatus(500)
@ -282,3 +292,100 @@ func (c *AdmiraltyController) CreateKubeSecret() {
c.ServeJSON() c.ServeJSON()
} }
// @name GetAdmiraltyNodes
// @description Allows user to test if an admiralty connection has already been established : Target and valid Secret set up on the local host and Source set up on remote host
// @Param dc_id path string true "which dc to contact"
// @Param execution path string true "execution id of the workflow"
// @Success 200
// @Success 203
// @router /node/:dc_id/:execution [get]
func (c *AdmiraltyController) GetNodeReady(){
var secret v1.Secret
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
}
node, err := serv.GetOneNode(c.Ctx.Request.Context(),execution)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
if node == nil {
c.Ctx.Output.SetStatus(404)
c.Data["json"] = map[string]string{
"error" : "the node for " + execution + " can't be found, make sure both target and source resources are set up on local and remote hosts",
}
c.ServeJSON()
return
}
resp, err := serv.GetKubeconfigSecret(c.Ctx.Request.Context(),execution)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": err.Error()}
c.ServeJSON()
return
}
if resp == nil {
c.Ctx.Output.SetStatus(500)
c.Data["json"] = map[string]string{"error": "Nodes was up but the secret can't be found"}
c.ServeJSON()
return
}
// Extract JWT token RS265 encoded
var editedKubeconfig map[string]interface{}
var kubeUsers []KubeUser
json.Unmarshal(resp,&secret)
byteEditedKubeconfig := secret.Data["config"]
err = yaml.Unmarshal(byteEditedKubeconfig,&editedKubeconfig)
// err = json.Unmarshal(byteEditedKubeconfig,&editedKubeconfig)
if err != nil {
fmt.Println("Error while retrieving the kubeconfig from secret-",execution)
fmt.Println(err)
c.Ctx.Output.SetStatus(500)
c.Data["json"] = err
c.ServeJSON()
return
}
b, err := json.Marshal(editedKubeconfig["users"])
err = yaml.Unmarshal(b,&kubeUsers)
token := kubeUsers[0].User.Token
// Decode token
t, _, err := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{})
if err != nil {
fmt.Println("couldn't decode token")
c.Data["json"] = false
c.ServeJSON()
}
expiration, err := t.Claims.GetExpirationTime()
fmt.Println("Expiration date : " + expiration.UTC().Format("2006-01-02T15:04:05"))
if expiration.Add(1 * time.Hour).Unix() < time.Now().Unix() {
c.Data["json"] = map[string]string{
"token" : "token in the secret is expired and must be regenerated",
}
c.ServeJSON()
}
c.Data["json"] = map[string]bool{"ok": true}
c.ServeJSON()
}

View File

@ -21,6 +21,8 @@ type BookingController struct {
beego.Controller beego.Controller
} }
var BookingExample booking.Booking
// @Title Search // @Title Search
// @Description search bookings by execution // @Description search bookings by execution
// @Param id path string true "id execution" // @Param id path string true "id execution"
@ -209,7 +211,13 @@ func (o *BookingController) Post() {
*/ */
var resp booking.Booking var resp booking.Booking
user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request) user, peerID, groups := oclib.ExtractTokenInfo(*o.Ctx.Request)
json.Unmarshal(o.Ctx.Input.CopyBody(10000000), &resp) err := json.Unmarshal(o.Ctx.Input.CopyBody(10000000), &resp)
if err != nil {
fmt.Println("Error unmarshalling")
fmt.Println(err)
fmt.Println(resp)
}
dc_id := resp.ResourceID dc_id := resp.ResourceID
// delete all previous bookings // delete all previous bookings
isDraft := o.Ctx.Input.Query("is_draft") isDraft := o.Ctx.Input.Query("is_draft")

View File

@ -40,7 +40,7 @@ func (o *SessionController) GetToken() {
return return
} }
fmt.Println("BLAPO", id, duration) fmt.Println("BLAPO", id, duration)
token, err := serv.GetToken(o.Ctx.Request.Context(), id, duration) token, err := serv.GenerateToken(o.Ctx.Request.Context(), id, duration)
if err != nil { if err != nil {
// change code to 500 // change code to 500
o.Ctx.Output.SetStatus(500) o.Ctx.Output.SetStatus(500)

View File

@ -4,20 +4,23 @@ import (
"context" "context"
"errors" "errors"
"oc-datacenter/conf" "oc-datacenter/conf"
v1 "k8s.io/api/core/v1"
) )
type Infrastructure interface { type Infrastructure interface {
CreateNamespace(ctx context.Context, ns string) error CreateNamespace(ctx context.Context, ns string) error
DeleteNamespace(ctx context.Context, ns string) error DeleteNamespace(ctx context.Context, ns string) error
GetToken(ctx context.Context, ns string, duration int) (string, error) GenerateToken(ctx context.Context, ns string, duration int) (string, error)
CreateServiceAccount(ctx context.Context, ns string) error CreateServiceAccount(ctx context.Context, ns string) error
CreateRoleBinding(ctx context.Context, ns string, roleBinding string, role string) error CreateRoleBinding(ctx context.Context, ns string, roleBinding string, role string) error
CreateRole(ctx context.Context, ns string, role string, groups [][]string, resources [][]string, verbs [][]string) error CreateRole(ctx context.Context, ns string, role string, groups [][]string, resources [][]string, verbs [][]string) error
GetTargets(ctx context.Context) ([]string,error) GetTargets(ctx context.Context) ([]string,error)
CreateAdmiraltySource(executionId string) ([]byte, error) CreateAdmiraltySource(context context.Context,executionId string) ([]byte, error)
CreateKubeconfigSecret(kubeconfig string, executionId string) ([]byte, error) CreateKubeconfigSecret(context context.Context,kubeconfig string, executionId string) ([]byte, error)
GetKubeconfigSecret(executionId string) ([]byte, error) GetKubeconfigSecret(context context.Context,executionId string) ([]byte, error)
CreateAdmiraltyTarget(executionId string)([]byte,error) CreateAdmiraltyTarget(context context.Context,executionId string)([]byte,error)
GetOneNode(context context.Context,executionID string) (*v1.Node, error)
} }
var _service = map[string]func() (Infrastructure, error){ var _service = map[string]func() (Infrastructure, error){

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"oc-datacenter/conf" "oc-datacenter/conf"
"strings"
authv1 "k8s.io/api/authentication/v1" authv1 "k8s.io/api/authentication/v1"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
@ -77,6 +78,7 @@ func NewRemoteKubernetesService(url string, ca string, cert string, key string)
func (k *KubernetesService) CreateNamespace(ctx context.Context, ns string) error { func (k *KubernetesService) CreateNamespace(ctx context.Context, ns string) error {
// Define the namespace // Define the namespace
fmt.Println("ExecutionID in CreateNamespace() : ", ns)
namespace := &v1.Namespace{ namespace := &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: ns, Name: ns,
@ -172,7 +174,7 @@ func (k *KubernetesService) DeleteNamespace(ctx context.Context, ns string) erro
return nil return nil
} }
func (k *KubernetesService) GetToken(ctx context.Context, ns string, duration int) (string, error) { func (k *KubernetesService) GenerateToken(ctx context.Context, ns string, duration int) (string, error) {
// Define TokenRequest (valid for 1 hour) // Define TokenRequest (valid for 1 hour)
d := int64(duration) d := int64(duration)
tokenRequest := &authv1.TokenRequest{ tokenRequest := &authv1.TokenRequest{
@ -190,6 +192,8 @@ func (k *KubernetesService) GetToken(ctx context.Context, ns string, duration in
return token.Status.Token, nil return token.Status.Token, nil
} }
// Needs refactoring : // Needs refactoring :
// - Retrieving the metada (in a method that Unmarshall the part of the json in a metadata object) // - Retrieving the metada (in a method that Unmarshall the part of the json in a metadata object)
func (k *KubernetesService) GetTargets(ctx context.Context) ([]string,error){ func (k *KubernetesService) GetTargets(ctx context.Context) ([]string,error){
@ -244,15 +248,15 @@ func (k *KubernetesService) GetTargets(ctx context.Context) ([]string,error){
// - have declared the same namespace as the one where the pods are created in the local cluster // - have declared the same namespace as the one where the pods are created in the local cluster
// //
// - have delcared a serviceAccount with sufficient permission to create pods // - have delcared a serviceAccount with sufficient permission to create pods
func (k *KubernetesService) CreateAdmiraltyTarget(executionId string)([]byte,error){ func (k *KubernetesService) CreateAdmiraltyTarget(context context.Context,executionId string)([]byte,error){
exists, err := k.GetKubeconfigSecret(executionId) exists, err := k.GetKubeconfigSecret(context,executionId)
if err != nil { if err != nil {
fmt.Println("Error verifying kube-secret before creating target") fmt.Println("Error verifying kube-secret before creating target")
return nil, err return nil, err
} }
if exists == nil { if exists == nil {
fmt.Println("Target needs to be binded to a secret in ns-",executionId) fmt.Println("Target needs to be binded to a secret in namespace ",executionId)
return nil, nil // Maybe we could create a wrapper for errors and add more info to have return nil, nil // Maybe we could create a wrapper for errors and add more info to have
} }
@ -269,8 +273,8 @@ func (k *KubernetesService) CreateAdmiraltyTarget(executionId string)([]byte,err
targetManifest = tpl.String() targetManifest = tpl.String()
resp, err := postCDRapiKube( resp, err := postCDRapiKube(
*k.Set, *k.Set,
context.TODO(), context,
"/apis/multicluster.admiralty.io/v1alpha1/namespaces/ns-"+ executionId +"/targets", "/apis/multicluster.admiralty.io/v1alpha1/namespaces/"+ executionId +"/targets",
[]byte(targetManifest), []byte(targetManifest),
map[string]string{"fieldManager":"kubectl-client-side-apply"}, map[string]string{"fieldManager":"kubectl-client-side-apply"},
map[string]string{"fieldValidation":"Strict"}, map[string]string{"fieldValidation":"Strict"},
@ -293,7 +297,7 @@ func (k *KubernetesService) CreateAdmiraltyTarget(executionId string)([]byte,err
// This method is temporary to implement the use of Admiralty, but must be edited // This method is temporary to implement the use of Admiralty, but must be edited
// to rather contact the oc-datacenter from the remote cluster to create the source // to rather contact the oc-datacenter from the remote cluster to create the source
// locally and retrieve the token for the serviceAccount // locally and retrieve the token for the serviceAccount
func (k *KubernetesService) CreateAdmiraltySource(executionId string) ([]byte, error) { func (k *KubernetesService) CreateAdmiraltySource(context context.Context,executionId string) ([]byte, error) {
var sourceManifest string var sourceManifest string
var tpl bytes.Buffer var tpl bytes.Buffer
tmpl, err := template.New("source"). tmpl, err := template.New("source").
@ -308,8 +312,8 @@ func (k *KubernetesService) CreateAdmiraltySource(executionId string) ([]byte, e
resp, err := postCDRapiKube( resp, err := postCDRapiKube(
*k.Set, *k.Set,
context.TODO(), context,
"/apis/multicluster.admiralty.io/v1alpha1/namespaces/ns-"+ executionId +"/sources", "/apis/multicluster.admiralty.io/v1alpha1/namespaces/"+ executionId +"/sources",
[]byte(sourceManifest), []byte(sourceManifest),
map[string]string{"fieldManager":"kubectl-client-side-apply"}, map[string]string{"fieldManager":"kubectl-client-side-apply"},
map[string]string{"fieldValidation":"Strict"}, map[string]string{"fieldValidation":"Strict"},
@ -326,7 +330,7 @@ func (k *KubernetesService) CreateAdmiraltySource(executionId string) ([]byte, e
// Create a secret from a kubeconfing. Use it to create the secret binded to an Admiralty // Create a secret from a kubeconfing. Use it to create the secret binded to an Admiralty
// target, which must contain the serviceAccount's token value // target, which must contain the serviceAccount's token value
func (k *KubernetesService) CreateKubeconfigSecret(kubeconfig string, executionId string) ([]byte, error) { func (k *KubernetesService) CreateKubeconfigSecret(context context.Context,kubeconfig string, executionId string) ([]byte, error) {
config, err := base64.StdEncoding.DecodeString(kubeconfig) config, err := base64.StdEncoding.DecodeString(kubeconfig)
// config, err := base64.RawStdEncoding.DecodeString(kubeconfig) // config, err := base64.RawStdEncoding.DecodeString(kubeconfig)
if err != nil { if err != nil {
@ -338,20 +342,20 @@ func (k *KubernetesService) CreateKubeconfigSecret(kubeconfig string, executionI
secretManifest := &v1.Secret{ secretManifest := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "kube-secret-" + executionId, Name: "kube-secret-" + executionId,
Namespace: "ns-" + executionId, Namespace: executionId,
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"config": config, "config": config,
}, },
} }
exists, err := k.GetKubeconfigSecret(executionId) exists, err := k.GetKubeconfigSecret(context,executionId)
if err != nil { if err != nil {
fmt.Println("Error verifying if kube secret exists in ns-", executionId) fmt.Println("Error verifying if kube secret exists in namespace ", executionId)
return nil, err return nil, err
} }
if exists != nil { if exists != nil {
fmt.Println("kube-secret already exists in ns-", executionId) fmt.Println("kube-secret already exists in namespace", executionId)
fmt.Println("Overriding existing kube-secret with a newer resource") fmt.Println("Overriding existing kube-secret with a newer resource")
// TODO : implement DeleteKubeConfigSecret(executionID) // TODO : implement DeleteKubeConfigSecret(executionID)
deleted, err := k.DeleteKubeConfigSecret(executionId) deleted, err := k.DeleteKubeConfigSecret(executionId)
@ -359,8 +363,8 @@ func (k *KubernetesService) CreateKubeconfigSecret(kubeconfig string, executionI
_ = err _ = err
} }
resp, err := k.Set.CoreV1(). resp, err := k.Set.CoreV1().
Secrets("ns-"+executionId). Secrets(executionId).
Create(context.TODO(),secretManifest,metav1.CreateOptions{}) Create(context,secretManifest,metav1.CreateOptions{})
if err != nil { if err != nil {
fmt.Println("Error while trying to contact API to get secret kube-secret-"+executionId) fmt.Println("Error while trying to contact API to get secret kube-secret-"+executionId)
@ -377,10 +381,10 @@ func (k *KubernetesService) CreateKubeconfigSecret(kubeconfig string, executionI
return data, nil return data, nil
} }
func (k *KubernetesService) GetKubeconfigSecret(executionId string) ([]byte, error) { func (k *KubernetesService) GetKubeconfigSecret(context context.Context,executionId string) ([]byte, error) {
resp, err := k.Set.CoreV1(). resp, err := k.Set.CoreV1().
Secrets("ns-"+executionId). Secrets(executionId).
Get(context.TODO(),"kube-secret-"+executionId,metav1.GetOptions{}) Get(context,"kube-secret-"+executionId,metav1.GetOptions{})
if err != nil { if err != nil {
if(apierrors.IsNotFound(err)){ if(apierrors.IsNotFound(err)){
@ -442,3 +446,25 @@ func postCDRapiKube(client kubernetes.Clientset, ctx context.Context, path strin
return resp, nil return resp, nil
} }
func (k *KubernetesService) GetOneNode(context context.Context,executionID string) (*v1.Node, error) {
res, err := k.Set.CoreV1().
Nodes().
List(
context,
metav1.ListOptions{},
)
if err != nil {
fmt.Println("Error getting the list of nodes from k8s API")
fmt.Println(err)
return nil, err
}
for _, node := range res.Items {
if isNode := strings.Contains(node.Name,"admiralty-"+executionID+"-target-"+executionID+"-"); isNode {
return &node, nil
}
}
return nil, nil
}