Add initial project structure with core functionality
- Created a new Go module named 'teleport' for secure port forwarding. - Added essential files including .gitignore, LICENSE, and README.md with project details. - Implemented configuration management with YAML support in config package. - Developed core client and server functionalities for handling port forwarding. - Introduced DNS server capabilities and integrated logging with sanitization. - Established rate limiting and metrics tracking for performance monitoring. - Included comprehensive tests for core components and functionalities. - Set up CI workflows for automated testing and release management using Gitea actions.
This commit is contained in:
269
pkg/logger/logger.go
Normal file
269
pkg/logger/logger.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
Log *logrus.Logger
|
||||
once sync.Once
|
||||
mu sync.RWMutex
|
||||
)
|
||||
|
||||
// Config holds logging configuration
|
||||
type Config struct {
|
||||
Level string `yaml:"level"` // debug, info, warn, error
|
||||
Format string `yaml:"format"` // json, text
|
||||
File string `yaml:"file"` // log file path (empty for stdout)
|
||||
MaxSize int `yaml:"max_size"` // max log file size in MB
|
||||
MaxBackups int `yaml:"max_backups"` // max number of backup files
|
||||
MaxAge int `yaml:"max_age"` // max age of backup files in days
|
||||
Compress bool `yaml:"compress"` // compress backup files
|
||||
}
|
||||
|
||||
// Init initializes the global logger with the given configuration
|
||||
func Init(config Config) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
Log = logrus.New()
|
||||
|
||||
// Set log level
|
||||
level, err := logrus.ParseLevel(config.Level)
|
||||
if err != nil {
|
||||
level = logrus.InfoLevel
|
||||
}
|
||||
Log.SetLevel(level)
|
||||
|
||||
// Set log format with sanitization
|
||||
switch config.Format {
|
||||
case "json":
|
||||
Log.SetFormatter(&SanitizedJSONFormatter{
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
})
|
||||
default:
|
||||
Log.SetFormatter(&SanitizedTextFormatter{
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
})
|
||||
}
|
||||
|
||||
// Set output
|
||||
if config.File != "" {
|
||||
// Ensure directory exists
|
||||
dir := filepath.Dir(config.File)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Open log file with secure permissions (owner read/write only)
|
||||
file, err := os.OpenFile(config.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set output to both file and stdout
|
||||
Log.SetOutput(io.MultiWriter(file, os.Stdout))
|
||||
} else {
|
||||
Log.SetOutput(os.Stdout)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLogger returns the global logger instance
|
||||
func GetLogger() *logrus.Logger {
|
||||
mu.RLock()
|
||||
if Log != nil {
|
||||
mu.RUnlock()
|
||||
return Log
|
||||
}
|
||||
mu.RUnlock()
|
||||
|
||||
// Initialize with default config if not already initialized
|
||||
once.Do(func() {
|
||||
Init(Config{
|
||||
Level: "info",
|
||||
Format: "text",
|
||||
File: "",
|
||||
})
|
||||
})
|
||||
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
return Log
|
||||
}
|
||||
|
||||
// Helper functions for common logging operations
|
||||
func Debug(args ...interface{}) {
|
||||
GetLogger().Debug(args...)
|
||||
}
|
||||
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
GetLogger().Debugf(format, args...)
|
||||
}
|
||||
|
||||
func Info(args ...interface{}) {
|
||||
GetLogger().Info(args...)
|
||||
}
|
||||
|
||||
func Infof(format string, args ...interface{}) {
|
||||
GetLogger().Infof(format, args...)
|
||||
}
|
||||
|
||||
func Warn(args ...interface{}) {
|
||||
GetLogger().Warn(args...)
|
||||
}
|
||||
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
GetLogger().Warnf(format, args...)
|
||||
}
|
||||
|
||||
func Error(args ...interface{}) {
|
||||
GetLogger().Error(args...)
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
GetLogger().Errorf(format, args...)
|
||||
}
|
||||
|
||||
func Fatal(args ...interface{}) {
|
||||
GetLogger().Fatal(args...)
|
||||
}
|
||||
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
GetLogger().Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// WithField creates a new logger entry with a field
|
||||
func WithField(key string, value interface{}) *logrus.Entry {
|
||||
return GetLogger().WithField(key, value)
|
||||
}
|
||||
|
||||
// WithFields creates a new logger entry with multiple fields
|
||||
func WithFields(fields logrus.Fields) *logrus.Entry {
|
||||
return GetLogger().WithFields(fields)
|
||||
}
|
||||
|
||||
// SanitizedTextFormatter sanitizes log output
|
||||
type SanitizedTextFormatter struct {
|
||||
FullTimestamp bool
|
||||
TimestampFormat string
|
||||
}
|
||||
|
||||
// Format formats the log entry with sanitization
|
||||
func (f *SanitizedTextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
// Sanitize sensitive data
|
||||
sanitizedEntry := *entry
|
||||
sanitizedEntry.Message = sanitizeString(entry.Message)
|
||||
|
||||
// Sanitize fields
|
||||
sanitizedFields := make(logrus.Fields)
|
||||
for k, v := range entry.Data {
|
||||
sanitizedFields[k] = sanitizeValue(v)
|
||||
}
|
||||
sanitizedEntry.Data = sanitizedFields
|
||||
|
||||
// Use default text formatter
|
||||
formatter := &logrus.TextFormatter{
|
||||
FullTimestamp: f.FullTimestamp,
|
||||
TimestampFormat: f.TimestampFormat,
|
||||
}
|
||||
return formatter.Format(&sanitizedEntry)
|
||||
}
|
||||
|
||||
// SanitizedJSONFormatter sanitizes JSON log output
|
||||
type SanitizedJSONFormatter struct {
|
||||
TimestampFormat string
|
||||
}
|
||||
|
||||
// Format formats the log entry with sanitization
|
||||
func (f *SanitizedJSONFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
// Sanitize sensitive data
|
||||
sanitizedEntry := *entry
|
||||
sanitizedEntry.Message = sanitizeString(entry.Message)
|
||||
|
||||
// Sanitize fields
|
||||
sanitizedFields := make(logrus.Fields)
|
||||
for k, v := range entry.Data {
|
||||
sanitizedFields[k] = sanitizeValue(v)
|
||||
}
|
||||
sanitizedEntry.Data = sanitizedFields
|
||||
|
||||
// Use default JSON formatter
|
||||
formatter := &logrus.JSONFormatter{
|
||||
TimestampFormat: f.TimestampFormat,
|
||||
}
|
||||
return formatter.Format(&sanitizedEntry)
|
||||
}
|
||||
|
||||
// sanitizeString removes or masks sensitive information
|
||||
func sanitizeString(s string) string {
|
||||
// Remove potential encryption keys (hex strings longer than 32 chars)
|
||||
keyPattern := regexp.MustCompile(`[a-fA-F0-9]{32,}`)
|
||||
s = keyPattern.ReplaceAllString(s, "[REDACTED_KEY]")
|
||||
|
||||
// Remove potential passwords and tokens
|
||||
passwordPattern := regexp.MustCompile(`(?i)(password|pass|pwd|secret|key|token|auth|credential)\s*[:=]\s*[^\s]+`)
|
||||
s = passwordPattern.ReplaceAllString(s, "$1=[REDACTED]")
|
||||
|
||||
// Remove potential API keys and tokens
|
||||
apiKeyPattern := regexp.MustCompile(`(?i)(api[_-]?key|access[_-]?token|bearer[_-]?token)\s*[:=]\s*[^\s]+`)
|
||||
s = apiKeyPattern.ReplaceAllString(s, "$1=[REDACTED]")
|
||||
|
||||
// Remove potential database connection strings
|
||||
dbPattern := regexp.MustCompile(`(?i)(mysql|postgres|mongodb|redis)://[^@]+@[^\s]+`)
|
||||
s = dbPattern.ReplaceAllString(s, "[REDACTED_DB_CONNECTION]")
|
||||
|
||||
// Remove potential JWT tokens
|
||||
jwtPattern := regexp.MustCompile(`eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*`)
|
||||
s = jwtPattern.ReplaceAllString(s, "[REDACTED_JWT]")
|
||||
|
||||
// Remove potential credit card numbers
|
||||
ccPattern := regexp.MustCompile(`\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b`)
|
||||
s = ccPattern.ReplaceAllString(s, "[REDACTED_CC]")
|
||||
|
||||
// Remove potential SSNs
|
||||
ssnPattern := regexp.MustCompile(`\b\d{3}-\d{2}-\d{4}\b`)
|
||||
s = ssnPattern.ReplaceAllString(s, "[REDACTED_SSN]")
|
||||
|
||||
// Remove potential IP addresses in sensitive contexts
|
||||
ipPattern := regexp.MustCompile(`\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b`)
|
||||
s = ipPattern.ReplaceAllString(s, "[REDACTED_IP]")
|
||||
|
||||
// Remove potential email addresses
|
||||
emailPattern := regexp.MustCompile(`\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b`)
|
||||
s = emailPattern.ReplaceAllString(s, "[REDACTED_EMAIL]")
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// sanitizeValue sanitizes various value types
|
||||
func sanitizeValue(v interface{}) interface{} {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
return sanitizeString(val)
|
||||
case []byte:
|
||||
return "[BINARY_DATA]"
|
||||
case map[string]interface{}:
|
||||
sanitized := make(map[string]interface{})
|
||||
for k, v := range val {
|
||||
sanitized[k] = sanitizeValue(v)
|
||||
}
|
||||
return sanitized
|
||||
case []interface{}:
|
||||
sanitized := make([]interface{}, len(val))
|
||||
for i, v := range val {
|
||||
sanitized[i] = sanitizeValue(v)
|
||||
}
|
||||
return sanitized
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user