This commit is contained in:
Jan 2025-02-21 23:37:34 +01:00
parent c67be49a46
commit 03251290fc
10 changed files with 382 additions and 0 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
BearerToken=ok

34
Dockerfile Normal file
View File

@ -0,0 +1,34 @@
FROM golang:1.22 as builder
WORKDIR /app
# Copy go module files and download dependencies
COPY go.mod go.sum ./
RUN go mod download
# Copy the rest of the application
COPY . .
# Build the Go application
RUN go build -o main .
# Stage 2: Create a minimal production image
FROM alpine:latest
WORKDIR /app
# Copy the built application from the builder stage
COPY --from=builder /app/main .
# Copy the .env file (if exists)
COPY .env .env
# Expose the necessary port (change as needed)
EXPOSE 8080
# Run the application
CMD ["./main"]
# Healthcheck to ensure the app is running
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1

View File

@ -1,2 +1,10 @@
# CCTV
## .env Configuration
```env
UploadDir=./upload
LOG_FILE=auth_failures.log
MaxFailedAttempts=3
BanDuration=5
BearerToken=jhasd083raASDasdoad§$234
````

41
cmd/main.go Normal file
View File

@ -0,0 +1,41 @@
package main
import (
"net/http"
"time"
"git.neunzweinull.com/jan/CCTV/config"
"git.neunzweinull.com/jan/CCTV/internal"
)
const (
CleanupInterval = 5 * time.Minute
)
func init() {
//Load the env file
config.LoadEnv()
//Initialize the log file
internal.InitLog()
//Initialize the Upload Directory
config.InitUploadDir()
//Start background cleanup for expired bans
go func() {
for {
time.Sleep(CleanupInterval)
internal.CleanupBans()
}
}()
}
func main() {
defer internal.CloseLog()
http.HandleFunc("/upload", internal.AuthMiddleware(internal.UploadHandler))
http.HandleFunc("/health", internal.HealthHandler)
http.ListenAndServe(":8080", nil)
}

62
config/env.go Normal file
View File

@ -0,0 +1,62 @@
package config
import (
"bufio"
"os"
"strings"
"git.neunzweinull.com/jan/CCTV/internal"
)
const (
Prefix = "CCTV_"
)
func LoadEnv() error {
// TODO: Load env from custom file
setDefaultEnvs()
file, err := os.Open(".env")
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.Split(scanner.Text(), "=")
//Ignore empty lines and comments
if len(line) < 2 || strings.HasPrefix(line[0], "#") {
continue
}
key := strings.TrimSpace(line[0])
value := strings.TrimSpace(line[1])
if key == "" || value == "" {
continue
}
// Add prefix
key = Prefix + key
// Remove quotes
value = strings.Trim(value, `"'`)
os.Setenv(key, value)
internal.SetShit()
}
return scanner.Err()
}
func setDefaultEnvs() {
os.Setenv(Prefix+"UploadDir", "uploads/")
os.Setenv(Prefix+"LOG_FILE", "auth_failures.log")
os.Setenv(Prefix+"MaxFailedAttempts", "3")
os.Setenv(Prefix+"BanDuration", "5")
}

18
config/upload.go Normal file
View File

