โโโ โโโ โโโโโโ โโโโโโโ โโโโโโโ โโโโโโโโโโโโ โโโ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโ โโโ โโ โโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โโโโโโ โโโโโโโโ โโโ โโโโโโ โโโโโโโโโโ โโโโโโโโโโโ โโโโโ
Production-grade containerized CLI authentication system
Warden Auth CLI is a secure, containerized command-line authentication system built entirely in Go. It implements user registration, password-based authentication, optional TOTP two-factor authentication, and persistent session management โ all running inside Docker with SQLite for storage.
This project was built to demonstrate production-grade backend engineering practices including clean architecture, layered separation of concerns, security-first design, and professional DevOps packaging.
| Feature | Implementation |
|---|---|
| Password Hashing | Argon2id โ OWASP 2023 recommended, memory-hard algorithm |
| Session Security | SHA256-hashed tokens โ raw token never stored on disk |
| Account Protection | Progressive lockout โ escalating delays before hard lock |
| Two-Factor Auth | TOTP โ Google Authenticator and Authy compatible |
| Audit Trail | Structured JSON logs โ every security event recorded |
| Container Security | Distroless image โ zero shell, zero OS attack surface |
| Architecture | 5-layer clean architecture โ fully decoupled and testable |
| Data Persistence | SQLite with WAL mode โ survives container restarts |
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Docker Container โ
โ gcr.io/distroless/static โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Warden Auth CLI โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ CLI โโโโถโ Service โโโโถโ Repository โ โ โ
โ โ โ Layer โ โ Layer โ โ Layer โ โ โ
โ โ โ โ โ โ โ โ โ โ
โ โ โ readline โ โ Argon2id โ โ SQLite + WAL โ โ โ
โ โ โ pterm โ โ TOTP โ โ Migrations โ โ โ
โ โ โ display โ โ Sessions โ โ Audit Logs โ โ โ
โ โ โโโโโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ โ โ โ
โ โ โผ โผ โผ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ Domain Layer โ โ โ
โ โ โ Models โข Interfaces โข Sentinel Errors โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโ โ
โ โผ โผ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โ db_data vol โ โ log_data vol โ โ
โ โ warden.db โ โ warden.log โ โ
โ โ (SQLite) โ โ (JSON audit) โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
| Layer | Package | Rule |
|---|---|---|
| Domain | internal/domain |
Zero external imports. Defines models, interfaces, errors. |
| Repository | internal/repository/sqlite |
All SQL lives here. Maps DB errors to domain errors. |
| Service | internal/service |
Business logic only. No SQL, no CLI, no display code. |
| CLI | internal/cli |
Input, routing, display only. No business logic. |
| Config | internal/config |
ENV loading with defaults. Single Config struct. |
| Logger | internal/logger |
Dual slog output โ JSON to file, text to stdout. |
Requirements: Docker and Docker Compose installed.
git clone https://github.com/AdeshDeshmukh/warden-auth-cli.git
cd warden-auth-cli
cp .env.example .env
docker-compose run --rm wardenThat is it. The application starts, migrations run automatically, and you are at the prompt.
| Command | Description |
|---|---|
register |
Create a new user account with password validation |
login |
Authenticate โ prompts for TOTP code if 2FA is enabled |
help |
Display all available commands |
exit |
Quit the application cleanly |
| Command | Description |
|---|---|
whoami |
Display full profile โ username, registration date, 2FA status, session expiry, last login |
enable-2fa |
Generate TOTP secret, display setup key, verify before saving |
disable-2fa |
Verify current TOTP code then remove 2FA from account |
logout |
Invalidate session token and return to login prompt |
help |
Display all available commands |
warden โฏ register
Username: adesh
Password: โขโขโขโขโขโขโขโข
Confirm Password: โขโขโขโขโขโขโขโข
โ
Account created successfully. You can now login.
warden โฏ login
Username: adesh
Password: โขโขโขโขโขโขโขโข
โ
Welcome back, adesh!
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Field โ Value โ
โโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Username โ adesh โ
โ Registered โ 2025-06-18 โ
โ Last Login โ Just now โ
โ 2FA Status โ Disabled โ
โ Session Expires โ in 30m 0s โ
โ Account Status โ Active โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
warden โฏ login
Username: adesh
Password: โขโขโขโขโขโขโขโข
2FA Code: โขโขโขโขโขโข
โ
Welcome back, adesh!
warden [adesh] โฏ enable-2fa
โน๏ธ Scan the QR code below with Google Authenticator or Authy:
โน๏ธ otpauth URL: otpauth://totp/Warden%20Auth:adesh?...
โน๏ธ Manual entry key: JBSWY3DPEHPK3PXP
Enter the 6-digit code from your authenticator app to confirm: โขโขโขโขโขโข
โ
2FA enabled successfully. Your account is now protected.
warden โฏ login
Username: adesh
Password: โขโขโขโขโขโขโขโข
โ Invalid credentials. Please try again.
warden โฏ login
Username: adesh
Password: โขโขโขโขโขโขโขโข
โ Invalid credentials. Please try again.
[after 5 failed attempts]
โ Too many failed attempts.
โ ๏ธ Account locked. Try again in 15m 0s.
Copy .env.example to .env and modify as needed. All values below are defaults.
| Variable | Default | Description |
|---|---|---|
DB_PATH |
./data/warden.db |
SQLite database file location |
LOG_PATH |
./logs/warden.log |
JSON audit log file location |
SESSION_TIMEOUT |
30m |
Session duration before automatic expiry |
MAX_FAILED_ATTEMPTS |
5 |
Failed login threshold before account lockout |
LOCKOUT_DURATION |
15m |
Duration of account lockout after threshold reached |
ARGON_MEMORY |
65536 |
Argon2id memory parameter in KB (64MB) |
ARGON_ITERATIONS |
3 |
Argon2id iteration count |
ARGON_PARALLELISM |
2 |
Argon2id parallel thread count |
bcrypt is CPU-hard only. Argon2id is both CPU-hard and memory-hard. The memory requirement (64MB per attempt) defeats GPU-based brute force attacks because GPU cores share limited memory bandwidth โ thousands of parallel attempts become impossible. OWASP has recommended Argon2id as the preferred password hashing algorithm since 2023. The parameters used here meet OWASP minimum requirements.
Storing raw session tokens in a database is equivalent to storing plaintext passwords. If the database file is ever compromised, an attacker would immediately control all active sessions. By storing SHA256(rawToken) instead, the raw token exists only in the running process memory and is never written to persistent storage. SHA256 is a one-way function โ the stored hash cannot be reversed.
A hard cutoff at N attempts is easy to detect and probe during an attack. Progressive delays (2 second artificial sleep from the third failed attempt onward) make brute force attempts slow and painful before the hard lockout triggers at 5 attempts. The exact countdown timer shown on lockout informs legitimate users without leaking timing internals to attackers.
When a new login succeeds, all existing sessions for that user are deleted before the new session is created. This prevents session fixation attacks where an attacker plants a known session identifier and waits for the victim to authenticate with it. It also prevents stale sessions from accumulating indefinitely.
Standard base images (ubuntu, alpine) contain shells, package managers, and hundreds of OS-level binaries. Each is a potential attack vector. The gcr.io/distroless/static-debian12 image contains only the compiled binary and minimal runtime. There is no /bin/sh โ shell injection is structurally impossible. There is no package manager โ supply chain attacks via OS packages cannot occur.
Using modernc.org/sqlite instead of the CGo-based mattn/go-sqlite3 means CGO_ENABLED=0 during build. This produces a fully static binary with zero shared library dependencies, which is what makes the distroless final image possible. A CGo build would require glibc in the final image, forcing a larger and less secure base.
When a username does not exist, the login handler still runs the full Argon2id computation on a dummy value before returning an error. Without this, an attacker could enumerate valid usernames by measuring response time โ a non-existent user would return faster than an existing one requiring hash verification.
When enabling 2FA, the user must successfully enter a valid six-digit code before the secret is written to the database. This proves the user has correctly scanned the setup key and their authenticator app is generating valid codes. Without this verification step, a user could enable 2FA with a misconfigured app and then be permanently unable to log in.
Every authentication event writes simultaneously to the audit_logs SQLite table and to a structured JSON log file. The JSON format is directly ingestible by SIEM tools such as Splunk, Elastic Stack, and Datadog. The username field is denormalized intentionally โ the audit trail remains intact even if a user account is deleted.
| Column | Type | Description |
|---|---|---|
id |
TEXT | UUID primary key โ prevents enumeration attacks |
username |
TEXT | Unique, 3-32 characters, alphanumeric and underscore |
password_hash |
TEXT | Argon2id encoded hash โ self-contained with salt and params |
totp_secret |
TEXT | TOTP secret key, null when 2FA is disabled |
totp_enabled |
INTEGER | 1 when 2FA is active, 0 otherwise |
failed_attempts |
INTEGER | Failed login counter โ persists across container restarts |
locked_until |
DATETIME | Lockout expiry timestamp, null when account is active |
last_login_at |
DATETIME | Timestamp of most recent successful authentication |
created_at |
DATETIME | Account creation timestamp in UTC |
| Column | Type | Description |
|---|---|---|
id |
TEXT | UUID primary key |
user_id |
TEXT | Foreign key to users.id โ cascades on delete |
token_hash |
TEXT | SHA256 of raw session token โ raw token never stored |
expires_at |
DATETIME | Session expiry โ validated on every command |
created_at |
DATETIME | Session creation timestamp |
| Column | Type | Description |
|---|---|---|
id |
INTEGER | Auto-increment primary key |
user_id |
TEXT | Foreign key to users.id โ set null on delete |
username |
TEXT | Denormalized โ audit trail survives account deletion |
event |
TEXT | Event type constant from predefined set |
detail |
TEXT | JSON metadata string with event-specific context |
created_at |
DATETIME | Event timestamp in UTC |
| Event | Triggered When | Detail Fields |
|---|---|---|
USER_REGISTERED |
New account created | {} |
LOGIN_SUCCESS |
Password verified, session created | {} |
LOGIN_FAILED |
Wrong password or locked account | reason, attempts_remaining |
ACCOUNT_LOCKED |
Failed attempts hit maximum | locked_until |
TOTP_ENABLED |
2FA setup completed and verified | {} |
TOTP_DISABLED |
2FA removed from account | {} |
SESSION_CREATED |
New session token issued | expires_at |
SESSION_EXPIRED |
Expired session detected on validation | {} |
LOGOUT |
User explicitly ended session | {} |
{
"time": "2025-06-18T10:23:01.234Z",
"level": "INFO",
"msg": "security_event",
"event": "LOGIN_SUCCESS",
"username": "adesh",
"detail": {}
}Stage 1 โ builder uses golang:1.25-alpine:
- Downloads all Go module dependencies
- Compiles with
CGO_ENABLED=0for a fully static binary - Strips debug info with
-ldflags="-w -s"for smaller size
Stage 2 โ final uses gcr.io/distroless/static-debian12:
- Contains only the compiled binary
- No shell, no package manager, no OS utilities
- Minimal CVE exposure from base image
| Base Image | Typical Size |
|---|---|
| ubuntu | ~180MB |
| alpine | ~20MB |
| distroless/static | ~15MB |
| Volume | Mount Point | Contents |
|---|---|---|
db_data |
/app/data |
SQLite database file |
log_data |
/app/logs |
Structured JSON audit log files |
Both volumes survive container restarts, rebuilds, and updates. Your data is never lost when stopping or updating the container.
go test -v -race ./tests/...| Test | What It Validates |
|---|---|
TestPasswordStrength_TooShort |
Rejects passwords under 8 characters |
TestPasswordStrength_NoUppercase |
Rejects passwords without uppercase |
TestPasswordStrength_NoLowercase |
Rejects passwords without lowercase |
TestPasswordStrength_NoDigit |
Rejects passwords without a digit |
TestPasswordStrength_CommonPassword |
Rejects passwords on common blocklist |
TestPasswordStrength_ValidPassword |
Accepts a strong valid password |
TestTOTPGenerate_ReturnsSecretAndURL |
TOTP generation returns non-empty values |
TestTOTPGenerate_DifferentSecretsEachTime |
Each call generates a unique secret |
TestTOTPVerify_InvalidCode |
Rejects wrong 6-digit codes |
TestTOTPVerify_EmptyCode |
Rejects empty code input |
warden-auth-cli/
โโโ cmd/
โ โโโ main.go Entry point โ config, DB, wiring, shutdown
โโโ internal/
โ โโโ config/
โ โ โโโ config.go ENV variable loading with typed defaults
โ โโโ domain/
โ โ โโโ user.go User model with IsLocked, LockoutRemaining
โ โ โโโ session.go Session model with IsExpired, TimeRemaining
โ โ โโโ audit.go AuditEvent constants and AuditLog struct
โ โ โโโ errors.go Sentinel errors for all domain failures
โ โ โโโ repository.go Repository interfaces owned by domain layer
โ โโโ migrations/
โ โ โโโ migrations.go go:embed entry point for SQL files
โ โ โโโ sql/
โ โ โโโ 001_users.sql Users table schema
โ โ โโโ 002_sessions.sql Sessions table with token_hash indexes
โ โ โโโ 003_audit_logs.sql Audit log table with event indexes
โ โโโ repository/
โ โ โโโ sqlite/
โ โ โโโ db.go Connection, WAL mode, migration runner
โ โ โโโ user_repo.go UserRepository SQLite implementation
โ โ โโโ session_repo.go SessionRepository SQLite implementation
โ โ โโโ audit_repo.go AuditRepository โ fire and forget
โ โโโ service/
โ โ โโโ auth.go Register, Login, lockout, password strength
โ โ โโโ session.go Create, Validate, Invalidate sessions
โ โ โโโ totp.go Generate, Enable, Disable, Verify TOTP
โ โโโ cli/
โ โ โโโ app.go readline loop, state machine, routing
โ โ โโโ pre_login.go register and login command handlers
โ โ โโโ post_login.go whoami, 2FA, logout command handlers
โ โ โโโ display.go All terminal output โ tables, colors, formatting
โ โโโ logger/
โ โโโ logger.go Dual slog handler โ JSON file and text stdout
โโโ tests/
โ โโโ auth_test.go Password strength and hashing unit tests
โ โโโ totp_test.go TOTP generation and verification unit tests
โโโ migrations/ Original SQL files (also embedded via internal/migrations)
โโโ .env.example All environment variables documented with defaults
โโโ .dockerignore Excludes secrets and build artifacts from image
โโโ .gitignore Excludes .env, binaries, runtime data from git
โโโ Dockerfile Multi-stage distroless build
โโโ docker-compose.yml Service definition with persistent named volumes
โโโ Makefile Developer workflow targets
โโโ LICENSE MIT License
โโโ README.md This document
# Run locally without Docker
make run
# Build binary
make build
./bin/warden
# Run all tests with race detector
make test
# Generate HTML coverage report
make test-cover
# Run linter
make lint
# Clean build artifacts
make clean| Target | Description |
|---|---|
make build |
Compile binary to bin/warden |
make run |
Run locally with go run |
make test |
Run all tests with race detector |
make test-cover |
Generate HTML test coverage report |
make docker-up |
Build and start with docker-compose |
make docker-down |
Stop containers and remove volumes |
make lint |
Run go vet across all packages |
make clean |
Remove build artifacts |
| Requirement | Status | Implementation |
|---|---|---|
| User registration | โ | register command with username and password validation |
| Login with password | โ | login command with Argon2id verification |
| Optional TOTP 2FA | โ | enable-2fa and disable-2fa with Google Authenticator |
| Secure password storage | โ | Argon2id with random salt, OWASP recommended |
| Account lockout | โ | Progressive delays then 15-minute hard lockout |
| Session management | โ | Configurable timeout, DB-backed, validated per command |
| SQLite persistence | โ | WAL mode, named Docker volume, survives restarts |
| Interactive prompt | โ | readline with command history and tab completion |
| Clear error messages | โ | Colored pterm output with descriptive messages |
| Help command | โ | Context-aware โ different commands before and after login |
| whoami command | โ | Full profile table auto-displayed on login |
| Docker container | โ | Multi-stage distroless build |
| README documentation | โ | This document |
| Database schema | โ | Three migration files, run automatically on startup |
| Unit tests | โ | 10 tests covering password strength and TOTP |
- WebAuthn / FIDO2 hardware security key support as a second authentication factor
- IP-based rate limiting to complement per-user account lockout for distributed attacks
- Multi-device session management with the ability to view and revoke individual sessions
- TOTP backup codes generated at 2FA setup for emergency account recovery
- Admin CLI commands to view audit logs, unlock accounts, and list active sessions
- Automated integration tests using an in-memory SQLite database for full flow coverage
- Session refresh to extend expiry on active use without requiring re-login
Adesh Deshmukh
MIT License โ Copyright ยฉ 2025 Adesh Deshmukh
See LICENSE for full terms.