feat: tunnel JWT auth
Tunnel method now requires Bearer JWT (HMAC-HS256) in gRPC metadata.
- internal/auth: pure-stdlib verifier + issuer + revoked-jti list
- internal/tunnel: per-target rate limiter + egress allowlist with
wildcard support + crypto/rand frame padding to {1KB, 4KB, 16KB}
- cmd/server reads /etc/stats-gateway/{jwt.key,tunnel.yaml,revoked.txt}
via --config-dir flag
- cmd/issue-token Go binary wraps auth.Issue
- configs/tunnel.yaml example with projectshitpost wildcard
Tests: 13 PASS across 5 packages.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestVerify_ValidToken(t *testing.T) {
|
||||
v := NewVerifier([]byte("test-secret"), nil)
|
||||
tok, _ := Issue([]byte("test-secret"), "alice", "tunnel", time.Hour)
|
||||
claims, err := v.Verify(tok)
|
||||
if err != nil {
|
||||
t.Fatalf("expected ok, got %v", err)
|
||||
}
|
||||
if claims.Subject != "alice" || claims.Scope != "tunnel" {
|
||||
t.Errorf("claims wrong: %+v", claims)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify_WrongSecret(t *testing.T) {
|
||||
v := NewVerifier([]byte("server-secret"), nil)
|
||||
tok, _ := Issue([]byte("attacker-secret"), "eve", "tunnel", time.Hour)
|
||||
if _, err := v.Verify(tok); err == nil {
|
||||
t.Fatal("expected verification fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify_Expired(t *testing.T) {
|
||||
v := NewVerifier([]byte("test-secret"), nil)
|
||||
tok, _ := Issue([]byte("test-secret"), "alice", "tunnel", -time.Minute)
|
||||
if _, err := v.Verify(tok); err == nil {
|
||||
t.Fatal("expected expired")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify_WrongScope(t *testing.T) {
|
||||
v := NewVerifier([]byte("test-secret"), nil)
|
||||
tok, _ := Issue([]byte("test-secret"), "alice", "read-only", time.Hour)
|
||||
if _, err := v.Verify(tok); err == nil {
|
||||
t.Fatal("expected scope mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify_RevokedJTI(t *testing.T) {
|
||||
revoked := map[string]bool{"abc123": true}
|
||||
v := NewVerifier([]byte("test-secret"), revoked)
|
||||
tok, _ := IssueWithJTI([]byte("test-secret"), "alice", "tunnel", time.Hour, "abc123")
|
||||
if _, err := v.Verify(tok); err == nil {
|
||||
t.Fatal("expected revoked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRevokedFile_Missing(t *testing.T) {
|
||||
m, err := LoadRevokedFile(filepath.Join(t.TempDir(), "nonexistent"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(m) != 0 {
|
||||
t.Errorf("expected empty, got %v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRevokedFile_ParsesEntries(t *testing.T) {
|
||||
tmp := filepath.Join(t.TempDir(), "r.txt")
|
||||
os.WriteFile(tmp, []byte("# comment\nabc\n\nxyz\n"), 0644)
|
||||
m, err := LoadRevokedFile(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !m["abc"] || !m["xyz"] || len(m) != 2 {
|
||||
t.Errorf("got %v", m)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user