Skip to content
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
13 changes: 13 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
TOKEN_HOST=token
TOKEN_PORT=50051
TOKEN_MOCK=true
TOKEN_DEBUG=false
AWS_TOKEN_KEY_ID=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
Expand All @@ -10,6 +11,18 @@ FORTANIX_ENDPOINT=
FORTANIX_API_KEY=
FORTANIX_KEY_ID=

# token TLS (mTLS) — server
# Generate certs once with: scripts/gen-certs.sh (see README for instructions)
TOKEN_TLS_ENABLED=false
TOKEN_TLS_CERT_FILE=/certs/server.crt
TOKEN_TLS_KEY_FILE=/certs/server.key
TOKEN_TLS_CA_FILE=/certs/ca.crt

# token TLS — clients (generator + preauth)
TOKEN_TLS_CLIENT_CA_FILE=/certs/ca.crt
TOKEN_TLS_CLIENT_CERT_FILE=/certs/client.crt
TOKEN_TLS_CLIENT_KEY_FILE=/certs/client.key

# generator:
SERVER_DB_HOST=server-db
SERVER_DB_PORT=3306
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ go.work.sum

# env file
.env

# TLS certs
certs/
4 changes: 2 additions & 2 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ tasks:
- |
find . -name 'go.mod' -exec dirname {} \; | while read module_dir; do
echo "Running go modernize in $module_dir"
(cd "$module_dir" && go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./...)
(cd "$module_dir" && go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.21.1 -test ./...)
done
modernize_fix:
cmds:
- |
find . -name 'go.mod' -exec dirname {} \; | while read module_dir; do
echo "Running go modernize_fix in $module_dir"
(cd "$module_dir" && go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...)
(cd "$module_dir" && go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@v0.21.1 -fix -test ./...)
done
test:
cmds:
Expand Down
5 changes: 5 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ services:
restart: unless-stopped
networks:
server:
volumes:
- ./certs:/certs:ro

generator:
build:
Expand All @@ -25,6 +27,7 @@ services:
server:
volumes:
- data:/app/data
- ./certs:/certs:ro

distributor:
build:
Expand Down Expand Up @@ -52,6 +55,8 @@ services:
- ${PREAUTH_GET_PORT}:${PREAUTH_GET_PORT}
networks:
server:
volumes:
- ./certs:/certs:ro

verifier:
build:
Expand Down
9 changes: 0 additions & 9 deletions services/distributor/api/routes.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package api

import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/healthcheck"
"github.com/gofiber/fiber/v2/middleware/helmet"
"ivpn.net/auth/services/distributor/config"
Expand All @@ -10,14 +9,6 @@ import (
)

func (h *Handler) SetupRoutes(cfg config.APIConfig) {
h.Server.Get("/debug-ip", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{
"ip": c.IP(),
"x-forwarded-for": c.Get("X-Forwarded-For"),
"x-real-ip": c.Get("X-Real-IP"),
})
})

h.Server.Use(helmet.New())
h.Server.Use(healthcheck.New())
h.Server.Use(auth.NewIPFilter(cfg.ApiAllowIPs))
Expand Down
12 changes: 12 additions & 0 deletions services/distributor/config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"errors"
"os"
"strings"
)
Expand Down Expand Up @@ -29,3 +30,14 @@ func New() (Config, error) {
},
}, nil
}

