12 KiB
Authentication & Authorization Implementation Summary
What Was Implemented
This document summarizes the Phase 1 authentication and authorization implementation for the Notifier service.
New Files & Modules Created
Core Authentication Package (internal/auth/)
-
auth.go - Core API key management
APIKeyStore: In-memory storage and validation of API keysAPIKey: Key metadata (client_id, roles, rate_limit, expiration, etc.)RateLimiter: Per-key rate limiting with sliding windowAuthContext: Request context for authenticated calls- Key generation, validation, deactivation, and introspection
-
rest_middleware.go - REST API authentication
RESTAuthMiddleware: Middleware for HTTP requests- Supports
Authorization: Bearer <key>andX-API-Key: <key>headers - Rate limit checking
- Automatic audit logging
-
grpc_middleware.go - gRPC authentication
GRPCAuthMiddleware: Unary and stream interceptors- Extracts API key from gRPC metadata
- Rate limit enforcement
- Audit logging for all auth events
-
authz.go - Role-based access control
NotifierAuthz: Authorization rule management- Per-notifier type/account role restrictions
- Flexible RBAC: empty allowed_roles = any authenticated user
- Built-in role checking
Configuration Updates
-
internal/config/config.go
- Added
AuthConfigstruct with:enabled: Toggle auth on/off (default: false)default_rate_limit: Default rate limit for new keys (100 req/min)
- Added
-
Notifier Config Structs - Added role support to all notifiers:
SMTPConfig.AllowedRolesSlackConfig.AllowedRolesNtfyConfig.AllowedRoles- Each notifier can now restrict which roles can use it
Integration Points
-
api/rest/router.go
- New
NewRouterWithAuth()function - Backward compatible:
NewRouter()still works without auth - Auth middleware applied to all
/api/v1/*routes except/health
- New
-
cmd/server/main.go
- Auth initialization on startup (if enabled)
- Authorization rules registration from config
- Pass auth store to both gRPC and REST servers
- Graceful handling when auth is disabled
Key Features
1. API Key Management
// Create API keys with:
store := auth.NewAPIKeyStore()
key, _ := store.CreateKey(
"client-id",
[]string{"role1", "role2"},
100, // rate limit: 100 req/min
&expirationTime, // optional expiration
)
// Validate keys
key, err := store.ValidateKey(apiKeyString)
// Check rate limits
allowed, _ := store.CheckRateLimit(apiKeyString)
// Manage keys
store.UpdateLastUsed(apiKeyString)
store.DeactivateKey(apiKeyString)
store.ListKeys(clientID)
2. Role-Based Access Control
# In config.yaml
notifiers:
smtp:
default:
...config...
allowed_roles:
- "notify-email"
- "admin"
slack:
default:
...config...
allowed_roles:
- "notify-slack"
- "notify-all"
If allowed_roles is empty, any authenticated user can use the notifier.
3. Rate Limiting
- Per-key rate limiting with sliding window
- Configurable on per-key basis (0 = unlimited)
- Automatically enforced at middleware level
- Returns
429 Too Many Requestswhen exceeded - Resets every 60 seconds
4. Audit Logging
All auth events are logged:
{
"timestamp": "2025-10-25T10:30:00Z",
"event": "auth_success",
"client_id": "billing-service",
"method": "SendNotification",
"remote_addr": "192.168.1.100"
}
API Usage
REST API
# Request
curl -X POST http://localhost:8080/api/v1/notifications \
-H "Authorization: Bearer nk_abc123..." \
-H "Content-Type: application/json" \
-d '{ "type": "email", ... }'
# Or use X-API-Key header
curl -X POST http://localhost:8080/api/v1/notifications \
-H "X-API-Key: nk_abc123..." \
-d '{ ... }'
# Error responses
401 Unauthorized # Missing/invalid key
403 Forbidden # Role not allowed
429 Too Many Requests # Rate limit exceeded
401 Unauthorized (API key expired) # Key expiration check
gRPC API
import "google.golang.org/grpc/metadata"
md := metadata.New(map[string][]string{
"authorization": {"bearer nk_abc123..."},
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
client.SendNotification(ctx, &pb.SendNotificationRequest{...})
// Error codes
codes.Unauthenticated # Missing/invalid key
codes.ResourceExhausted # Rate limit exceeded
codes.PermissionDenied # Role not allowed
Configuration Examples
Minimal (Auth Disabled - Default)
auth:
enabled: false # Default behavior, no auth enforced
notifiers:
smtp:
default:
host: smtp.example.com
...
Basic (Auth Enabled, No Role Restrictions)
auth:
enabled: true
default_rate_limit: 100
notifiers:
smtp:
default:
host: smtp.example.com
...
allowed_roles: [] # All authenticated users
slack:
default:
webhook_url: ...
allowed_roles: [] # All authenticated users
Advanced (Multi-Tenant with Role Restrictions)
auth:
enabled: true
default_rate_limit: 100
notifiers:
smtp:
default:
host: smtp.example.com
...
allowed_roles: ["notify-email", "admin"]
tenant-a:
host: smtp-tenant-a.com
...
allowed_roles: ["tenant-a-admin"]
tenant-b:
host: smtp-tenant-b.com
...
allowed_roles: ["tenant-b-admin"]
slack:
default:
webhook_url: ...
allowed_roles: ["notify-all"]
Backward Compatibility
- Auth is disabled by default - existing deployments continue to work unchanged
NewRouter()function still works without auth- All changes are additive - no existing APIs were modified
- Notifier configs are backward compatible (allowed_roles is optional)
Security Properties
What's Protected
- ✅ API endpoint access (all
/api/v1/*routes) - ✅ gRPC service calls
- ✅ Rate limit enforcement per key
- ✅ Role-based notifier access
- ✅ Expiration checking
- ✅ Deactivation support
- ✅ Audit logging
What's Not Protected (Phase 1)
- ❌ Health check endpoint (
/health) - intentionally open - ❌ Key creation/management endpoints - requires external management
- ❌ Admin operations - not implemented in Phase 1
- ❌ Key rotation - manual implementation required
Credential Security
- API keys are cryptographically random (32 bytes = 64 hex chars)
- Recommended: store in environment variables or secrets manager
- Not stored in plaintext in config files
- Supports key expiration and deactivation
- Per-key audit trail available via logs
Testing the Implementation
1. Enable Auth in Config
auth:
enabled: true
default_rate_limit: 100
notifiers:
stdout: true
2. Create an API Key
store := auth.NewAPIKeyStore()
key, _ := store.CreateKey("test-client", []string{"notify-all"}, 100, nil)
fmt.Println(key.Key)
3. Test REST API
# With auth
curl -H "Authorization: Bearer nk_<your-key>" \
http://localhost:8080/api/v1/notifications
# Without auth (should fail)
curl http://localhost:8080/api/v1/notifications
# 401 Unauthorized
# Invalid key (should fail)
curl -H "Authorization: Bearer invalid" \
http://localhost:8080/api/v1/notifications
# 401 Unauthorized
4. Test gRPC
grpcurl -plaintext \
-H "authorization: bearer nk_<your-key>" \
localhost:50051 notifier.v1.NotifierService/HealthCheck
# Should return 200 OK if key is valid
Next Steps (Phase 2+)
Recommended future enhancements:
- JWT Tokens - Replace API keys with short-lived JWTs
- Key Rotation - Automatic key rotation mechanism
- OAuth2 Integration - Support OAuth2 for client credentials flow
- Admin API - Key creation/management via API endpoints
- Vault Integration - Direct HashiCorp Vault integration
- Metrics - Prometheus metrics for auth events
- mTLS - Mutual TLS authentication for gRPC
- Scopes - Fine-grained permission scopes
- WebAuthn - Hardware key support
- Audit Webhooks - Send auth events to external systems
File Structure
notifier/
├── internal/
│ ├── auth/
│ │ ├── auth.go # Core API key management
│ │ ├── rest_middleware.go # REST authentication
│ │ ├── grpc_middleware.go # gRPC authentication
│ │ └── authz.go # Authorization rules
│ ├── config/
│ │ └── config.go # Updated with AuthConfig
│ └── notifier/
│ ├── smtp.go # Updated with allowed_roles
│ ├── slack.go # Updated with allowed_roles
│ └── ntfy.go # Updated with allowed_roles
├── api/
│ └── rest/
│ └── router.go # Updated with auth support
├── cmd/
│ └── server/
│ └── main.go # Updated with auth initialization
└── docs/
├── AUTH.md # Comprehensive auth documentation
├── AUTH_QUICK_START.md # Quick start guide
├── CLIENT_RECOMMENDATIONS.md # Best practices for client developers
└── IMPLEMENTATION_SUMMARY.md # This file
Code Statistics
- New files: 4 (auth package)
- Modified files: 5 (config, routers, main, notifier configs)
- Lines added: ~700 (auth implementation)
- Lines added: ~300 (documentation)
- Build status: ✅ Compiles successfully
- Backward compatible: ✅ Yes (auth disabled by default)
Known Limitations
-
In-Memory Storage - API keys are lost on restart
- Workaround: Re-create keys on startup or implement persistence
-
No Key Management API - Keys must be created programmatically
- Phase 2: Implement admin API for key management
-
No Token Revocation - Only deactivation available
- Keys can be deactivated but not selectively revoked
-
Basic Rate Limiting - Simple sliding window, not distributed
- Not suitable for multi-instance deployments
- Workaround: Use single instance or implement Redis-backed rate limiter
-
No Metrics Export - Auth events only logged, not exported
- Phase 2: Add Prometheus metrics
Support & Maintenance
Troubleshooting
-
Auth not working?
- Check
auth.enabled: truein config - Verify API key format:
nk_<32-hex> - Check role names match notifier
allowed_roles
- Check
-
Rate limit errors?
- Increase
default_rate_limitin config - Create new key with higher rate limit
- Wait 60 seconds for window to reset
- Increase
-
Key expired?
- Check
key.ExpiresAttimestamp - Create new key with
expiresInparameter or nil
- Check
References
- Authentication Package:
internal/auth/ - REST Router:
api/rest/router.go:NewRouterWithAuth() - gRPC Server:
cmd/server/main.go:startGRPCServer() - Full Documentation:
docs/AUTH.md - Quick Start:
docs/AUTH_QUICK_START.md - Client Guide:
docs/CLIENT_RECOMMENDATIONS.md
Summary
Phase 1 provides a solid foundation for authentication and authorization in the Notifier service:
✅ Simple API Key Authentication - Easy to implement and use ✅ Rate Limiting - Prevent abuse ✅ Role-Based Access - Fine-grained control ✅ Audit Logging - Security visibility ✅ Backward Compatible - Auth is optional ✅ Well Documented - Comprehensive guides for users and developers
The implementation is production-ready for single-instance deployments and can be extended to support more advanced scenarios in future phases.