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"
"oc-datacenter/infrastructure"
"slices"
"time"
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 {
@ -20,6 +24,12 @@ type Kubeconfig struct {
Data *string
}
type KubeUser struct {
Name string
User struct {
Token string
}
}
// Operations about the admiralty objects of the datacenter
type AdmiraltyController struct {
beego.Controller
@ -76,9 +86,9 @@ func (c *AdmiraltyController) GetOneTarget() {
// @Description Create an Admiralty Source on remote cluster
// @Param dc_id path string true "which dc to contact"
// @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
// @router /sources/:dc_id/:execution [post]
// @router /source/:dc_id/:execution [post]
func (c *AdmiraltyController) CreateSource() {
var data KubeInfo
json.Unmarshal(c.Ctx.Input.CopyBody(10000000),&data)
@ -114,7 +124,7 @@ func (c *AdmiraltyController) CreateSource() {
return
}
res, err := serv.CreateAdmiraltySource(execution)
res, err := serv.CreateAdmiraltySource(c.Ctx.Request.Context(),execution)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
@ -152,7 +162,7 @@ func (c *AdmiraltyController) CreateAdmiraltyTarget(){
return
}
resp, err := serv.CreateAdmiraltyTarget(execution)
resp, err := serv.CreateAdmiraltyTarget(c.Ctx.Request.Context(),execution)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
@ -204,7 +214,7 @@ func(c *AdmiraltyController) GetKubeSecret() {
return
}
resp, err := serv.GetKubeconfigSecret(execution)
resp, err := serv.GetKubeconfigSecret(c.Ctx.Request.Context(),execution)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
@ -268,7 +278,7 @@ func (c *AdmiraltyController) CreateKubeSecret() {
return
}
resp, err := serv.CreateKubeconfigSecret(*kubeconfig.Data,execution)
resp, err := serv.CreateKubeconfigSecret(c.Ctx.Request.Context(),*kubeconfig.Data,execution)
if err != nil {
// change code to 500
c.Ctx.Output.SetStatus(500)
@ -281,4 +291,101 @@ func (c *AdmiraltyController) CreateKubeSecret() {
c.Data["json"] = respData
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
}
var BookingExample booking.Booking
// @Title Search
// @Description search bookings by execution
// @Param id path string true "id execution"
@ -209,7 +211,13 @@ func (o *BookingController) Post() {
*/
var resp booking.Booking
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
// delete all previous bookings
isDraft := o.Ctx.Input.Query("is_draft")

View File

@ -40,7 +40,7 @@ func (o *SessionController) GetToken() {
return
}
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 {
// change code to 500
o.Ctx.Output.SetStatus(500)

View File

@ -4,20 +4,23 @@ import (
"context"
"errors"
"oc-datacenter/conf"
v1 "k8s.io/api/core/v1"
)
type Infrastructure interface {
CreateNamespace(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
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
GetTargets(ctx context.Context) ([]string,error)
CreateAdmiraltySource(executionId string) ([]byte, error)
CreateKubeconfigSecret(kubeconfig string, executionId string) ([]byte, error)
GetKubeconfigSecret(executionId string) ([]byte, error)
CreateAdmiraltyTarget(executionId string)([]byte,error)
CreateAdmiraltySource(context context.Context,executionId string) ([]byte, error)
CreateKubeconfigSecret(context context.Context,kubeconfig string, executionId string) ([]byte, error)
GetKubeconfigSecret(context context.Context,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){

View File

@ -9,6 +9,7 @@ import (
"fmt"
"html/template"
"oc-datacenter/conf"
"strings"
authv1 "k8s.io/api/authentication/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 {
// Define the namespace
fmt.Println("ExecutionID in CreateNamespace() : ", ns)
namespace := &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns,
@ -172,7 +174,7 @@ func (k *KubernetesService) DeleteNamespace(ctx context.Context, ns string) erro
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)
d := int64(duration)
tokenRequest := &authv1.TokenRequest{
@ -190,6 +192,8 @@ func (k *KubernetesService) GetToken(ctx context.Context, ns string, duration in
return token.Status.Token, nil
}
// Needs refactoring :
// - 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){
@ -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 delcared a serviceAccount with sufficient permission to create pods
func (k *KubernetesService) CreateAdmiraltyTarget(executionId string)([]byte,error){
exists, err := k.GetKubeconfigSecret(executionId)
func (k *KubernetesService) CreateAdmiraltyTarget(context context.Context,executionId string)([]byte,error){
exists, err := k.GetKubeconfigSecret(context,executionId)
if err != nil {
fmt.Println("Error verifying kube-secret before creating target")
return nil, err
}
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
}
@ -269,8 +273,8 @@ func (k *KubernetesService) CreateAdmiraltyTarget(executionId string)([]byte,err
targetManifest = tpl.String()
resp, err := postCDRapiKube(
*k.Set,
context.TODO(),
"/apis/multicluster.admiralty.io/v1alpha1/namespaces/ns-"+ executionId +"/targets",
context,
"/apis/multicluster.admiralty.io/v1alpha1/namespaces/"+ executionId +"/targets",
[]byte(targetManifest),
map[string]string{"fieldManager":"kubectl-client-side-apply"},
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
// to rather contact the oc-datacenter from the remote cluster to create the source
// 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 tpl bytes.Buffer
tmpl, err := template.New("source").
@ -308,8 +312,8 @@ func (k *KubernetesService) CreateAdmiraltySource(executionId string) ([]byte, e
resp, err := postCDRapiKube(
*k.Set,
context.TODO(),
"/apis/multicluster.admiralty.io/v1alpha1/namespaces/ns-"+ executionId +"/sources",
context,
"/apis/multicluster.admiralty.io/v1alpha1/namespaces/"+ executionId +"/sources",
[]byte(sourceManifest),
map[string]string{"fieldManager":"kubectl-client-side-apply"},
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
// 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.RawStdEncoding.DecodeString(kubeconfig)
if err != nil {
@ -338,20 +342,20 @@ func (k *KubernetesService) CreateKubeconfigSecret(kubeconfig string, executionI
secretManifest := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-secret-" + executionId,
Namespace: "ns-" + executionId,
Namespace: executionId,
},
Data: map[string][]byte{
"config": config,
},
}
exists, err := k.GetKubeconfigSecret(executionId)
exists, err := k.GetKubeconfigSecret(context,executionId)
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
}
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")
// TODO : implement DeleteKubeConfigSecret(executionID)
deleted, err := k.DeleteKubeConfigSecret(executionId)
@ -359,8 +363,8 @@ func (k *KubernetesService) CreateKubeconfigSecret(kubeconfig string, executionI
_ = err
}
resp, err := k.Set.CoreV1().
Secrets("ns-"+executionId).
Create(context.TODO(),secretManifest,metav1.CreateOptions{})
Secrets(executionId).
Create(context,secretManifest,metav1.CreateOptions{})
if err != nil {
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
}
func (k *KubernetesService) GetKubeconfigSecret(executionId string) ([]byte, error) {
func (k *KubernetesService) GetKubeconfigSecret(context context.Context,executionId string) ([]byte, error) {
resp, err := k.Set.CoreV1().
Secrets("ns-"+executionId).
Get(context.TODO(),"kube-secret-"+executionId,metav1.GetOptions{})
Secrets(executionId).
Get(context,"kube-secret-"+executionId,metav1.GetOptions{})
if err != nil {
if(apierrors.IsNotFound(err)){
@ -441,4 +445,26 @@ func postCDRapiKube(client kubernetes.Clientset, ctx context.Context, path strin
}
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
}