Pourquoi Securiser une application Go ?
La sécurité est un élément crucial dans tout développement logiciel moderne, y compris pour les applications Go. Dans un monde où la cybercriminalité continue à s'aggraver, il devient de plus en plus important de mettre en place des mesures de sécurité robustes pour protéger vos utilisateurs et votre application. Un cas d'usage concret est le traitement confidentiel des données personnelles : même une petite faille peut entraîner la perte de milliers de comptes.
Prerequis
Pour suivre ce tutoriel, vous aurez besoin des éléments suivants :
- Connaissances en Go : Vous devriez être familier avec les bases du langage Go.
- Environnement de développement : Un environnement de développement Go configuré (Go installed, editor with Go support).
- Gestionnaire de dépendances :
go modpour gérer les dépendances de votre projet.
Concepts fondamentaux
1. Authentification et Autorisation
L'authentification vérifie l'identité d'un utilisateur tandis que l'autorisation contrôle ce qu'il peut faire une fois authentifié.
// Exemple simplifié de l'utilisation de JWT pour l'authentification
package main
import (
"net/http"
"github.com/dgrijalva/jwt-go"
)
type Claims struct {
UserID int `json:"user_id"`
jwt.StandardClaims
}
func authenticateToken(w http.ResponseWriter, r *http.Request) (*Claims, error) {
tokenString := r.Header.Get("Authorization")
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("secret"), nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, err
}
return claims, nil
}
2. Hébergement sécurisé
Utilisez un hébergeur fiable et configurez vos serveurs pour minimiser les vulnérabilités.
// Exemple de configuration HTTP pour une application Go avec HTTPS
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, world!"))
})
srv := &http.Server{
Addr: ":8443",
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
},
}
log.Fatal(srv.ListenAndServeTLS("", ""))
}
3. Protection contre les injections SQL
Utilisez des paramètres nommés pour éviter les injections SQL.
// Exemple d'utilisation de paramètres nommés avec sqlx
package main
import (
"database/sql"
"log"
"github.com/jmoiron/sqlx"
)
func getUser(db *sqlx.DB, id int) (User, error) {
var user User
err := db.Get(&user, "SELECT id, name FROM users WHERE id=$1", id)
return user, err
}
type User struct {
ID int `db:"id"`
Name string `db:"name"`
}
4. Sérialisation et deserialisation sécurisée
Utilisez des librairies de sérialisation fiables comme encoding/json avec des options pour éviter les attaques de déserialisation.
// Exemple d'utilisation de json.Unmarshal avec des options
package main
import (
"encoding/json"
"log"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func unmarshalUser(data []byte) (*User, error) {
var user User
err := json.Unmarshal(data, &user)
if err != nil {
return nil, err
}
return &user, nil
}
Mise en pratique : projet fil rouge
Étape 1 : Configuration du projet
Créez un nouveau projet Go et initialisez-le avec go mod.
mkdir secure-go-app
cd secure-go-app
go mod init secure-go-app
Étape 2 : Création de la structure de base
Créez les fichiers suivants :
main.gomodels/user.gohandlers/auth.gomiddlewares/authMiddleware.go
Étape 3 : Implémentation de l'authentification JWT
// main.go
package main
import (
"net/http"
)
func main() {
http.HandleFunc("/login", authHandler)
http.HandleFunc("/", protectedHandler, AuthMiddleware)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func authHandler(w http.ResponseWriter, r *http.Request) {
// Implémentation de l'authentification
w.Write([]byte("Login successful!"))
}
func protectedHandler(w http.ResponseWriter, r *http.Request) {
// Implémentation de la gestion des requêtes protégées
w.Write([]byte("Access granted to protected resource!"))
}
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims, err := authenticateToken(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), "user", claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func authenticateToken(r *http.Request) (*Claims, error) {
tokenString := r.Header.Get("Authorization")
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("secret"), nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, err
}
return claims, nil
}
type Claims struct {
UserID int `json:"user_id"`
jwt.StandardClaims
}
go
// models/user.go
package models
import (
"time"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
}
go
// handlers/auth.go
package handlers
import (
"net/http"
"secure-go-app/models"
)
func registerHandler(w http.ResponseWriter, r *http.Request) {
// Implémentation de l'inscription
w.Write([]byte("Register successful!"))
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
// Implémentation de la connexion
w.Write([]byte("Login successful!"))
}
go
// middlewares/authMiddleware.go
package middlewares
import (
"net/http"
"secure-go-app/models"
)
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims, err := models.authenticateToken(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), "user", claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Étape 4 : Exécution du projet
go run main.go handlers/auth.go models/user.go middlewares/authMiddleware.go
Erreurs frequentes et debugging
1. Erreur d'authentification JWT
Code incorrect :
tokenString := r.Header.Get("Authorization")
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("wrong-secret"), nil
})
Code correct :
tokenString := r.Header.Get("Authorization")
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("secret"), nil
})
2. Erreur de sérialisation
Code incorrect :
var user User
err := json.Unmarshal(data, &user)
if err != nil {
return nil, err
}
Code correct :
var user User
err := json.Unmarshal(data, &user)
if err != nil {
// Log de l'erreur pour débogage
log.Printf("Error unmarshalling JSON: %v", err)
return nil, err
}
3. Erreur d'injection SQL
Code incorrect :
var user User
err := db.Get(&user, "SELECT id, name FROM users WHERE id="+r.URL.Query().Get("id"))
if err != nil {
return nil, err
}
Code correct :
var user User
err := db.Get(&user, "SELECT id, name FROM users WHERE id=$1", r.URL.Query().Get("id"))
if err != nil {
return nil, err
}
Pour aller plus loin
1. Utilisation de frameworks web sécurisés comme Gin ou Echo
2. Sécurité des sessions et cookies
- Utilisation de cookies HttpOnly et Secure
- Gestion des sessions avec des clés secrètes robustes
3. Logging et monitoring
- Utilisation de loggers spécialisés comme Zap ou Sugared Logger
- Configurer des alertes et des métriques pour surveiller les activités de sécurité
Défi pratique
Créez un gestionnaire de mots de passe avec des fonctionnalités suivantes :
- Inscription d'utilisateurs avec mot de passe haché
- Connexion sécurisée avec JWT
- Changement de mot de passe
- Protection contre l'injection SQL