Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sprint 10, increment 24 #26

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ var (
// setupServer configures and returns a new HTTP router with middleware and routes.
// It includes logging, compression, optional HMAC checking, and controller routes.
func setupServer(
logger logging.ILogger, controller *usecases.BaseController, hmacKey string, key *rsa.PrivateKey,
logger logging.ILogger, controller *usecases.BaseController, hmacKey, subnet string, key *rsa.PrivateKey,
) *chi.Mux {
r := chi.NewRouter()
r.Use(logging.Middleware(logger))
Expand All @@ -45,6 +45,9 @@ func setupServer(
if key != nil {
r.Use(secure.MiddlewareCryptoReader(key))
}
if subnet != "" {
r.Use(secure.MiddlewareIPFilter(logger, subnet))
}
r.Mount("/", controller.Route())
return r
}
Expand Down Expand Up @@ -137,7 +140,7 @@ func main() {
if err != nil {
panic(err)
}
r := setupServer(logger, controller, conf.HMACKey, key)
r := setupServer(logger, controller, conf.HMACKey, conf.TrustedSubnet, key)
srv := http.Server{Addr: conf.Addr, Handler: r}
go func() {
logger.Infof("Launching the server at %s\n", conf.Addr)
Expand Down
47 changes: 47 additions & 0 deletions internal/agent/adapters/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import (
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"

Expand Down Expand Up @@ -173,6 +175,12 @@ func (r *HTTPReportAdapter) createRequest(path url.URL, payload []byte) (*http.R
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Content-Encoding", "gzip")
if addr, err := getLocalIP(); err != nil {
r.Logger.Errorf("Failed to add X-Real-IP header: %v", err)
return nil, err
} else {
req.Header.Add("X-Real-IP", addr)
}
return req, nil
}

Expand Down Expand Up @@ -249,3 +257,42 @@ func encryptAES(plaintext []byte) ([]byte, []byte, error) {

return key, ciphertext, nil
}

func getLocalIP() (string, error) {
interfaces, err := net.Interfaces()
if err != nil {
return "", err
}

for _, iface := range interfaces {
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
continue // interface down or loopback
}

// Get associated unicast interface addresses.
addrs, err := iface.Addrs()
if err != nil {
return "", err
}

// Iterate over the addresses looking for a non-loopback IPv4 address.
for _, addr := range addrs {
ip := getIPFromAddr(addr)
if ip != nil && !ip.IsLoopback() && ip.To4() != nil {
return ip.String(), nil // return the first non-loopback IPv4 address
}
}
}
return "", fmt.Errorf("no active network interface found")
}

func getIPFromAddr(addr net.Addr) net.IP {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
return ip
}
5 changes: 5 additions & 0 deletions internal/infra/config/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
const (
templatePath = "web/template/"
DefAddr = "localhost:8080"
DefTrustedSubnet = ""
DefStoreInterval = 300
DefFileStoragePath = "/tmp/metrics-db.json"
DefRestore = true
Expand Down Expand Up @@ -56,6 +57,9 @@ type Config struct {
// CryptoKey is used for payload decryption
CryptoKey string `env:"CRYPTO_KEY" json:"crypto_key"`

// TrustedSubnet is used for allowing only certain client IP addresses access the server
TrustedSubnet string `env:"TRUSTED_SUBNET" json:"trusted_subnet"`

// RetryAttempts is the number of retry attempts for failed requests.
RetryAttempts int

Expand Down Expand Up @@ -99,6 +103,7 @@ func InitConfig() (*Config, error) {
flag.UintVar(&conf.StoreInterval, "i", DefStoreInterval, "How often to store data in the file")
flag.StringVar(&conf.HMACKey, "k", "", "HMAC key for integrity checks")
flag.StringVar(&conf.CryptoKey, "crypto-key", "", "Path to a file with the server private key")
flag.StringVar(&conf.TrustedSubnet, "t", DefTrustedSubnet, "The subnet from which the server accepts requests")
flag.Parse()
if jsonConfigPath, ok := os.LookupEnv("CONFIG"); ok {
conf.ConfigPath = jsonConfigPath
Expand Down
1 change: 1 addition & 0 deletions internal/infra/config/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ func TestInitConfigWithJSON(t *testing.T) {
ConfigPath: "/tmp/test-monitoring-server-config-priority-env.json", // from env
Addr: "0.0.0.0:8901", // from cmd
CryptoKey: "max.key", // from JSON
TrustedSubnet: DefTrustedSubnet,
StoreInterval: DefStoreInterval,
FileStoragePath: DefFileStoragePath,
Restore: DefRestore,
Expand Down
41 changes: 41 additions & 0 deletions internal/infra/secure/subnet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Package secure provides middleware for filtering clients' requests based on their IP addresses
package secure

import (
"net"
"net/http"

"github.com/matthiasBT/monitoring/internal/infra/logging"
)

// MiddlewareIPFilter either allows a client to proceed with its request or blocks it if the client's IP is not trusted
func MiddlewareIPFilter(logger logging.ILogger, rawSubnet string) func(next http.Handler) http.Handler {
if rawSubnet == "" {
panic("Empty subnet string")
}
_, subnet, err := net.ParseCIDR(rawSubnet)
if err != nil {
panic("Invalid CIDR notation")
}
return func(next http.Handler) http.Handler {
checkIP := func(w http.ResponseWriter, r *http.Request) {
clientIPRaw := r.Header.Get("X-Real-IP")
logger.Infof("Client IP address: %s", clientIPRaw)
if clientIPRaw == "" {
http.Error(w, "Missing X-Real-IP header value", http.StatusForbidden)
return
}
var clientIP = net.ParseIP(clientIPRaw)
if clientIP == nil {
http.Error(w, "Invalid X-Real-IP header value", http.StatusForbidden)
return
}
if !subnet.Contains(clientIP) {
http.Error(w, "Untrusted client IP address", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(checkIP)
}
}
Loading