// Proxy console (étape 9 du déploiement Syllabus). // // Laravel demande à Nova l'adresse de la console série d'une VM, la range // dans Redis sous un jeton aléatoire pour 5 minutes, et ne donne que ce // jeton au navigateur. Ce service lit le jeton, ouvre (ou réutilise) la // connexion WebSocket vers nova-serialproxy, et recopie les octets dans les // deux sens — en lecture seule pour les abonnés qui n'ont pas le droit // d'écrire (voir hub.go). package main import ( "context" "encoding/json" "log" "net/http" "os" "github.com/gorilla/websocket" "github.com/redis/go-redis/v9" ) // Doit correspondre exactement à ce que Laravel écrit dans Redis // (App\Services\OpenStack\JetonConsole). type jetonConsole struct { URL string `json:"url"` InstanceID string `json:"instance_id"` Role string `json:"role"` } var rdb *redis.Client func main() { adresseEcoute := env("PROXY_CONSOLE_ECOUTE", "127.0.0.1:8081") adresseRedis := env("PROXY_CONSOLE_REDIS", "127.0.0.1:6380") motDePasseRedis := os.Getenv("PROXY_CONSOLE_REDIS_MOT_DE_PASSE") rdb = redis.NewClient(&redis.Options{ Addr: adresseRedis, Password: motDePasseRedis, }) http.HandleFunc("/console/", gererConsole) http.HandleFunc("/sante", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) log.Printf("proxy-console à l'écoute sur %s (Redis %s)", adresseEcoute, adresseRedis) log.Fatal(http.ListenAndServe(adresseEcoute, nil)) } func gererConsole(w http.ResponseWriter, r *http.Request) { jeton := r.URL.Path[len("/console/"):] if jeton == "" { http.Error(w, "jeton manquant", http.StatusBadRequest) return } valeur, err := rdb.Get(context.Background(), "console:"+jeton).Result() if err == redis.Nil { http.Error(w, "jeton inconnu ou expiré", http.StatusNotFound) return } if err != nil { log.Printf("erreur Redis pour le jeton %s : %v", jeton, err) http.Error(w, "erreur serveur", http.StatusInternalServerError) return } var jc jetonConsole if err := json.Unmarshal([]byte(valeur), &jc); err != nil { http.Error(w, "jeton corrompu", http.StatusInternalServerError) return } conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Printf("échec de l'upgrade WebSocket : %v", err) return } hub, err := obtenirHub(jc.InstanceID, jc.URL) if err != nil { log.Printf("échec de connexion à nova-serialproxy pour %s : %v", jc.InstanceID, err) _ = conn.WriteMessage(websocket.TextMessage, []byte("\r\nConsole indisponible.\r\n")) _ = conn.Close() return } client := &Client{conn: conn, role: jc.Role, hub: hub} hub.ajouter(client) client.pompeClient() } func env(cle, defaut string) string { if v := os.Getenv(cle); v != "" { return v } return defaut }