Neo Oclib + Config Minio For private source
This commit is contained in:
@@ -20,9 +20,10 @@ var roleWaiters sync.Map
|
||||
|
||||
// ArgoKubeEvent carries the peer-routing metadata for a resource provisioning event.
|
||||
//
|
||||
// When MinioID is non-empty and Local is false, the event concerns Minio credential provisioning.
|
||||
// When Local is true, the event concerns local PVC provisioning.
|
||||
// Otherwise it concerns Admiralty kubeconfig provisioning.
|
||||
// Dispatch rules (evaluated in order):
|
||||
// 1. Type == STORAGE_RESOURCE → Minio credentials or PVC
|
||||
// 2. (Type == PROCESSING_RESOURCE || DATA_RESOURCE) && SourceResourceID != "" → Minio++ pre-signed URL (Phase 4)
|
||||
// 3. everything else → Admiralty kubeconfig
|
||||
type ArgoKubeEvent struct {
|
||||
ExecutionsID string `json:"executions_id"`
|
||||
DestPeerID string `json:"dest_peer_id"`
|
||||
@@ -36,8 +37,13 @@ type ArgoKubeEvent struct {
|
||||
// response is routed back to this peer once provisioning completes.
|
||||
OriginID string `json:"origin_id,omitempty"`
|
||||
// Images is the list of container images to pre-pull on the target peer
|
||||
// before the workflow starts. Empty for STORAGE_RESOURCE events.
|
||||
// before the workflow starts. Empty for STORAGE_RESOURCE / PROCESSING_RESOURCE source events.
|
||||
Images []string `json:"images,omitempty"`
|
||||
// SourceResourceID is non-empty only for Phase 4 (isReachable=false):
|
||||
// it identifies the Processing or Data resource whose binary/data must be
|
||||
// fetched via a pre-signed Minio URL. When set, the event is a Minio++ request,
|
||||
// NOT an Admiralty compute event.
|
||||
SourceResourceID string `json:"source_resource_id,omitempty"`
|
||||
}
|
||||
|
||||
// ListenNATS starts all NATS subscriptions for the infrastructure layer.
|
||||
@@ -54,6 +60,9 @@ func ListenNATS() {
|
||||
}
|
||||
kube := kubernetes.NewKubernetesService(argo.ExecutionsID)
|
||||
|
||||
isSourcePresign := argo.SourceResourceID != "" &&
|
||||
(argo.Type == tools.PROCESSING_RESOURCE || argo.Type == tools.DATA_RESOURCE)
|
||||
|
||||
if argo.Type == tools.STORAGE_RESOURCE {
|
||||
if argo.Local {
|
||||
fmt.Println("DETECT LOCAL PVC ARGO_KUBE_EVENT")
|
||||
@@ -126,6 +135,40 @@ func ListenNATS() {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if isSourcePresign {
|
||||
fmt.Println("DETECT SOURCE PRESIGN ARGO_KUBE_EVENT", argo.Type, argo.SourceResourceID)
|
||||
// ── Minio++ : génération d'URL pré-signée pour source privée ──
|
||||
presigner := storage.NewSourcePresigner(argo.ExecutionsID, argo.SourceResourceID)
|
||||
event := storage.SourcePresignEvent{
|
||||
ExecutionsID: argo.ExecutionsID,
|
||||
SourceResourceID: argo.SourceResourceID,
|
||||
SourcePeerID: argo.SourcePeerID,
|
||||
DestPeerID: argo.DestPeerID,
|
||||
OriginID: argo.OriginID,
|
||||
DataType: argo.Type.EnumIndex(),
|
||||
}
|
||||
if argo.SourcePeerID == argo.DestPeerID {
|
||||
// Même peer : génère directement l'URL et émet CONSIDERS_EVENT local.
|
||||
go presigner.InitializeAsSource(context.Background(), event, true)
|
||||
} else {
|
||||
// Cross-peer : route via PROPALGATION_EVENT(PB_SOURCE_PRESIGN)
|
||||
// vers le peer propriétaire de la ressource.
|
||||
if b, err := json.Marshal(event); err == nil {
|
||||
if b2, err := json.Marshal(&tools.PropalgationMessage{
|
||||
Payload: b,
|
||||
Action: tools.PB_SOURCE_PRESIGN,
|
||||
}); err == nil {
|
||||
fmt.Println("ROUTE SOURCE PRESIGN TO", argo.SourcePeerID)
|
||||
go tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
|
||||
FromApp: "oc-datacenter",
|
||||
Datatype: argo.Type,
|
||||
User: resp.User,
|
||||
Method: int(tools.PROPALGATION_EVENT),
|
||||
Payload: b2,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Println("DETECT COMPUTE ARGO_KUBE_EVENT")
|
||||
// ── Pre-pull + Admiralty kubeconfig provisioning ─────────────
|
||||
@@ -165,6 +208,20 @@ func ListenNATS() {
|
||||
}
|
||||
},
|
||||
|
||||
// ─── SOURCE_PRESIGN_EVENT ────────────────────────────────────────────────────
|
||||
// Forwarded by oc-discovery after receiving PB_SOURCE_PRESIGN via libp2p
|
||||
// ProtocolSourcePresignResource. This peer is the resource owner (Minio source).
|
||||
// It generates a pre-signed URL and responds via PB_CONSIDERS → OriginID.
|
||||
tools.SOURCE_PRESIGN_EVENT: func(resp tools.NATSResponse) {
|
||||
event := storage.SourcePresignEvent{}
|
||||
if err := json.Unmarshal(resp.Payload, &event); err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println("SOURCE_PRESIGN_EVENT received resource=", event.SourceResourceID)
|
||||
presigner := storage.NewSourcePresigner(event.ExecutionsID, event.SourceResourceID)
|
||||
go presigner.InitializeAsSource(context.Background(), event, false)
|
||||
},
|
||||
|
||||
// ─── ADMIRALTY_CONFIG_EVENT ─────────────────────────────────────────────────
|
||||
// Forwarded by oc-discovery after receiving via libp2p ProtocolAdmiraltyConfigResource.
|
||||
// Payload is a KubeconfigEvent (phase discriminated by Kubeconfig presence).
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package storage
|
||||
|
||||
// source_key_store.go — lecture de la table opaque_key → real_path (côté oc-datacenter)
|
||||
//
|
||||
// oc-catalog est le seul writer : il génère les clés et écrit le fichier JSON.
|
||||
// oc-datacenter est read-only : il résout une clé opaque avant de générer
|
||||
// une presigned URL pour s'assurer que la clé est bien connue du peer local
|
||||
// (protection contre les clés forgées par un peer distant).
|
||||
//
|
||||
// Partage de volume : oc-catalog et oc-datacenter montent le même volume Docker
|
||||
// sur /data. oc-catalog écrit /data/source-keys.json via write-then-rename
|
||||
// (atomique sur Linux). oc-datacenter lit le fichier à chaque Resolve — pas de
|
||||
// cache en mémoire, ce qui garantit de voir les clés écrites après le démarrage.
|
||||
// Le fichier étant petit (centaines d'entrées), le coût I/O est négligeable.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
oclib "cloud.o-forge.io/core/oc-lib"
|
||||
)
|
||||
|
||||
// SourceKeyEntry est une entrée du store.
|
||||
type SourceKeyEntry struct {
|
||||
RealPath string `json:"real_path"`
|
||||
ResourceID string `json:"resource_id"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// SourceKeyStore est le lecteur de la table privée.
|
||||
// Pas de cache — lit le fichier à chaque appel pour voir les nouvelles entrées
|
||||
// écrites par oc-catalog sans nécessiter de coordination inter-process.
|
||||
type SourceKeyStore struct {
|
||||
path string
|
||||
}
|
||||
|
||||
var (
|
||||
globalSourceKeyStore *SourceKeyStore
|
||||
sourceKeyStoreInitOnce sync.Once
|
||||
)
|
||||
|
||||
// InitSourceKeyStore initialise le store global avec le chemin du fichier partagé.
|
||||
// Idempotent.
|
||||
func InitSourceKeyStore(path string) {
|
||||
sourceKeyStoreInitOnce.Do(func() {
|
||||
globalSourceKeyStore = &SourceKeyStore{path: path}
|
||||
log := oclib.GetLogger()
|
||||
log.Info().Msgf("SourceKeyStore: watching %s (shared volume with oc-catalog)", path)
|
||||
})
|
||||
}
|
||||
|
||||
// GetSourceKeyStore retourne le store global.
|
||||
func GetSourceKeyStore() *SourceKeyStore {
|
||||
return globalSourceKeyStore
|
||||
}
|
||||
|
||||
// Resolve retourne l'entrée associée à opaqueKey, ou false si inconnue.
|
||||
// Lit le fichier JSON à chaque appel pour voir les entrées écrites par oc-catalog.
|
||||
func (s *SourceKeyStore) Resolve(opaqueKey string) (SourceKeyEntry, bool) {
|
||||
data, err := os.ReadFile(s.path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
log := oclib.GetLogger()
|
||||
log.Error().Msg(fmt.Sprintf("SourceKeyStore.Resolve: cannot read %s: %v", s.path, err))
|
||||
}
|
||||
return SourceKeyEntry{}, false
|
||||
}
|
||||
var entries map[string]SourceKeyEntry
|
||||
if err := json.Unmarshal(data, &entries); err != nil {
|
||||
log := oclib.GetLogger()
|
||||
log.Error().Msg(fmt.Sprintf("SourceKeyStore.Resolve: cannot parse %s: %v", s.path, err))
|
||||
return SourceKeyEntry{}, false
|
||||
}
|
||||
e, ok := entries[opaqueKey]
|
||||
return e, ok
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
package storage
|
||||
|
||||
// source_presign.go — Minio++ : génération d'URL pré-signée pour source privée (Phase 4)
|
||||
//
|
||||
// Appelé depuis le handler ARGO_KUBE_EVENT quand :
|
||||
// (Type == PROCESSING_RESOURCE || Type == DATA_RESOURCE) && SourceResourceID != ""
|
||||
//
|
||||
// Protocole :
|
||||
// 1. Le peer local reçoit la demande.
|
||||
// - Même peer que le propriétaire → génère l'URL directement.
|
||||
// - Peer distant → route via PROPALGATION_EVENT(PB_SOURCE_PRESIGN) vers le peer source.
|
||||
// 2. Le peer source génère une URL pré-signée Minio (TTL = 24h).
|
||||
// 3. Il émet PB_CONSIDERS avec { origin_id, executions_id, peer_id, resource_id, presigned_url }.
|
||||
// 4. oc-discovery route PB_CONSIDERS vers OriginID → CONSIDERS_EVENT local → oc-monitord.
|
||||
//
|
||||
// Convention bucket/key Minio :
|
||||
// bucket : "oc-resources"
|
||||
// key : SourceResourceID
|
||||
//
|
||||
// Le bucket "oc-resources" est permanent (créé à la publication de la ressource).
|
||||
// Pas de service account éphémère, pas de bucket par exécution.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"oc-datacenter/conf"
|
||||
|
||||
oclib "cloud.o-forge.io/core/oc-lib"
|
||||
"cloud.o-forge.io/core/oc-lib/models/live"
|
||||
"cloud.o-forge.io/core/oc-lib/models/resources"
|
||||
"cloud.o-forge.io/core/oc-lib/tools"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
)
|
||||
|
||||
const (
|
||||
// resourcesBucket est le bucket Minio permanent qui stocke les binaires/données
|
||||
// des ressources à source privée. Il est créé lors de la publication de la ressource.
|
||||
resourcesBucket = "oc-resources"
|
||||
// presignTTL est la durée de validité de l'URL pré-signée.
|
||||
// Suffisamment large pour couvrir l'exécution du workflow + buffer.
|
||||
presignTTL = 24 * time.Hour
|
||||
)
|
||||
|
||||
// SourcePresignEvent est le payload NATS échangé entre peers pour une demande
|
||||
// d'URL pré-signée (routé via PROPALGATION_EVENT action=PB_SOURCE_PRESIGN).
|
||||
type SourcePresignEvent struct {
|
||||
ExecutionsID string `json:"executions_id"`
|
||||
SourceResourceID string `json:"source_resource_id"`
|
||||
// SourcePeerID est le peer qui héberge la ressource (propriétaire Minio).
|
||||
SourcePeerID string `json:"source_peer_id"`
|
||||
// DestPeerID est le peer qui exécute le workflow (oc-monitord).
|
||||
DestPeerID string `json:"dest_peer_id"`
|
||||
// OriginID est le peer initiateur de la demande ; la réponse PB_CONSIDERS lui sera renvoyée.
|
||||
OriginID string `json:"origin_id"`
|
||||
// DataType encode PROCESSING_RESOURCE ou DATA_RESOURCE pour le logging.
|
||||
DataType int `json:"data_type"`
|
||||
}
|
||||
|
||||
// sourceConsidersPayload est le payload PB_CONSIDERS renvoyé par le peer source.
|
||||
// Le champ presigned_url est lu par oc-monitord (StartConsidersListener / globalSourceCache).
|
||||
type sourceConsidersPayload struct {
|
||||
OriginID string `json:"origin_id"`
|
||||
ExecutionsID string `json:"executions_id"`
|
||||
// PeerID est le SourcePeerID de l'événement original :
|
||||
// utilisé pour construire sourceConsidersKey dans oc-monitord.
|
||||
PeerID string `json:"peer_id"`
|
||||
ResourceID string `json:"resource_id"`
|
||||
PresignedURL string `json:"presigned_url,omitempty"`
|
||||
Error *string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// SourcePresigner porte le contexte d'une demande d'URL pré-signée.
|
||||
type SourcePresigner struct {
|
||||
ExecutionsID string
|
||||
SourceResourceID string
|
||||
}
|
||||
|
||||
func NewSourcePresigner(executionsID, resourceID string) *SourcePresigner {
|
||||
return &SourcePresigner{ExecutionsID: executionsID, SourceResourceID: resourceID}
|
||||
}
|
||||
|
||||
// ── Point d'entrée principal ──────────────────────────────────────────────────
|
||||
|
||||
// InitializeAsSource est appelé sur le peer qui héberge la ressource (Minio source).
|
||||
//
|
||||
// Il valide la clé opaque contre le SourceKeyStore local, génère une URL
|
||||
// pré-signée pour l'objet (resourcesBucket / SourceResourceID) puis émet
|
||||
// PB_CONSIDERS vers OriginID.
|
||||
//
|
||||
// self=true → le peer local est aussi le peer origine (CONSIDERS_EVENT direct).
|
||||
func (s *SourcePresigner) InitializeAsSource(ctx context.Context, event SourcePresignEvent, self bool) {
|
||||
logger := oclib.GetLogger()
|
||||
|
||||
// ── Validation de la clé opaque ──────────────────────────────────────────
|
||||
// Si la clé est inconnue du store local, c'est soit une clé forgée par B,
|
||||
// soit une clé non encore enregistrée. Dans les deux cas on refuse.
|
||||
var resolvedEntry SourceKeyEntry
|
||||
if store := GetSourceKeyStore(); store != nil {
|
||||
entry, ok := store.Resolve(s.SourceResourceID)
|
||||
if !ok {
|
||||
err := fmt.Errorf("unknown opaque key %q — refusing presign request", s.SourceResourceID)
|
||||
logger.Warn().Msg("SourcePresigner.InitializeAsSource: " + err.Error())
|
||||
s.emitConsiders(event, "", err, self)
|
||||
return
|
||||
}
|
||||
resolvedEntry = entry
|
||||
}
|
||||
|
||||
// ── Vérification des AE (Autorisations d'Exploitation) ───────────────────
|
||||
// Charge la ressource et vérifie que le peer demandeur (DestPeerID) est
|
||||
// autorisé par ses AEs. Seules les contraintes évaluables sans contexte
|
||||
// de workflow sont vérifiées ici (peer, validité temporelle, révocation).
|
||||
// Les contraintes de couplage sont vérifiées par oc-schedulerd.
|
||||
//
|
||||
// Dégradation gracieuse : si la ressource est introuvable en DB (ex. erreur
|
||||
// transitoire), on ne bloque pas — oc-schedulerd reste la barrière souveraine.
|
||||
if resolvedEntry.ResourceID != "" && event.DestPeerID != "" {
|
||||
if violations := s.checkResourceAE(resolvedEntry.ResourceID, event.DataType, event.DestPeerID); len(violations) > 0 {
|
||||
msgs := make([]string, 0, len(violations))
|
||||
for _, v := range violations {
|
||||
msgs = append(msgs, string(v.Type)+": "+v.Message)
|
||||
}
|
||||
err := fmt.Errorf("AE violation for resource %s peer %s: %s",
|
||||
resolvedEntry.ResourceID, event.DestPeerID, strings.Join(msgs, "; "))
|
||||
logger.Warn().Msg("SourcePresigner.InitializeAsSource: " + err.Error())
|
||||
// Signal de comportement frauduleux vers le peer demandeur.
|
||||
resources.EmitAEBehaviorReport(event.DestPeerID, violations)
|
||||
s.emitConsiders(event, "", err, self)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
minioURL, err := s.loadMinioURL(event.SourcePeerID)
|
||||
if err != nil {
|
||||
logger.Error().Msg("SourcePresigner.InitializeAsSource: " + err.Error())
|
||||
s.emitConsiders(event, "", err, self)
|
||||
return
|
||||
}
|
||||
|
||||
presignedURL, err := s.generatePresignedURL(ctx, minioURL, s.SourceResourceID)
|
||||
if err != nil {
|
||||
logger.Error().Msg("SourcePresigner.InitializeAsSource: failed to generate presigned URL: " + err.Error())
|
||||
s.emitConsiders(event, "", err, self)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info().Msg(fmt.Sprintf(
|
||||
"SourcePresigner: presigned URL generated for resource=%s exec=%s",
|
||||
s.SourceResourceID, s.ExecutionsID,
|
||||
))
|
||||
s.emitConsiders(event, presignedURL, nil, self)
|
||||
}
|
||||
|
||||
// ── Génération URL pré-signée ─────────────────────────────────────────────────
|
||||
|
||||
// generatePresignedURL ouvre un client Minio en lecture seule (root credentials)
|
||||
// et génère une URL pré-signée GET valide pour presignTTL.
|
||||
//
|
||||
// Le bucket est "oc-resources" (permanent) ; la clé est resourceID.
|
||||
func (s *SourcePresigner) generatePresignedURL(ctx context.Context, minioURL, resourceID string) (string, error) {
|
||||
client, err := minio.New(minioURL, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(conf.GetConfig().MinioRootKey, conf.GetConfig().MinioRootSecret, ""),
|
||||
Secure: false,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("generatePresignedURL: failed to create minio client: %w", err)
|
||||
}
|
||||
|
||||
reqParams := make(url.Values)
|
||||
presignedURL, err := client.PresignedGetObject(ctx, resourcesBucket, resourceID, presignTTL, reqParams)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("generatePresignedURL: PresignedGetObject failed bucket=%s key=%s: %w",
|
||||
resourcesBucket, resourceID, err)
|
||||
}
|
||||
|
||||
return presignedURL.String(), nil
|
||||
}
|
||||
|
||||
// ── Émission de la réponse ────────────────────────────────────────────────────
|
||||
|
||||
// emitConsiders publie la réponse PB_CONSIDERS contenant l'URL pré-signée
|
||||
// (ou l'erreur) vers le peer OriginID.
|
||||
//
|
||||
// self=true → CONSIDERS_EVENT direct sur NATS local (même peer).
|
||||
// self=false → PROPALGATION_EVENT(PB_CONSIDERS) → oc-discovery route vers OriginID.
|
||||
func (s *SourcePresigner) emitConsiders(event SourcePresignEvent, presignedURL string, provErr error, self bool) {
|
||||
var errStr *string
|
||||
if provErr != nil {
|
||||
e := provErr.Error()
|
||||
errStr = &e
|
||||
}
|
||||
|
||||
payload, _ := json.Marshal(sourceConsidersPayload{
|
||||
OriginID: event.OriginID,
|
||||
ExecutionsID: event.ExecutionsID,
|
||||
PeerID: event.SourcePeerID, // clé de routage dans globalSourceCache
|
||||
ResourceID: event.SourceResourceID,
|
||||
PresignedURL: presignedURL,
|
||||
Error: errStr,
|
||||
})
|
||||
|
||||
if self {
|
||||
tools.NewNATSCaller().SetNATSPub(tools.CONSIDERS_EVENT, tools.NATSResponse{
|
||||
FromApp: "oc-datacenter",
|
||||
Datatype: tools.PROCESSING_RESOURCE,
|
||||
Method: int(tools.CONSIDERS_EVENT),
|
||||
Payload: payload,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Cross-peer : PB_CONSIDERS → oc-discovery route vers OriginID via ProtocolConsidersResource.
|
||||
b, _ := json.Marshal(&tools.PropalgationMessage{
|
||||
DataType: tools.PROCESSING_RESOURCE.EnumIndex(),
|
||||
Action: tools.PB_CONSIDERS,
|
||||
Payload: payload,
|
||||
})
|
||||
tools.NewNATSCaller().SetNATSPub(tools.PROPALGATION_EVENT, tools.NATSResponse{
|
||||
FromApp: "oc-datacenter",
|
||||
Datatype: -1,
|
||||
Method: int(tools.PROPALGATION_EVENT),
|
||||
Payload: b,
|
||||
})
|
||||
}
|
||||
|
||||
// ── Lookup URL Minio locale ───────────────────────────────────────────────────
|
||||
|
||||
// loadMinioURL retrouve l'URL du Minio hébergé par peerID en cherchant dans
|
||||
// les live storages (même logique que MinioSetter.loadMinioURL).
|
||||
// Pour les sources privées on utilise le premier live storage disponible
|
||||
// (le Minio "resource store" du peer, pas un storage lié à un workflow).
|
||||
func (s *SourcePresigner) loadMinioURL(peerID string) (string, error) {
|
||||
res := oclib.NewRequest(oclib.LibDataEnum(oclib.LIVE_STORAGE), "", peerID, []string{}, nil).LoadAll(false, 0, 10000)
|
||||
if res.Err != "" {
|
||||
return "", fmt.Errorf("loadMinioURL: failed to load live storages for peer %s: %s", peerID, res.Err)
|
||||
}
|
||||
for _, dbo := range res.Data {
|
||||
l, ok := dbo.(*live.LiveStorage)
|
||||
if !ok || l.Source == "" {
|
||||
continue
|
||||
}
|
||||
return l.Source, nil
|
||||
}
|
||||
return "", fmt.Errorf("loadMinioURL: no live storage found for peer %s", peerID)
|
||||
}
|
||||
|
||||
// ── Vérification AE au presign ────────────────────────────────────────────────
|
||||
|
||||
// checkResourceAE charge la ressource identifiée par resourceID depuis la DB
|
||||
// locale et vérifie les AEs évaluables sans contexte de workflow :
|
||||
// - Révocation
|
||||
// - Validité temporelle (ValidFrom / ValidUntil)
|
||||
// - Restriction de peer (AllowedPeerIDs)
|
||||
//
|
||||
// Les contraintes de couplage (RequiredResourceIDs / ForbiddenResourceIDs) et
|
||||
// de workflow (AllowedWorkflowIDs) ne sont PAS vérifiées ici — oc-schedulerd
|
||||
// les vérifie de façon souveraine avant tout lancement d'exécution.
|
||||
//
|
||||
// Retourne nil si la ressource est introuvable (dégradation gracieuse).
|
||||
func (s *SourcePresigner) checkResourceAE(resourceID string, dataType int, consumerPeerID string) []resources.AEViolation {
|
||||
res := oclib.NewRequestAdmin(oclib.LibDataEnum(dataType), nil).LoadOne(resourceID)
|
||||
if res.Err != "" || res.Data == nil {
|
||||
log := oclib.GetLogger()
|
||||
log.Warn().Msgf("checkResourceAE: cannot load resource %s (type %d): %s — skipping AE check",
|
||||
resourceID, dataType, res.Err)
|
||||
return nil // dégradation gracieuse : oc-schedulerd reste la barrière
|
||||
}
|
||||
|
||||
type hasAE interface {
|
||||
GetExploitationAuthorizations() []resources.ExploitationAuthorization
|
||||
}
|
||||
ra, ok := res.Data.(hasAE)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
var violations []resources.AEViolation
|
||||
for _, ae := range ra.GetExploitationAuthorizations() {
|
||||
vs := checkAEPresign(ae, resourceID, consumerPeerID, now)
|
||||
violations = append(violations, vs...)
|
||||
}
|
||||
return violations
|
||||
}
|
||||
|
||||
// checkAEPresign évalue uniquement les contraintes d'une AE qui sont vérifiables
|
||||
// sans le contexte complet du workflow (couplage, workflow ID).
|
||||
func checkAEPresign(ae resources.ExploitationAuthorization, resourceID, consumerPeerID string, now time.Time) []resources.AEViolation {
|
||||
var vs []resources.AEViolation
|
||||
add := func(t resources.AEViolationType, msg string) {
|
||||
vs = append(vs, resources.AEViolation{AEID: ae.ID, ResourceID: resourceID, Type: t, Message: msg})
|
||||
}
|
||||
|
||||
if ae.IsRevoked {
|
||||
add(resources.AEViolationRevoked,
|
||||
fmt.Sprintf("AE %s for resource %s is revoked", ae.ID, resourceID))
|
||||
return vs // revoked → pas la peine de continuer
|
||||
}
|
||||
if ae.ValidUntil != nil && now.After(*ae.ValidUntil) {
|
||||
add(resources.AEViolationExpired,
|
||||
fmt.Sprintf("AE %s for resource %s expired at %s", ae.ID, resourceID, ae.ValidUntil.Format(time.RFC3339)))
|
||||
return vs
|
||||
}
|
||||
if ae.ValidFrom != nil && now.Before(*ae.ValidFrom) {
|
||||
add(resources.AEViolationNotYetValid,
|
||||
fmt.Sprintf("AE %s for resource %s not valid until %s", ae.ID, resourceID, ae.ValidFrom.Format(time.RFC3339)))
|
||||
return vs
|
||||
}
|
||||
// Restriction de peer : AllowedPeerIDs vide = tous les peers autorisés.
|
||||
if consumerPeerID != "" && len(ae.AllowedPeerIDs) > 0 {
|
||||
allowed := false
|
||||
for _, id := range ae.AllowedPeerIDs {
|
||||
if id == consumerPeerID {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
add(resources.AEViolationPeerNotAllowed,
|
||||
fmt.Sprintf("peer %s not in allowed list for resource %s (AE %s)",
|
||||
consumerPeerID, resourceID, ae.ID))
|
||||
}
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
// ── Nettoyage post-exécution ──────────────────────────────────────────────────
|
||||
|
||||
// CleanupSourceSecrets supprime tous les K8s Secrets créés par oc-monitord
|
||||
// pour les sources privées de cette exécution (label oc-execution-id=<execID>
|
||||
// et oc-secret-type=source-presigned).
|
||||
//
|
||||
// Appelé depuis TeardownForExecution() dans watchdog.go.
|
||||
func CleanupSourceSecrets(ctx context.Context, executionsID string) {
|
||||
logger := oclib.GetLogger()
|
||||
|
||||
k, err := tools.NewKubernetesService(
|
||||
conf.GetConfig().KubeHost+":"+conf.GetConfig().KubePort,
|
||||
conf.GetConfig().KubeCA, conf.GetConfig().KubeCert, conf.GetConfig().KubeData,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error().Msg("CleanupSourceSecrets: failed to create k8s service: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
labelSelector := "oc-execution-id=" + executionsID + ",oc-secret-type=source-presigned"
|
||||
if err := k.DeleteSecretsByLabel(ctx, executionsID, labelSelector); err != nil {
|
||||
logger.Error().Msg("CleanupSourceSecrets: " + err.Error())
|
||||
return
|
||||
}
|
||||
logger.Info().Msg("CleanupSourceSecrets: source secrets removed for exec " + executionsID)
|
||||
}
|
||||
@@ -454,5 +454,7 @@ func TeardownForExecution(executionID string, executionsID string) {
|
||||
admiralty.NewAdmiraltySetter(executionsID).TeardownIfRemote(exec, selfPeerID)
|
||||
storage.NewMinioSetter(executionsID, "").TeardownForExecution(ctx, selfPeerID)
|
||||
storage.NewPVCSetter(executionsID, "").TeardownForExecution(ctx, selfPeerID)
|
||||
// Supprime les Secrets K8s éphémères des sources privées (Phase 4).
|
||||
storage.CleanupSourceSecrets(ctx, executionsID)
|
||||
kubernetes.NewKubernetesService(executionsID).CleanupImages(ctx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user