Paillasse/proxy-console/main.go

100 lines
2.7 KiB
Go
Raw Normal View History

// 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
}