Hi, I'm Mohamed Chaker Jouini 👋

Building a Secure IoT Gateway: From University Assignment to Production-Ready System

What started as a university assignment quickly became one of my favorite projects. The brief was simple: build a secure gateway for IoT devices. The timeline was long enough but sometimes constraints breed creativity so i gave myself a way closer deadline than any of my peers, and I managed to complete this in record time while building something I’m genuinely proud of.

This post walks through the entire architecture of SecureIoTGateway - a production-ready security layer that sits between your IoT devices and backend server, handling authentication, message validation, and attack prevention. If you’ve ever wondered how to actually secure IoT communications beyond “just use HTTPS,” this is for you.

Kubernetes Installation on Debian 13 (VM)

Welcome to this hands-on guide for installing Kubernetes on Debian 13! If you’re new to Kubernetes or just want to set up a development cluster to experiment with, you’re in the right place. This tutorial will walk you through every step with clear explanations, so you’ll understand not just what you’re doing, but why you’re doing it.

What You’ll Build

By the end of this guide, you’ll have a fully functional single-node Kubernetes cluster running on your Debian 13 virtual machine. This setup is perfect for learning, testing applications, or developing containerized workloads locally. While production clusters typically have multiple nodes (separate control plane and worker machines), a single-node setup is ideal for getting started.

Integration Guide: Adding Rate Limiting to Your Go Web Application

This guide walks you through integrating the go-ratelimit library into real-world Go web applications, from basic setup to production deployment.

Quick Integration

Step 1: Install the Library

go get github.com/Jouini-Mohamed-Chaker/go-ratelimit

Step 2: Basic HTTP Server Integration

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/Jouini-Mohamed-Chaker/go-ratelimit"
)

func main() {
    // Create rate limiter: 1000 requests per hour with 50 burst capacity
    limiter := ratelimit.NewWithConfig(ratelimit.Config{
        Limit:     1000,
        Window:    time.Hour,
        BurstSize: 50,
        SkipPaths: []string{"/health", "/metrics"}, // Health checks bypass rate limiting
    })

    // Your existing handlers
    http.HandleFunc("/api/users", handleUsers)
    http.HandleFunc("/api/posts", handlePosts)
    http.HandleFunc("/health", handleHealth)

    // Apply rate limiting to all routes
    server := &http.Server{
        Addr:    ":8080",
        Handler: limiter.Wrap(http.DefaultServeMux),
    }

    log.Println("Server starting on :8080 with rate limiting (1000 req/hour)")
    log.Fatal(server.ListenAndServe())
}

func handleUsers(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    fmt.Fprintf(w, `{"users": ["alice", "bob", "charlie"]}`)
}

func handlePosts(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    fmt.Fprintf(w, `{"posts": [{"id": 1, "title": "Hello World"}]}`)
}

func handleHealth(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
}

Framework-Specific Integration

Gorilla Mux

package main

import (
    "encoding/json"
    "net/http"
    "time"

    "github.com/gorilla/mux"
    "github.com/Jouini-Mohamed-Chaker/go-ratelimit"
)

func main() {
    // Create rate limiter with different limits for different endpoints
    apiLimiter := ratelimit.NewWithConfig(ratelimit.Config{
        Limit:      100,              // 100 requests per minute for API
        Window:     time.Minute,
        BurstSize:  10,
        TrustedIPs: []string{"127.0.0.1"}, // Local development
    })

    authLimiter := ratelimit.NewWithConfig(ratelimit.Config{
        Limit:  5,               // Strict limit for auth endpoints
        Window: time.Minute,
        BurstSize: 2,
    })

    r := mux.NewRouter()

    // API routes with standard rate limiting
    apiRouter := r.PathPrefix("/api").Subrouter()
    apiRouter.Use(func(next http.Handler) http.Handler {
        return apiLimiter.Wrap(next)
    })
    apiRouter.HandleFunc("/users", getUsers).Methods("GET")
    apiRouter.HandleFunc("/posts", getPosts).Methods("GET")

    // Auth routes with stricter rate limiting
    authRouter := r.PathPrefix("/auth").Subrouter()
    authRouter.Use(func(next http.Handler) http.Handler {
        return authLimiter.Wrap(next)
    })
    authRouter.HandleFunc("/login", handleLogin).Methods("POST")
    authRouter.HandleFunc("/register", handleRegister).Methods("POST")

    // Public routes without rate limiting
    r.HandleFunc("/health", handleHealth).Methods("GET")
    r.HandleFunc("/", handleHome).Methods("GET")

    http.ListenAndServe(":8080", r)
}

