Menjalankan Redis secara internal—baik sebagai subproses langsung, embedded engine, maupun container sidecar lokal—sering dipilih untuk memangkas latensi I/O pada penyimpanan session dan token bucket rate limit. Namun, konfigurasi default Redis dirancang untuk lingkungan tepercaya. Menjalankan engine ini di localhost tanpa pengerasan (hardening) membuka risiko eksekusi kode arbitrer via script Lua, kebocoran data antar-proses, hingga degradasi sistem akibat kehabisan alokasi memori (OOM).

Vektor Serangan pada Runtime Redis Lokal

Asumsi bahwa layanan internal di 127.0.0.1 aman secara implisit adalah kesalahan fatal. Terdapat tiga celah utama yang sering tereksploitasi:

  • Akses Soket Terbuka: Proses lokal lain dengan hak akses rendah pada sistem operasi yang sama dapat mengirim payload jika port TCP loopback dibuka tanpa autentikasi.
  • Eksploitasi Scripting Arbitrer: Fitur EVAL, SCRIPT, atau fungsi debugging dapat digunakan untuk mengonsumsi CPU secara penuh (infinite loop) atau mengekstrak memori sistem jika sandbox Lua ditembus.
  • Memory Exhaustion DoS: Tanpa batas memori absolut dan kebijakan penggusuran (eviction policy), alokasi key session yang melonjak dapat memicu Linux OOM Killer mematikan proses utama aplikasi.

Konfigurasi Hardening: redis-embedded.conf

Matikan seluruh listening TCP port. Gunakan Unix Domain Socket (UDS) dengan izin akses file sistem ketat (0770 atau 0700), batasi konsumsi memori, dan hapus perintah-perintah berbahaya dari engine Redis runtime.

# Nonaktifkan TCP port sepenuhnya
port 0
tls-port 0

# Jalankan komunikasi eksklusif via Unix Domain Socket
unixsocket /var/run/app-redis/redis.sock
unixsocketperm 700

# Isolasi alokasi RAM
maxmemory 128mb
maxmemory-policy allkeys-lru

# Matikan snapshotting persistensi disk yang tidak dibutuhkan untuk session sementara
save ""
appendonly no

# Nonaktifkan command berbahaya dan debugging
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG ""
rename-command SHUTDOWN ""
rename-command DEBUG ""
rename-command MODULE ""
rename-command SLAVEOF ""
rename-command REPLICAOF ""

# Batasi evaluasi script Lua jika tidak digunakan oleh rate limiter
rename-command EVAL ""
rename-command EVALSHA ""
rename-command SCRIPT ""

Isolasi Izin Akses File Socket

Unix Domain Socket bergantung langsung pada permissions sistem berkas Linux. Buat direktori soket yang hanya dapat dibaca dan ditulisi oleh grup atau user aplikasi.

# Buat direktori runtime soket
sudo mkdir -p /var/run/app-redis

# Set kepemilikan khusus user aplikasi (contoh: apprunner)
sudo chown -R apprunner:apprunner /var/run/app-redis
sudo chmod 700 /var/run/app-redis

Implementasi Client Runtime Aman (Go)

Berikut implementasi inisialisasi koneksi pada runtime Go menggunakan library go-redis. Kode ini mengabaikan network stack TCP, berkomunikasi via Unix socket terproteksi, serta mengonfigurasi batas timeout ketat untuk mencegah pool exhaustion.

package main

import (
	"context"
	"log"
	"net"
	"time"

	"github.com/redis/go-redis/v9"
)

func NewEmbeddedRedisClient(socketPath string) *redis.Client {
	rdb := redis.NewClient(&redis.Options{
		// Tentukan network unix secara eksplisit
		Network: "unix",
		Addr:    socketPath,

		// Batasi koneksi pool agar tidak membebani memori runtime
		PoolSize:        10,
		MinIdleConns:    2,
		DialTimeout:     2 * time.Second,
		ReadTimeout:     1 * time.Second,
		WriteTimeout:    1 * time.Second,
		PoolTimeout:     3 * time.Second,
	})

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	if err := rdb.Ping(ctx).Err(); err != nil {
		log.Fatalf("Gagal terhubung ke embedded Redis socket: %v", err)
	}

	return rdb
}

func main() {
	socketPath := "/var/run/app-redis/redis.sock"
	client := NewEmbeddedRedisClient(socketPath)
	defer client.Close()

	ctx := context.Background()
	
	// Simpan token rate limit dengan TTL pasti
	err := client.Set(ctx, "rate:user:101", 1, 60*time.Second).Err()
	if err != nil {
		log.Printf("Error menulis session: %v", err)
		return
	}

	log.Println("Operasi Redis via UDS berhasil dieksekusi.")
}

Strategi Mitigasi Memory DoS

Ketika Redis embedded kehabisan alokasi ruang (mencapai maxmemory):

  1. Gunakan Kebijakan allkeys-lru: Untuk session store atau token bucket, hapus key lama secara otomatis (LRU) tanpa melempar runtime error ke aplikasi.
  2. Hindari noeviction: Kebijakan noeviction menyebabkan Redis mengembalikan status OOM command not allowed ketika memori penuh, yang berpotensi memutus alur login pengguna.
  3. Alokasikan Memory Overcommit Linux: Jika Redis dijalankan sebagai fork-process, pastikan sysctl vm.overcommit_memory=1 disetel untuk menghindari alokasi gagal saat runtime memanggil operasi internal.

Trade-off dan Limitasi

Pendekatan Unix Domain Socket memangkas latensi sekitar 20-30% dibanding loopback TCP karena meniadakan overhead routing stack TCP/IP lokal. Namun, metode ini memiliki batasan:

  • Skalabilitas Horizontal Terbatas: Socket hanya dapat diakses di node/server yang sama. Jika aplikasi berekspansi menjadi multi-instance, runtime harus dimigrasikan ke Redis tersentralisasi.
  • Hot-Reloading ACL Terbatas: Penggunaan rename-command memerlukan restart engine jika Anda perlu mengaktifkan kembali perintah tertentu untuk kebutuhan debugging.