// Validate checks that all required configuration values are present.
func (c Config) Validate() error {
if c.API.Port == "" {
return errors.New("required env var not set: DISTRIBUTOR_PORT")
}
if c.API.PSK == "" {
return errors.New("required env var not set: DISTRIBUTOR_PSK")
}
return nil
}
8 changes: 6 additions & 2 deletions services/distributor/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ import (
func main() {
cfg, err := config.New()
if err != nil {
log.Println(err)
log.Fatal(err)
}

if err := cfg.Validate(); err != nil {
log.Fatal("configuration error: ", err)
}

service := service.New(cfg)

err = api.Start(cfg.API, service)
if err != nil {
log.Println(err)
log.Fatal(err)
}
}
7 changes: 6 additions & 1 deletion services/distributor/middleware/auth/auth.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package auth

import (
"crypto/subtle"
"slices"
"strings"

Expand All @@ -10,6 +11,10 @@ import (
func NewIPFilter(allowedIPs []string) fiber.Handler {

return func(c *fiber.Ctx) error {
if slices.Contains(allowedIPs, "*") {
return c.Next()
}

clientIP := c.IP()
if slices.Contains(allowedIPs, clientIP) {
return c.Next()
Expand All @@ -22,7 +27,7 @@ func NewIPFilter(allowedIPs []string) fiber.Handler {
func NewPSK(psk string) fiber.Handler {

return func(c *fiber.Ctx) error {
if GetToken(c) == psk {
if subtle.ConstantTimeCompare([]byte(GetToken(c)), []byte(psk)) == 1 {
return c.Next()
}

Expand Down
10 changes: 5 additions & 5 deletions services/distributor/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package service

import (
"encoding/json"
"fmt"
"io"
"log"
"os"
Expand Down Expand Up @@ -31,21 +32,20 @@ func (s *Service) GetManifest() (model.Manifest, error) {
// Open the JSON file
file, err := os.Open(path)
if err != nil {
log.Println("failed to open file:", err)
return model.Manifest{}, fmt.Errorf("failed to open manifest: %w", err)
}
defer file.Close()

// Read file contents
bytes, err := io.ReadAll(file)
if err != nil {
log.Println("failed to read file:", err)
return model.Manifest{}, fmt.Errorf("failed to read manifest: %w", err)
}

// Unmarshal JSON into Manifest struct
var manifest model.Manifest
err = json.Unmarshal(bytes, &manifest)
if err != nil {
log.Println("failed to unmarshal JSON:", err)
if err = json.Unmarshal(bytes, &manifest); err != nil {
return model.Manifest{}, fmt.Errorf("failed to unmarshal manifest: %w", err)
}

return manifest, nil
Expand Down
46 changes: 45 additions & 1 deletion services/generator/client/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ package client

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"log"
"os"

"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"ivpn.net/auth/services/generator/config"
proto "ivpn.net/auth/services/proto"
Expand Down Expand Up @@ -42,10 +48,48 @@ func (c *TokenClient) GenerateToken(input string) (string, error) {

func connect(cfg config.TokenServerConfig) (*grpc.ClientConn, error) {
address := cfg.Host + ":" + cfg.Port
conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))

var creds grpc.DialOption
if cfg.TLSEnabled {
tlsCfg, err := buildClientTLS(cfg)
if err != nil {
return nil, fmt.Errorf("tls config: %w", err)
}
creds = grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg))
} else {
if os.Getenv("GENERATOR_ALLOW_INSECURE") != "true" {
return nil, errors.New("TLS is disabled but GENERATOR_ALLOW_INSECURE is not set to 'true'; refusing insecure connection")
}
log.Println("WARNING: gRPC connection to token server is unencrypted (TLS disabled)")
creds = grpc.WithTransportCredentials(insecure.NewCredentials())
}

conn, err := grpc.NewClient(address, creds)
if err != nil {
return nil, fmt.Errorf("failed to connect to gRPC server at %s: %w", address, err)
}

return conn, nil
}

func buildClientTLS(cfg config.TokenServerConfig) (*tls.Config, error) {
caPEM, err := os.ReadFile(cfg.TLSCACertFile)
if err != nil {
return nil, fmt.Errorf("read CA cert: %w", err)
}
caPool := x509.NewCertPool()
if !caPool.AppendCertsFromPEM(caPEM) {
return nil, errors.New("failed to parse CA certificate")
}

cert, err := tls.LoadX509KeyPair(cfg.TLSCertFile, cfg.TLSKeyFile)
if err != nil {
return nil, fmt.Errorf("load client cert/key: %w", err)
}

return &tls.Config{
RootCAs: caPool,
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
}, nil
}
50 changes: 46 additions & 4 deletions services/generator/config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"errors"
"os"
"strconv"
)
Expand All @@ -14,8 +15,12 @@ type DBConfig struct {
}

type TokenServerConfig struct {
Host string
Port string
Host string
Port string
TLSEnabled bool
TLSCACertFile string
TLSCertFile string
TLSKeyFile string
}

type ServiceConfig struct {
Expand All @@ -38,8 +43,12 @@ func New() (Config, error) {

return Config{
TokenServer: TokenServerConfig{
Host: os.Getenv("TOKEN_HOST"),
Port: os.Getenv("TOKEN_PORT"),
Host: os.Getenv("TOKEN_HOST"),
Port: os.Getenv("TOKEN_PORT"),
TLSEnabled: os.Getenv("TOKEN_TLS_ENABLED") == "true",
TLSCACertFile: os.Getenv("TOKEN_TLS_CLIENT_CA_FILE"),
TLSCertFile: os.Getenv("TOKEN_TLS_CLIENT_CERT_FILE"),
TLSKeyFile: os.Getenv("TOKEN_TLS_CLIENT_KEY_FILE"),
},
DB: DBConfig{
Host: os.Getenv("SERVER_DB_HOST"),
Expand All @@ -55,3 +64,36 @@ func New() (Config, error) {
},
}, nil
}

// Validate checks that all required configuration values are present.
func (c Config) Validate() error {
required := map[string]string{
"TOKEN_HOST": c.TokenServer.Host,
"TOKEN_PORT": c.TokenServer.Port,
"SERVER_DB_HOST": c.DB.Host,
"SERVER_DB_PORT": c.DB.Port,
"SERVER_DB_NAME": c.DB.Name,
"SERVER_DB_USER": c.DB.User,
"SERVER_DB_PASSWORD": c.DB.Password,
}
for name, val := range required {
if val == "" {
return errors.New("required env var not set: " + name)
}
}
if c.Service.TPS <= 0 {
return errors.New("GENERATOR_TPS must be a positive integer")
}
if c.TokenServer.TLSEnabled {
if c.TokenServer.TLSCACertFile == "" {
return errors.New("required env var not set: TOKEN_TLS_CLIENT_CA_FILE")
}
if c.TokenServer.TLSCertFile == "" {
return errors.New("required env var not set: TOKEN_TLS_CLIENT_CERT_FILE")
}
if c.TokenServer.TLSKeyFile == "" {
return errors.New("required env var not set: TOKEN_TLS_CLIENT_KEY_FILE")
}
}
return nil
}
17 changes: 10 additions & 7 deletions services/generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ import (
func main() {
cfg, err := config.New()
if err != nil {
log.Println(err)
log.Fatal(err)
}

if err := cfg.Validate(); err != nil {
log.Fatal("configuration error: ", err)
}

db, err := repository.NewDB(cfg)
if err != nil {
log.Println(err)
log.Fatal(err)
}

tokenClient, err := client.New(cfg.TokenServer)
if err != nil {
log.Println(err)
log.Fatal(err)
}

service := service.New(cfg, db, tokenClient)
Expand All @@ -34,7 +38,7 @@ func main() {
switch args[1] {
case "sync":
if err := service.Generate(); err != nil {
log.Println(err)
log.Fatal(err)
}
return

Expand All @@ -47,8 +51,7 @@ func main() {
}
}

err = service.Start()
if err != nil {
log.Println(err)
if err = service.Start(); err != nil {
log.Fatal(err)
}
}
Loading
Loading