func getUsers(w http.ResponseWriter, r *http.Request) {
    users := []map[string]interface{}{
        {"id": 1, "name": "Alice"},
        {"id": 2, "name": "Bob"},
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]interface{}{"users": users})
}

func getPosts(w http.ResponseWriter, r *http.Request) {
    posts := []map[string]interface{}{
        {"id": 1, "title": "First Post", "author": "Alice"},
        {"id": 2, "title": "Second Post", "author": "Bob"},
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]interface{}{"posts": posts})
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
    // Simulate login logic
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{
        "message": "Login successful",
        "token":   "fake-jwt-token",
    })
}

func handleRegister(w http.ResponseWriter, r *http.Request) {
    // Simulate registration logic
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{
        "message": "Registration successful",
    })
}

func handleHealth(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
}

func handleHome(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/html")
    w.Write([]byte(`<h1>Welcome to My API</h1><p>Rate limiting is active!</p>`))
}

Gin Framework

package main

import (
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/Jouini-Mohamed-Chaker/go-ratelimit"
)

// Gin middleware adapter
func RateLimitMiddleware(limiter *ratelimit.Limiter) gin.HandlerFunc {
    return func(c *gin.Context) {
        // Create a wrapper to make Gin's context compatible
        wrapper := &ginResponseWriter{ResponseWriter: c.Writer, context: c}
        
        if limiter.Wrap(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            c.Next()
        })).ServeHTTP(wrapper, c.Request); wrapper.written {
            c.Abort()
            return
        }
    }
}

type ginResponseWriter struct {
    gin.ResponseWriter
    context *gin.Context
    written bool
}

func (w *ginResponseWriter) Write(data []byte) (int, error) {
    w.written = true
    return w.ResponseWriter.Write(data)
}

func (w *ginResponseWriter) WriteHeader(statusCode int) {
    if statusCode != http.StatusOK {
        w.written = true
    }
    w.ResponseWriter.WriteHeader(statusCode)
}

func main() {
    r := gin.Default()

    // Create rate limiters for different route groups
    apiLimiter := ratelimit.NewWithConfig(ratelimit.Config{
        Limit:     200,
        Window:    time.Minute,
        BurstSize: 20,
    })

    authLimiter := ratelimit.NewWithConfig(ratelimit.Config{
        Limit:  10,
        Window: time.Minute,
        BurstSize: 3,
    })

    // API routes with rate limiting
    api := r.Group("/api")
    api.Use(RateLimitMiddleware(apiLimiter))
    {
        api.GET("/users", getUsers)
        api.GET("/posts", getPosts)
        api.POST("/posts", createPost)
    }

    // Auth routes with stricter limiting
    auth := r.Group("/auth")
    auth.Use(RateLimitMiddleware(authLimiter))
    {
        auth.POST("/login", login)
        auth.POST("/register", register)
        auth.POST("/forgot-password", forgotPassword)
    }

    // Public routes (no rate limiting)
    r.GET("/health", func(c *gin.Context) {
        c.JSON(200, gin.H{"status": "ok"})
    })

    r.GET("/", func(c *gin.Context) {
        c.HTML(200, "index.html", gin.H{
            "title": "My API with Rate Limiting",
        })
    })

    r.Run(":8080")
}