@ -0,0 +1,18 @@
package config
import (
"fmt"
"os"
)
func InitUploadDir() {
if _, err := os.Stat(os.Getenv("CCTV_UploadDir")); err == nil {
return
}
err := os.Mkdir(os.Getenv("CCTV_UploadDir"), 0755)
if err != nil {
fmt.Println("Error creating upload directory:", err)
os.Exit(1)
}
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.neunzweinull.com/jan/CCTV
go 1.22.2

13
internal/bans.go Normal file
View File

@ -0,0 +1,13 @@
package internal
import "time"
func CleanupBans() {
mu.Lock()
defer mu.Unlock()
for ip, banTime := range bannedIPs {
if time.Now().After(banTime) {
delete(bannedIPs, ip)
}
}
}

71
internal/log.go Normal file
View File

@ -0,0 +1,71 @@
package internal
import (
"fmt"
"os"
"path/filepath"
"runtime"
"sync"
"time"
)
var (
logFile *os.File
mu sync.Mutex
)
func InitLog() {
var err error
logFileName := os.Getenv("CCTV_LOG_FILE")
if logFileName == "" {
logFileName = "app.log" // Default log file name if the environment variable is not set
}
var logFilePath string
if runtime.GOOS == "windows" {
logFilePath = filepath.Join("C:\\ProgramData\\CCTV", logFileName)
} else {
logFilePath = filepath.Join("/var/log", logFileName)
}
// Ensure directory exists
err = os.MkdirAll(filepath.Dir(logFilePath), 0755)
if err != nil {
fmt.Println("Error creating log directory:", err)
os.Exit(1)
}
logFile, err = os.OpenFile(logFilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Println("Error opening log file:", err)
os.Exit(1)
}
}
func LogFailedAttempt(ip string) {
logEntry := fmt.Sprintf("[%s] Failed auth attempt from IP: %s\n", time.Now().Format(time.RFC3339), ip)
logFile.WriteString(logEntry)
logFile.Sync()
fmt.Print(logEntry)
}
func LogBan(ip string) {
logEntry := fmt.Sprintf("[%s] Banned IP: %s\n", time.Now().Format(time.RFC3339), ip)
logFile.WriteString(logEntry)
logFile.Sync()
fmt.Print(logEntry)
}
func LogSuccessUpload(ip string, fileName string) {
logEntry := fmt.Sprintf("[%s] Successfully uploaded file %s from IP: %s\n", time.Now().Format(time.RFC3339), fileName, ip)
logFile.WriteString(logEntry)
logFile.Sync()
fmt.Print(logEntry)
}
func CloseLog() {
logFile.Close()
}

131
internal/server.go Normal file
View File

@ -0,0 +1,131 @@
package internal
import (
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
var (
MaxFailedAttempts = 3
BanDuration = 5 * time.Minute
UploadDir = ""
failedAttempts = make(map[string]int)
bannedIPs = make(map[string]time.Time)
)
func SetShit() {
if maxAttempts, err := strconv.Atoi(os.Getenv("CCTV_MaxFailedAttempts")); err == nil {
MaxFailedAttempts = maxAttempts
}
if banDuration, err := time.ParseDuration(os.Getenv("CCTV_BanDuration")); err == nil {
BanDuration = banDuration * time.Minute
}
UploadDir = os.Getenv("CCTV_UploadDir")
}
func UploadHandler(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(10 << 20)
if err != nil {
http.Error(w, "Error parsing form", http.StatusBadRequest)
return
}
file, handler, err := r.FormFile("file")
if err != nil {
http.Error(w, "Error retrieving file", http.StatusBadRequest)
return
}
defer file.Close()
filePath := filepath.Join(UploadDir, handler.Filename)
if _, err := os.Stat(filePath); err == nil {
http.Error(w, "File already exists", http.StatusBadRequest)
return
}
out, err := os.Create(filePath)
if err != nil {
http.Error(w, "Error creating file", http.StatusInternalServerError)
return
}
defer out.Close()
_, err = io.Copy(out, file)
if err != nil {
http.Error(w, "Error saving file", http.StatusInternalServerError)
return
}
LogSuccessUpload(getClientIP(r), handler.Filename)
}
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ip := getClientIP(r)
mu.Lock()
// Check if IP is banned
if banTime, banned := bannedIPs[ip]; banned {
fmt.Print("Banned IP: ", ip, " until ", banTime, "\n")
if time.Now().Before(banTime) {
bannedIPs[ip] = time.Now().Add(BanDuration)
http.Error(w, "You are banned", http.StatusForbidden)
mu.Unlock()
return
}
delete(bannedIPs, ip)
}
mu.Unlock()
authHeader := r.Header.Get("Authorization")
if !strings.HasPrefix(authHeader, "Bearer ") || strings.TrimPrefix(authHeader, "Bearer ") != os.Getenv("CCTV_BearerToken") {
LogFailedAttempt(ip)
mu.Lock()
failedAttempts[ip]++
if failedAttempts[ip] >= MaxFailedAttempts {
bannedIPs[ip] = time.Now().Add(BanDuration)
delete(failedAttempts, ip)
LogBan(ip)
}
mu.Unlock()
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
mu.Lock()
delete(failedAttempts, ip)
mu.Unlock()
next(w, r)
}
}
func HealthHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
// Get client IP address from request
func getClientIP(r *http.Request) string {
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return r.RemoteAddr // Fallback
}
return ip
}