Working
This commit is contained in:
parent
c67be49a46
commit
03251290fc
34
Dockerfile
Normal file
34
Dockerfile
Normal 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
|
||||||
@ -1,2 +1,10 @@
|
|||||||
# CCTV
|
# CCTV
|
||||||
|
|
||||||
|
## .env Configuration
|
||||||
|
```env
|
||||||
|
UploadDir=./upload
|
||||||
|
LOG_FILE=auth_failures.log
|
||||||
|
MaxFailedAttempts=3
|
||||||
|
BanDuration=5
|
||||||
|
BearerToken=jhasd083raASDasdoad§$234
|
||||||
|
````
|
||||||
41
cmd/main.go
Normal file
41
cmd/main.go
Normal 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
62
config/env.go
Normal 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
18
config/upload.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
13
internal/bans.go
Normal file
13
internal/bans.go
Normal 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
71
internal/log.go
Normal 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
131
internal/server.go
Normal 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
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user