func getUsers(c *gin.Context) {
    c.JSON(200, gin.H{
        "users": []gin.H{
            {"id": 1, "name": "Alice"},
            {"id": 2, "name": "Bob"},
        },
    })
}

func getPosts(c *gin.Context) {
    c.JSON(200, gin.H{
        "posts": []gin.H{
            {"id": 1, "title": "First Post"},
            {"id": 2, "title": "Second Post"},
        },
    })
}

func createPost(c *gin.Context) {
    c.JSON(201, gin.H{
        "message": "Post created",
        "id":      123,
    })
}

func login(c *gin.Context) {
    c.JSON(200, gin.H{
        "message": "Login successful",
        "token":   "fake-jwt-token",
    })
}

func register(c *gin.Context) {
    c.JSON(201, gin.H{
        "message": "Registration successful",
    })
}

func forgotPassword(c *gin.Context) {
    c.JSON(200, gin.H{
        "message": "Password reset email sent",
    })
}

Echo Framework

package main

import (
    "net/http"
    "time"

    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
    "github.com/Jouini-Mohamed-Chaker/go-ratelimit"
)

// Echo middleware adapter
func RateLimitMiddleware(limiter *ratelimit.Limiter) echo.MiddlewareFunc {
    return func(next echo.HandlerFunc) echo.HandlerFunc {
        return func(c echo.Context) error {
            req := c.Request()
            res := c.Response().Writer

            // Check if request should be rate limited
            handler := limiter.Wrap(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                // Continue to next handler
                if err := next(c); err != nil {
                    c.Error(err)
                }
            }))

            handler.ServeHTTP(res, req)
            return nil
        }
    }
}

func main() {
    e := echo.New()

    // Middleware
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    // Rate limiters
    apiLimiter := ratelimit.NewWithConfig(ratelimit.Config{
        Limit:     500,
        Window:    time.Hour,
        BurstSize: 50,
        SkipPaths: []string{"/health", "/metrics"},
    })

    strictLimiter := ratelimit.NewWithConfig(ratelimit.Config{
        Limit:  15,
        Window: time.Minute,
        BurstSize: 5,
    })

    // API group with rate limiting
    api := e.Group("/api", RateLimitMiddleware(apiLimiter))
    api.GET("/users", getUsers)
    api.GET("/posts", getPosts)
    api.POST("/posts", createPost)

    // Auth group with strict rate limiting
    auth := e.Group("/auth", RateLimitMiddleware(strictLimiter))
    auth.POST("/login", login)
    auth.POST("/register", register)

    // Public routes
    e.GET("/health", health)
    e.GET("/", home)

    e.Logger.Fatal(e.Start(":8080"))
}

func getUsers(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]interface{}{
        "users": []map[string]interface{}{
            {"id": 1, "name": "Alice"},
            {"id": 2, "name": "Bob"},
        },
    })
}

func getPosts(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]interface{}{
        "posts": []map[string]interface{}{
            {"id": 1, "title": "Hello World"},
        },
    })
}

func createPost(c echo.Context) error {
    return c.JSON(http.StatusCreated, map[string]string{
        "message": "Post created successfully",
    })
}

func login(c echo.Context) error {
    return c.JSON(http.StatusOK, map[string]string{
        "message": "Login successful",
        "token":   "fake-jwt-token",
    })
}

func register(c echo.Context) error {
    return c.JSON(http.StatusCreated, map[string]string{
        "message": "Registration successful",
    })
}

func health(c echo.Context) error {
    return c.String(http.StatusOK, "OK")
}

func home(c echo.Context) error {
    return c.HTML(http.StatusOK, "<h1>Welcome to My API</h1><p>Rate limiting active!</p>")
}

Production Configuration

Environment-Based Configuration

package main

import (
    "log"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"

    "github.com/Jouini-Mohamed-Chaker/go-ratelimit"
)

