Security Best Practices
A comprehensive security guide for the Swit microservices framework, covering the complete security lifecycle from development to deployment.
Table of Contents
- Overview
- Security Development Lifecycle (SDLC)
- Authentication Best Practices
- Authorization Best Practices
- API Security Guidelines
- Common Vulnerability Protection (OWASP Top 10)
- Data Protection
- Transport Layer Security
- Secret Management
- Security Configuration
- Security Monitoring & Auditing
- Container Security
- Dependency Management
- Security Testing
- Related Resources
Overview
The Swit framework provides multi-layered security features, including:
- Authentication - OAuth2/OIDC, JWT, mTLS
- Authorization - OPA Policy Engine (RBAC/ABAC)
- Transport Security - TLS/mTLS encryption
- Data Protection - Encryption, masking, auditing
- Security Monitoring - Metrics, logs, tracing
- Automated Scanning - gosec, Trivy, govulncheck
Security Principles
- Defense in Depth - Multiple layers of security controls
- Least Privilege - Grant only necessary permissions
- Deny by Default - Access requires explicit allowance
- Fail Securely - Default to deny on errors
- Complete Audit - Log all security events
Security Development Lifecycle (SDLC)
1. Planning Phase
Threat Modeling
Identify potential security threats:
# Use threat modeling tools
- Data flow diagram analysis
- STRIDE threat classification
- Attack surface analysisSecurity Requirements
Define security requirements:
- Authentication and authorization requirements
- Data protection requirements
- Compliance requirements (GDPR, PCI-DSS, etc.)
- Audit logging requirements
2. Design Phase
Architecture Security Review
// ✅ Recommended: Use framework security features
config := &server.ServerConfig{
Name: "my-service",
Security: &SecurityConfig{
OAuth2: &OAuth2Config{
Enabled: true,
Provider: "keycloak",
},
OPA: &OPAConfig{
Mode: "embedded",
PolicyDir: "./policies",
},
},
}
// ❌ Avoid: Custom insecure authentication schemes
// Don't reinvent the wheelDesign Principles
- Separate authentication and authorization logic
- Use mature security libraries
- Implement proper error handling
- Avoid leaking sensitive information
3. Development Phase
Secure Coding Standards
Follow Go secure coding best practices:
// ✅ Input validation
func CreateUser(req *CreateUserRequest) error {
if err := validateEmail(req.Email); err != nil {
return fmt.Errorf("invalid email: %w", err)
}
if err := validatePassword(req.Password); err != nil {
return fmt.Errorf("weak password: %w", err)
}
// Use parameterized queries to prevent SQL injection
return db.Create(&User{Email: req.Email}).Error
}
// ❌ Avoid: SQL string concatenation
// query := "SELECT * FROM users WHERE email = '" + email + "'"Code Review Checklist
Check before each commit:
- [ ] Complete input validation
- [ ] No hardcoded secrets
- [ ] Proper error handling
- [ ] Sensitive data encrypted
- [ ] Logs contain no sensitive info
- [ ] Dependencies updated
4. Testing Phase
Security Testing Types
# Static code analysis
make quality # Includes gosec scan
# Dependency vulnerability scanning
make security # Trivy + govulncheck
# Unit tests (including security tests)
make test
# Integration tests
make test-advanced TYPE=integration5. Deployment Phase
Pre-deployment Checks
# 1. Scan container images
trivy image myservice:latest
# 2. Validate configuration
./myservice --validate-config
# 3. Check secret management
# Ensure no hardcoded secrets
# 4. Enable TLS
# Ensure TLS is enabled in production6. Maintenance Phase
Continuous Security Monitoring
- Regular dependency updates
- Monitor security advisories
- Review audit logs
- Perform regular security scans
- Conduct penetration testing
Authentication Best Practices
OAuth2/OIDC Authentication
Recommended Configuration
# swit.yaml
oauth2:
enabled: true
provider: keycloak
client_id: my-service
client_secret: ${OAUTH2_CLIENT_SECRET} # Read from environment variable
issuer_url: https://auth.example.com/realms/production
use_discovery: true
# Security configuration
scopes:
- openid
- profile
- email
jwt:
signing_method: RS256 # Use asymmetric encryption
clock_skew: 5m
required_claims:
- sub
- email
# Enable caching for performance
cache:
enabled: true
max_size: 5000
ttl: 15m
# TLS configuration
tls:
enabled: true
ca_file: /etc/ssl/certs/ca-bundle.crtImplementation Example
package main
import (
"github.com/innovationmech/swit/pkg/security/oauth2"
"github.com/innovationmech/swit/pkg/middleware"
)
func setupOAuth2() error {
// 1. Create OAuth2 client
config := &oauth2.Config{
Provider: "keycloak",
ClientID: "my-service",
ClientSecret: os.Getenv("OAUTH2_CLIENT_SECRET"),
IssuerURL: "https://auth.example.com/realms/production",
UseDiscovery: true,
}
client, err := oauth2.NewClient(config)
if err != nil {
return err
}
// 2. Create authentication middleware
authMiddleware := middleware.NewOAuth2Middleware(client)
// 3. Apply to routes
router.Use(authMiddleware.Authenticate())
return nil
}JWT Token Validation
Best Practices
import "github.com/innovationmech/swit/pkg/security/jwt"
// JWT validator configuration
jwtConfig := &jwt.ValidatorConfig{
// Use JWKS to automatically fetch public keys
JWKSConfig: &jwt.JWKSConfig{
URL: "https://auth.example.com/.well-known/jwks.json",
CacheEnabled: true,
CacheTTL: time.Hour,
RefreshInterval: 15 * time.Minute,
},
// Validate claims
Issuer: "https://auth.example.com",
Audience: "my-service-api",
ClockSkew: 5 * time.Minute,
// Required claims
RequiredClaims: []string{"sub", "exp", "iat"},
}
validator, err := jwt.NewValidator(jwtConfig)
if err != nil {
log.Fatal(err)
}
// Validate token
token := "eyJhbGciOiJSUzI1NiIs..."
claims, err := validator.Validate(token)
if err != nil {
// Token invalid
return err
}
// Use claims
userID := claims.Subject
email := claims["email"].(string)Password Security
Password Policy
import "github.com/innovationmech/swit/pkg/utils"
// Password validation rules
func ValidatePassword(password string) error {
if len(password) < 12 {
return errors.New("password must be at least 12 characters")
}
// Check complexity
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
hasNumber := regexp.MustCompile(`[0-9]`).MatchString(password)
hasSpecial := regexp.MustCompile(`[!@#$%^&*]`).MatchString(password)
if !hasUpper || !hasLower || !hasNumber || !hasSpecial {
return errors.New("password must contain uppercase, lowercase, number and special character")
}
return nil
}
// Password hashing
func HashPassword(password string) (string, error) {
// Use bcrypt (recommended)
hashedPassword, err := utils.HashPassword(password)
if err != nil {
return "", err
}
return hashedPassword, nil
}
// Password verification
func VerifyPassword(hashedPassword, password string) bool {
return utils.CheckPassword(hashedPassword, password)
}mTLS Authentication
Client Certificate Verification
# swit.yaml
server:
http:
addr: :8080
tls:
enabled: true
cert_file: /etc/ssl/certs/server-cert.pem
key_file: /etc/ssl/private/server-key.pem
# Require and verify client certificates
client_auth: require_and_verify
client_ca_file: /etc/ssl/certs/client-ca.pem
min_version: "1.2"Authorization Best Practices
OPA Policy Engine
RBAC Implementation
# policies/rbac.rego
package authz
import future.keywords.if
import future.keywords.in
# Default deny
default allow := false
# Admins have all permissions
allow if {
"admin" in input.subject.roles
}
# Role-permission mapping
role_permissions := {
"editor": ["read", "write"],
"viewer": ["read"],
"manager": ["read", "write", "delete", "approve"],
}
# Check role permissions
allow if {
some role in input.subject.roles
permissions := role_permissions[role]
input.action in permissions
}
# Resource owner can access their own resources
allow if {
input.resource.owner == input.subject.user
}ABAC Implementation
# policies/abac.rego
package authz
import future.keywords.if
# Attribute-based access control
allow if {
# Check department match
input.subject.attributes.department == input.resource.department
# Check security clearance
input.subject.attributes.clearance_level >= input.resource.security_level
# Check time window
is_business_hours
}
# Business hours check
is_business_hours if {
hour := time.clock(time.now_ns())[0]
hour >= 9
hour < 18
}Go Integration
import "github.com/innovationmech/swit/pkg/security/opa"
// Create OPA client
opaConfig := &opa.Config{
Mode: "embedded",
DefaultDecisionPath: "authz/allow",
EmbeddedConfig: &opa.EmbeddedConfig{
PolicyDir: "./pkg/security/opa/policies",
},
Cache: &opa.CacheConfig{
Enabled: true,
MaxSize: 10000,
TTL: 5 * time.Minute,
},
}
client, err := opa.NewClient(opaConfig)
if err != nil {
log.Fatal(err)
}
// Evaluate policy
input := map[string]interface{}{
"subject": map[string]interface{}{
"user": "alice",
"roles": []string{"editor"},
},
"action": "write",
"resource": "documents/project-plan.pdf",
}
result, err := client.Evaluate(ctx, input)
if err != nil {
return err
}
if !result.Allow {
return errors.New("access denied")
}Authorization Middleware
import (
"github.com/innovationmech/swit/pkg/middleware"
"github.com/gin-gonic/gin"
)
// Create authorization middleware
func setupAuthorization(opaClient *opa.Client) gin.HandlerFunc {
return middleware.NewOPAMiddleware(opaClient, middleware.OPAMiddlewareConfig{
InputBuilder: func(c *gin.Context) map[string]interface{} {
// Extract user info from context
user := c.GetString("user")
roles := c.GetStringSlice("roles")
return map[string]interface{}{
"subject": map[string]interface{}{
"user": user,
"roles": roles,
},
"action": c.Request.Method,
"resource": c.Request.URL.Path,
}
},
OnDeny: func(c *gin.Context) {
c.JSON(403, gin.H{"error": "access denied"})
c.Abort()
},
})
}
// Apply to routes
router.GET("/api/v1/documents/:id",
authMiddleware,
authorizationMiddleware,
getDocumentHandler,
)API Security Guidelines
Input Validation
Validate All Inputs
import (
"github.com/go-playground/validator/v10"
)
// Use struct tags for validation
type CreateUserRequest struct {
Email string `json:"email" validate:"required,email"`
Username string `json:"username" validate:"required,alphanum,min=3,max=20"`
Password string `json:"password" validate:"required,min=12"`
Age int `json:"age" validate:"required,gte=18,lte=120"`
}
var validate = validator.New()
func CreateUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "invalid request"})
return
}
// Validate request
if err := validate.Struct(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Additional business validation
if err := ValidatePassword(req.Password); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Process request...
}Output Encoding
Prevent XSS Attacks
import "html"
// HTML encoding
func SafeOutput(userInput string) string {
return html.EscapeString(userInput)
}
// JSON response automatically encodes
c.JSON(200, gin.H{
"message": userInput, // Gin handles JSON encoding automatically
})Rate Limiting
Prevent Abuse
import "github.com/innovationmech/swit/pkg/middleware"
// IP-based rate limiting
rateLimiter := middleware.NewRateLimiter(middleware.RateLimiterConfig{
RequestsPerSecond: 10,
Burst: 20,
KeyFunc: func(c *gin.Context) string {
return c.ClientIP()
},
})
router.Use(rateLimiter)
// User-based rate limiting
userRateLimiter := middleware.NewRateLimiter(middleware.RateLimiterConfig{
RequestsPerSecond: 100,
Burst: 200,
KeyFunc: func(c *gin.Context) string {
user := c.GetString("user")
return "user:" + user
},
})
router.Use(authMiddleware, userRateLimiter)CORS Configuration
Secure Cross-Origin Configuration
import "github.com/gin-contrib/cors"
// CORS configuration
corsConfig := cors.Config{
// ❌ Avoid: Allow all origins
// AllowOrigins: []string{"*"},
// ✅ Recommended: Explicitly specify allowed origins
AllowOrigins: []string{
"https://app.example.com",
"https://admin.example.com",
},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}
router.Use(cors.New(corsConfig))API Versioning
Backward Compatibility
// Use path versioning
v1 := router.Group("/api/v1")
{
v1.GET("/users", getUsersV1)
}
v2 := router.Group("/api/v2")
{
v2.GET("/users", getUsersV2)
}
// Or use header versioning
router.Use(func(c *gin.Context) {
version := c.GetHeader("API-Version")
if version == "" {
version = "v1" // Default version
}
c.Set("api_version", version)
c.Next()
})Common Vulnerability Protection (OWASP Top 10)
1. Injection Attacks
SQL Injection Protection
// ✅ Use GORM parameterized queries
db.Where("email = ?", email).First(&user)
// ✅ Use named parameters
db.Where("email = @email AND status = @status",
sql.Named("email", email),
sql.Named("status", "active"),
).Find(&users)
// ❌ Avoid: String concatenation
// query := "SELECT * FROM users WHERE email = '" + email + "'"Command Injection Protection
import "os/exec"
// ✅ Use parameter arrays
cmd := exec.Command("ls", "-la", userDir)
// ❌ Avoid: Shell execution
// cmd := exec.Command("sh", "-c", "ls -la " + userDir)2. Broken Authentication
Session Management
// ✅ Use secure session configuration
sessionConfig := sessions.Config{
CookieHTTPOnly: true, // Prevent XSS
CookieSecure: true, // HTTPS only
CookieSameSite: http.SameSiteStrictMode, // Prevent CSRF
MaxAge: 3600, // 1 hour expiry
}
// Regenerate session ID after login
func Login(c *gin.Context) {
// Authenticate user...
// Regenerate session ID to prevent session fixation
session := sessions.Default(c)
session.Clear()
session.Set("user_id", user.ID)
session.Save()
}3. Sensitive Data Exposure
Encrypt Sensitive Data
import "github.com/innovationmech/swit/pkg/utils"
// Encrypt sensitive fields
type User struct {
ID uint
Email string
SSN string // Social Security Number - needs encryption
}
func (u *User) BeforeSave(tx *gorm.DB) error {
if u.SSN != "" {
encrypted, err := utils.Encrypt(u.SSN, encryptionKey)
if err != nil {
return err
}
u.SSN = encrypted
}
return nil
}
func (u *User) AfterFind(tx *gorm.DB) error {
if u.SSN != "" {
decrypted, err := utils.Decrypt(u.SSN, encryptionKey)
if err != nil {
return err
}
u.SSN = decrypted
}
return nil
}Log Redaction
import "github.com/innovationmech/swit/pkg/logger"
// Configure log redaction
logger.SetRedactFields([]string{
"password",
"token",
"api_key",
"secret",
"ssn",
"credit_card",
})
// Logs automatically redacted
log.Info("User created",
zap.String("email", user.Email),
zap.String("password", "********"), // Automatically redacted
)4. XML External Entities (XXE)
Secure XML Parsing
import "encoding/xml"
// ✅ Disable external entities
decoder := xml.NewDecoder(reader)
decoder.Strict = true
// Go's xml package doesn't parse external entities by default5. Broken Access Control
Enforce Access Control
// ✅ Check permissions for each request
func GetDocument(c *gin.Context) {
docID := c.Param("id")
userID := c.GetString("user_id")
// Get document
var doc Document
if err := db.First(&doc, docID).Error; err != nil {
c.JSON(404, gin.H{"error": "not found"})
return
}
// Check permissions
if doc.OwnerID != userID && !hasAdminRole(c) {
c.JSON(403, gin.H{"error": "access denied"})
return
}
c.JSON(200, doc)
}
// ❌ Avoid: Controlling access via URL parameters
// func GetDocument(c *gin.Context) {
// if c.Query("admin") == "true" {
// // Dangerous!
// }
// }6. Security Misconfiguration
Secure Default Configuration
# ✅ Production configuration
server:
debug: false # Disable debug mode
expose_errors: false # Don't expose error details
http:
tls:
enabled: true
min_version: "1.2"
# ❌ Avoid: Development config in production
# server:
# debug: true
# expose_errors: true7. Cross-Site Scripting (XSS)
Content Security Policy (CSP)
// Add CSP headers
router.Use(func(c *gin.Context) {
c.Header("Content-Security-Policy",
"default-src 'self'; "+
"script-src 'self' 'unsafe-inline'; "+
"style-src 'self' 'unsafe-inline'; "+
"img-src 'self' data: https:;")
c.Next()
})
// Add other security headers
router.Use(func(c *gin.Context) {
c.Header("X-Content-Type-Options", "nosniff")
c.Header("X-Frame-Options", "DENY")
c.Header("X-XSS-Protection", "1; mode=block")
c.Next()
})8. Insecure Deserialization
Secure Deserialization
// ✅ Use type-safe JSON binding
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "invalid request"})
return
}
// ❌ Avoid: Untrusted deserialization
// var data map[string]interface{}
// if err := json.Unmarshal(untrustedData, &data); err != nil {
// // May execute malicious code
// }9. Using Components with Known Vulnerabilities
Dependency Management
# Regularly scan dependencies
make security
# Update dependencies
go get -u ./...
go mod tidy
# Check for known vulnerabilities
govulncheck ./...10. Insufficient Logging & Monitoring
Complete Audit Logging
import "github.com/innovationmech/swit/pkg/security/audit"
// Record security events
auditor := audit.NewLogger(audit.Config{
Enabled: true,
Events: []string{
"authentication_success",
"authentication_failure",
"authorization_denied",
"sensitive_data_accessed",
},
})
// Record authentication event
auditor.Log(audit.Event{
Type: "authentication_success",
Actor: userID,
Action: "login",
Resource: "system",
Timestamp: time.Now(),
Result: "success",
})Data Protection
Data at Rest Encryption
Database Encryption
import "github.com/innovationmech/swit/pkg/utils"
// Field-level encryption
type CreditCard struct {
ID uint
UserID uint
Number string // Encrypted storage
CVV string // Encrypted storage
ExpiryMonth int
ExpiryYear int
}
func (c *CreditCard) BeforeSave(tx *gorm.DB) error {
if c.Number != "" {
encrypted, err := utils.EncryptAES(c.Number, encryptionKey)
if err != nil {
return err
}
c.Number = encrypted
}
if c.CVV != "" {
encrypted, err := utils.EncryptAES(c.CVV, encryptionKey)
if err != nil {
return err
}
c.CVV = encrypted
}
return nil
}
func (c *CreditCard) AfterFind(tx *gorm.DB) error {
if c.Number != "" {
decrypted, err := utils.DecryptAES(c.Number, encryptionKey)
if err != nil {
return err
}
c.Number = decrypted
}
if c.CVV != "" {
decrypted, err := utils.DecryptAES(c.CVV, encryptionKey)
if err != nil {
return err
}
c.CVV = decrypted
}
return nil
}File Encryption
import "crypto/aes"
// Encrypt file
func EncryptFile(inputPath, outputPath string, key []byte) error {
plaintext, err := os.ReadFile(inputPath)
if err != nil {
return err
}
ciphertext, err := utils.EncryptAES(plaintext, key)
if err != nil {
return err
}
return os.WriteFile(outputPath, ciphertext, 0600)
}Data in Transit Encryption
See Transport Layer Security section.
Data Masking
PII Data Masking
// Mask phone number
func MaskPhone(phone string) string {
if len(phone) < 7 {
return "***"
}
return phone[:3] + "****" + phone[len(phone)-4:]
}
// Mask email
func MaskEmail(email string) string {
parts := strings.Split(email, "@")
if len(parts) != 2 {
return "***"
}
username := parts[0]
if len(username) > 3 {
username = username[:3] + "***"
}
return username + "@" + parts[1]
}
// API response masking
type UserResponse struct {
ID uint `json:"id"`
Email string `json:"email"`
Phone string `json:"phone"`
}
func (u *User) ToResponse() UserResponse {
return UserResponse{
ID: u.ID,
Email: MaskEmail(u.Email),
Phone: MaskPhone(u.Phone),
}
}Transport Layer Security
TLS Configuration
Production TLS
# swit.yaml
server:
http:
addr: :8080
tls:
enabled: true
cert_file: /etc/ssl/certs/server-cert.pem
key_file: /etc/ssl/private/server-key.pem
min_version: "1.2" # Minimum TLS 1.2
max_version: "1.3" # Recommended TLS 1.3
cipher_suites:
- TLS_AES_128_GCM_SHA256
- TLS_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
grpc:
addr: :9090
tls:
enabled: true
cert_file: /etc/ssl/certs/server-cert.pem
key_file: /etc/ssl/private/server-key.pem
min_version: "1.2"mTLS (Mutual TLS)
Service-to-Service Communication
# Server-side
server:
grpc:
tls:
enabled: true
cert_file: /etc/ssl/certs/server-cert.pem
key_file: /etc/ssl/private/server-key.pem
# Require client certificates
client_auth: require_and_verify
client_ca_file: /etc/ssl/certs/client-ca.pemClient Configuration
import (
"crypto/tls"
"crypto/x509"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
func createMTLSClient() (*grpc.ClientConn, error) {
// Load client certificate
cert, err := tls.LoadX509KeyPair(
"/etc/ssl/certs/client-cert.pem",
"/etc/ssl/private/client-key.pem",
)
if err != nil {
return nil, err
}
// Load CA certificate
caCert, err := os.ReadFile("/etc/ssl/certs/ca-cert.pem")
if err != nil {
return nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// TLS configuration
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
MinVersion: tls.VersionTLS12,
}
creds := credentials.NewTLS(tlsConfig)
// Create connection
return grpc.Dial("service.example.com:9090",
grpc.WithTransportCredentials(creds),
)
}Certificate Management
Certificate Generation
# Generate CA certificate
openssl genrsa -out ca-key.pem 4096
openssl req -new -x509 -days 3650 -key ca-key.pem -out ca-cert.pem
# Generate server certificate
openssl genrsa -out server-key.pem 4096
openssl req -new -key server-key.pem -out server.csr
openssl x509 -req -days 365 -in server.csr -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem
# Generate client certificate
openssl genrsa -out client-key.pem 4096
openssl req -new -key client-key.pem -out client.csr
openssl x509 -req -days 365 -in client.csr -CA ca-cert.pem -CAkey ca-key.pem -set_serial 02 -out client-cert.pemCertificate Rotation
# Use cert-manager (Kubernetes)
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: myservice-cert
spec:
secretName: myservice-tls
duration: 2160h # 90 days
renewBefore: 360h # Renew 15 days before expiry
issuerRef:
name: ca-issuer
kind: Issuer
dnsNames:
- myservice.example.comSecret Management
Environment Variables
Basic Usage
# .env file (don't commit to Git)
OAUTH2_CLIENT_SECRET=your-secret-here
JWT_HMAC_SECRET=your-jwt-secret
DATABASE_PASSWORD=your-db-passwordimport "github.com/joho/godotenv"
func init() {
// Load .env file
if err := godotenv.Load(); err != nil {
log.Println("No .env file found")
}
}
func main() {
clientSecret := os.Getenv("OAUTH2_CLIENT_SECRET")
if clientSecret == "" {
log.Fatal("OAUTH2_CLIENT_SECRET is required")
}
}Kubernetes Secrets
Create Secret
# From files
kubectl create secret generic app-secrets \
--from-file=oauth2-client-secret=./client-secret.txt \
--from-file=db-password=./db-password.txt
# From literal values
kubectl create secret generic app-secrets \
--from-literal=oauth2-client-secret='your-secret' \
--from-literal=db-password='your-password'Use Secret
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myservice
spec:
template:
spec:
containers:
- name: myservice
image: myservice:latest
env:
- name: OAUTH2_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: app-secrets
key: oauth2-client-secret
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: db-passwordHashiCorp Vault
Configuration
# swit.yaml
secrets:
provider: vault
vault:
addr: https://vault.example.com:8200
auth_method: approle
role_id: ${VAULT_ROLE_ID}
secret_id: ${VAULT_SECRET_ID}
kv_version: 2
mount_path: secret
path: swit/production
tls:
enabled: true
ca_cert: /etc/ssl/certs/vault-ca.pemUsage
import "github.com/innovationmech/swit/pkg/security/secrets"
// Create Vault client
secretsManager, err := secrets.NewManager(secretsConfig)
if err != nil {
log.Fatal(err)
}
// Get secret
clientSecret, err := secretsManager.GetSecret("oauth2_client_secret")
if err != nil {
log.Fatal(err)
}
// Set secret
err = secretsManager.SetSecret("api_key", "new-api-key")
if err != nil {
log.Fatal(err)
}Secret Rotation
Automatic Rotation Strategy
// Rotate secrets periodically
func rotateKeys(secretsManager *secrets.Manager) {
ticker := time.NewTicker(30 * 24 * time.Hour) // 30 days
defer ticker.Stop()
for range ticker.C {
// Generate new key
newKey := generateSecureKey()
// Save new key
if err := secretsManager.SetSecret("encryption_key_new", newKey); err != nil {
log.Error("Failed to rotate key", zap.Error(err))
continue
}
// Notify services to use new key
notifyKeyRotation(newKey)
// Keep old key for a while to decrypt old data
time.Sleep(7 * 24 * time.Hour)
// Delete old key
secretsManager.DeleteSecret("encryption_key_old")
}
}Security Configuration
Configuration Validation
Validate on Startup
import "github.com/innovationmech/swit/pkg/server"
func main() {
// Load configuration
config, err := server.LoadConfig("swit.yaml")
if err != nil {
log.Fatal(err)
}
// Validate configuration
if err := config.Validate(); err != nil {
log.Fatal("Invalid configuration:", err)
}
// Security checks
if err := validateSecurityConfig(config); err != nil {
log.Fatal("Security configuration error:", err)
}
}
func validateSecurityConfig(config *server.ServerConfig) error {
// TLS must be enabled in production
if config.Environment == "production" {
if !config.HTTP.TLS.Enabled {
return errors.New("TLS must be enabled in production")
}
if config.HTTP.TLS.MinVersion < "1.2" {
return errors.New("TLS 1.2 or higher required in production")
}
}
// Check secret configuration
if config.OAuth2.Enabled && config.OAuth2.ClientSecret == "" {
return errors.New("OAuth2 client secret is required")
}
return nil
}Environment Separation
Configuration File Structure
config/
├── swit.yaml # Base configuration
├── swit-dev.yaml # Development environment
├── swit-staging.yaml # Staging environment
└── swit-production.yaml # Production environmentLoad Configuration
func LoadEnvironmentConfig() (*server.ServerConfig, error) {
env := os.Getenv("ENVIRONMENT")
if env == "" {
env = "dev"
}
configFile := fmt.Sprintf("config/swit-%s.yaml", env)
return server.LoadConfig(configFile)
}Secure Defaults
Configuration Defaults
func NewDefaultSecurityConfig() *SecurityConfig {
return &SecurityConfig{
// TLS enabled by default
TLS: &TLSConfig{
Enabled: true,
MinVersion: "1.2",
},
// CORS default restrictions
CORS: &CORSConfig{
AllowOrigins: []string{}, // Explicitly configure
AllowCredentials: false,
},
// Rate limiting enabled by default
RateLimit: &RateLimitConfig{
Enabled: true,
RequestsPerSecond: 10,
Burst: 20,
},
// Audit enabled by default
Audit: &AuditConfig{
Enabled: true,
Events: []string{
"authentication_failure",
"authorization_denied",
},
},
}
}Security Monitoring & Auditing
Audit Logging
Configure Auditing
# swit.yaml
security:
audit:
enabled: true
log_level: info
# Event types to record
events:
- authentication_success
- authentication_failure
- authorization_denied
- token_issued
- token_revoked
- policy_evaluated
- sensitive_data_accessed
- password_changed
- permission_changed
# Output configuration
outputs:
- type: file
path: /var/log/swit/audit.log
format: json
max_size: 100 # MB
max_backups: 10
max_age: 30 # days
- type: syslog
network: udp
addr: localhost:514
tag: swit-audit
# Sensitive field filtering
sensitive_fields:
- password
- client_secret
- api_key
- token
- ssn
- credit_card
redact_policy: mask
mask_char: "*"Record Audit Events
import "github.com/innovationmech/swit/pkg/security/audit"
// Create audit logger
auditor := audit.NewLogger(auditConfig)
// Record authentication event
auditor.Log(audit.Event{
Type: "authentication_failure",
Actor: email,
Action: "login",
Resource: "system",
Result: "failure",
Reason: "invalid password",
Timestamp: time.Now(),
Metadata: map[string]interface{}{
"ip_address": clientIP,
"user_agent": userAgent,
},
})
// Record authorization event
auditor.Log(audit.Event{
Type: "authorization_denied",
Actor: userID,
Action: "delete",
Resource: fmt.Sprintf("documents/%s", docID),
Result: "denied",
Reason: "insufficient permissions",
Timestamp: time.Now(),
})
// Record sensitive data access
auditor.Log(audit.Event{
Type: "sensitive_data_accessed",
Actor: userID,
Action: "read",
Resource: "user/ssn",
Result: "success",
Timestamp: time.Now(),
})Security Metrics
Prometheus Metrics
# swit.yaml
security:
metrics:
enabled: true
namespace: swit
subsystem: security
collect:
- authentication_total
- authentication_errors
- authorization_total
- authorization_denied
- token_validation_duration
- policy_evaluation_duration
- jwt_cache_hits
- jwt_cache_misses
labels:
service: my-service
environment: productionQuery Metrics
# Authentication failure rate
rate(swit_security_authentication_errors_total[5m])
# Authorization denial rate
rate(swit_security_authorization_denied_total[5m])
# Token validation latency
histogram_quantile(0.95,
rate(swit_security_token_validation_duration_bucket[5m]))
# Policy evaluation latency
histogram_quantile(0.99,
rate(swit_security_policy_evaluation_duration_bucket[5m]))Alert Rules
# prometheus-alerts.yaml
groups:
- name: security
rules:
- alert: HighAuthenticationFailureRate
expr: |
rate(swit_security_authentication_errors_total[5m]) > 10
for: 5m
labels:
severity: warning
annotations:
summary: "High authentication failure rate"
description: "More than 10 authentication failures per second"
- alert: FrequentAuthorizationDenials
expr: |
rate(swit_security_authorization_denied_total[5m]) > 5
for: 10m
labels:
severity: warning
annotations:
summary: "Frequent authorization denials"
description: "Possible unauthorized access attempts"
- alert: SlowPolicyEvaluation
expr: |
histogram_quantile(0.95,
rate(swit_security_policy_evaluation_duration_bucket[5m])
) > 1
for: 10m
labels:
severity: warning
annotations:
summary: "Slow policy evaluation"
description: "95th percentile policy evaluation > 1s"Security Monitoring
Real-time Monitoring
import "github.com/innovationmech/swit/pkg/security/metrics"
// Create security metrics collector
metricsCollector := metrics.NewCollector(metricsConfig)
// Middleware to record metrics
func securityMetricsMiddleware(collector *metrics.Collector) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
duration := time.Since(start)
// Record request metrics
collector.RecordRequest(metrics.RequestMetrics{
Path: c.Request.URL.Path,
Method: c.Request.Method,
StatusCode: c.Writer.Status(),
Duration: duration,
UserID: c.GetString("user_id"),
})
// Record authentication metrics
if authenticated := c.GetBool("authenticated"); authenticated {
collector.IncAuthentications("success")
} else {
collector.IncAuthentications("failure")
}
}
}Container Security
Dockerfile Security
Best Practices
# Use specific version of base image
FROM golang:1.21-alpine3.18 AS builder
# Run as non-root user
RUN addgroup -g 1000 appgroup && \
adduser -u 1000 -G appgroup -s /bin/sh -D appuser
# Set working directory
WORKDIR /app
# Copy dependency files
COPY go.mod go.sum ./
RUN go mod download && go mod verify
# Copy source code
COPY . .
# Build application
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo \
-ldflags="-w -s" -o /app/myservice ./cmd/myservice
# Minimize runtime image
FROM alpine:3.18
# Install CA certificates
RUN apk --no-cache add ca-certificates
# Create non-root user
RUN addgroup -g 1000 appgroup && \
adduser -u 1000 -G appgroup -s /bin/sh -D appuser
# Set working directory
WORKDIR /app
# Copy binary from builder stage
COPY --from=builder --chown=appuser:appgroup /app/myservice .
# Copy configuration files
COPY --chown=appuser:appgroup swit.yaml .
# Switch to non-root user
USER appuser
# Expose ports
EXPOSE 8080 9090
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD ["/app/myservice", "health"]
# Run application
ENTRYPOINT ["/app/myservice"]Image Scanning
CI/CD Integration
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: docker build -t myservice:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: myservice:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'Local Scanning
# Scan image
trivy image myservice:latest
# Scan Dockerfile
trivy config Dockerfile
# Scan and generate report
trivy image --format json --output results.json myservice:latestKubernetes Security
Pod Security Policy
# pod-security-policy.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
readOnlyRootFilesystem: trueSecurity Context
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myservice
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: myservice
image: myservice:latest
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
resources:
limits:
cpu: "1"
memory: "512Mi"
requests:
cpu: "100m"
memory: "128Mi"Network Policy
# network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: myservice-netpol
spec:
podSelector:
matchLabels:
app: myservice
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: production
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432Dependency Management
Dependency Scanning
Makefile Integration
# Security scanning
.PHONY: security
security: security-gosec security-trivy security-govulncheck
# gosec - Go security scanning
.PHONY: security-gosec
security-gosec:
@echo "Running gosec..."
@gosec -fmt=json -out=gosec-report.json ./...
# Trivy - Vulnerability scanning
.PHONY: security-trivy
security-trivy:
@echo "Running trivy..."
@trivy fs --format json --output trivy-report.json .
# govulncheck - Go vulnerability database checking
.PHONY: security-govulncheck
security-govulncheck:
@echo "Running govulncheck..."
@govulncheck ./...Regular Updates
# Check updatable dependencies
go list -u -m all
# Update all dependencies to latest minor version
go get -u ./...
# Update specific dependency
go get github.com/gin-gonic/gin@latest
# Clean unused dependencies
go mod tidy
# Verify dependencies
go mod verifyDependency Locking
Using go.sum
# go.sum file locks dependency versions
# Ensure go.sum is committed to version control
# Verify go.sum
go mod verify
# If go.sum doesn't exist, regenerate
go mod tidyPrivate Dependencies
Configure Private Repositories
# Configure GOPRIVATE
export GOPRIVATE=github.com/myorg/*
# Configure Git credentials
git config --global url."https://${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"Security Testing
Unit Testing
Security-Related Tests
package security_test
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPasswordValidation(t *testing.T) {
tests := []struct {
name string
password string
wantErr bool
}{
{
name: "valid password",
password: "SecureP@ssw0rd!",
wantErr: false,
},
{
name: "too short",
password: "Short1!",
wantErr: true,
},
{
name: "no special char",
password: "LongPassword123",
wantErr: true,
},
{
name: "no number",
password: "LongPassword!@#",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidatePassword(tt.password)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestXSSPrevention(t *testing.T) {
maliciousInputs := []string{
"<script>alert('XSS')</script>",
"<img src=x onerror=alert('XSS')>",
"javascript:alert('XSS')",
}
for _, input := range maliciousInputs {
output := SanitizeInput(input)
assert.NotContains(t, output, "<script>")
assert.NotContains(t, output, "onerror")
assert.NotContains(t, output, "javascript:")
}
}
func TestSQLInjectionPrevention(t *testing.T) {
maliciousInputs := []string{
"' OR '1'='1",
"'; DROP TABLE users--",
"1' UNION SELECT * FROM users--",
}
for _, input := range maliciousInputs {
// Parameterized queries should be safe
var count int64
err := db.Model(&User{}).Where("email = ?", input).Count(&count).Error
assert.NoError(t, err)
assert.Equal(t, int64(0), count)
}
}Integration Testing
Authentication Tests
func TestOAuth2Authentication(t *testing.T) {
// Setup test server
router := setupTestRouter()
// Test unauthenticated access
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/protected", nil)
router.ServeHTTP(w, req)
assert.Equal(t, 401, w.Code)
// Test invalid token
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/api/v1/protected", nil)
req.Header.Set("Authorization", "Bearer invalid-token")
router.ServeHTTP(w, req)
assert.Equal(t, 401, w.Code)
// Test valid token
validToken := generateTestToken()
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/api/v1/protected", nil)
req.Header.Set("Authorization", "Bearer "+validToken)
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
}Authorization Tests
func TestOPAAuthorization(t *testing.T) {
tests := []struct {
name string
user string
roles []string
action string
resource string
wantAllow bool
}{
{
name: "admin can delete",
user: "admin",
roles: []string{"admin"},
action: "delete",
resource: "documents/1",
wantAllow: true,
},
{
name: "viewer cannot write",
user: "user1",
roles: []string{"viewer"},
action: "write",
resource: "documents/1",
wantAllow: false,
},
{
name: "editor can write",
user: "user2",
roles: []string{"editor"},
action: "write",
resource: "documents/1",
wantAllow: true,
},
}
opaClient := setupTestOPA()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
input := map[string]interface{}{
"subject": map[string]interface{}{
"user": tt.user,
"roles": tt.roles,
},
"action": tt.action,
"resource": tt.resource,
}
result, err := opaClient.Evaluate(context.Background(), input)
assert.NoError(t, err)
assert.Equal(t, tt.wantAllow, result.Allow)
})
}
}Penetration Testing
OWASP ZAP Integration
# zap-scan.yml
name: OWASP ZAP Scan
on:
schedule:
- cron: '0 2 * * *' # Daily at 2 AM
jobs:
zap_scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Start application
run: |
docker-compose up -d
sleep 30
- name: ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.7.0
with:
target: 'http://localhost:8080'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-a'
- name: ZAP Full Scan
uses: zaproxy/action-full-scan@v0.4.0
with:
target: 'http://localhost:8080'
rules_file_name: '.zap/rules.tsv'Related Resources
Official Documentation
External Resources
OWASP Resources
Go Security
Standards & Compliance
Tool Documentation
License
Copyright (c) 2024-2025 Six-Thirty Labs, Inc.
Licensed under the Apache License, Version 2.0.