type AppConfig struct {
    Port            string
    RateLimit       int
    RateWindow      time.Duration
    BurstSize       int
    TrustedIPs      []string
    SkipPaths       []string
    EnableRateLimit bool
}

func loadConfig() *AppConfig {
    config := &AppConfig{
        Port:            getEnv("PORT", "8080"),
        RateLimit:       getEnvInt("RATE_LIMIT", 1000),
        RateWindow:      getEnvDuration("RATE_WINDOW", "1h"),
        BurstSize:       getEnvInt("BURST_SIZE", 100),
        TrustedIPs:      getEnvSlice("TRUSTED_IPS", []string{"127.0.0.1"}),
        SkipPaths:       getEnvSlice("SKIP_PATHS", []string{"/health", "/metrics"}),
        EnableRateLimit: getEnvBool("ENABLE_RATE_LIMIT", true),
    }
    return config
}

func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

func getEnvInt(key string, defaultValue int) int {
    if value := os.Getenv(key); value != "" {
        if intValue, err := strconv.Atoi(value); err == nil {
            return intValue
        }
    }
    return defaultValue
}

func getEnvDuration(key string, defaultValue string) time.Duration {
    value := getEnv(key, defaultValue)
    if duration, err := time.ParseDuration(value); err == nil {
        return duration
    }
    // Fallback parsing
    if duration, err := time.ParseDuration(defaultValue); err == nil {
        return duration
    }
    return time.Hour
}

func getEnvBool(key string, defaultValue bool) bool {
    if value := os.Getenv(key); value != "" {
        if boolValue, err := strconv.ParseBool(value); err == nil {
            return boolValue
        }
    }
    return defaultValue
}

func getEnvSlice(key string, defaultValue []string) []string {
    if value := os.Getenv(key); value != "" {
        return strings.Split(value, ",")
    }
    return defaultValue
}

func main() {
    config := loadConfig()

    var handler http.Handler = http.DefaultServeMux

    // Setup your routes
    http.HandleFunc("/api/users", handleAPI)
    http.HandleFunc("/api/posts", handleAPI)
    http.HandleFunc("/health", handleHealth)

    // Apply rate limiting if enabled
    if config.EnableRateLimit {
        limiter := ratelimit.NewWithConfig(ratelimit.Config{
            Limit:      config.RateLimit,
            Window:     config.RateWindow,
            BurstSize:  config.BurstSize,
            SkipPaths:  config.SkipPaths,
            TrustedIPs: config.TrustedIPs,
        })

        // Custom rate limit handler for better API responses
        limiter.OnLimitReached = func(w http.ResponseWriter, r *http.Request) {
            w.Header().Set("Content-Type", "application/json")
            w.Header().Set("Retry-After", "60")
            w.WriteHeader(http.StatusTooManyRequests)
            w.Write([]byte(`{
                "error": "rate_limit_exceeded",
                "message": "Too many requests. Please slow down.",
                "retry_after": 60
            }`))
        }

        handler = limiter.Wrap(handler)
        log.Printf("Rate limiting enabled: %d requests per %v (burst: %d)", 
            config.RateLimit, config.RateWindow, config.BurstSize)
    } else {
        log.Println("Rate limiting disabled")
    }

    log.Printf("Server starting on port %s", config.Port)
    log.Fatal(http.ListenAndServe(":"+config.Port, handler))
}

func handleAPI(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.Write([]byte(`{"message": "API response", "path": "` + r.URL.Path + `"}`))
}

func handleHealth(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
}

Docker Configuration

Create a .env file:

Archlinux Installation (VM)

Goal: a minimal working Arch Linux VM, every command shown and briefly explained.

This is a hands-on walkthrough, not the Arch Wiki. I’ll show each command in the exact order to run and explain what it does. For deep dives, refer to the Arch Wiki.

I’m writing this from Debian 13 and using GNOME Boxes, but the steps work with VirtualBox, VMware, or any other hypervisor.

Prerequisites

  • Comfortable using a terminal (run, copy/paste commands).