add server/client tls certificates
This commit is contained in:
		
							parent
							
								
									54460c0f80
								
							
						
					
					
						commit
						d04107ca8b
					
				@ -7,8 +7,11 @@ GATEWAY_ROUTE_FILE_PATH=configs/routes.json
 | 
			
		||||
GATEWAY_GEO_DB_FILE_PATH=geodb/GeoLite2-Country.mmdb
 | 
			
		||||
 | 
			
		||||
### TLS SETTINGS
 | 
			
		||||
GATEWAY_TLS_FULLCHAIN_CERT=SSL/fullchain.pem
 | 
			
		||||
GATEWAY_TLS_PRIVATE_KEY=SSL/privkey.pem
 | 
			
		||||
GATEWAY_TLS_ROOT_CA=tls-certs/ca.crt
 | 
			
		||||
GATEWAY_TLS_CLIENT_CERT=tls-certs/client.crt
 | 
			
		||||
GATEWAY_TLS_CLIENT_KEY=tls-certs/client.key
 | 
			
		||||
GATEWAY_TLS_SERVER_CERT=tls-certs/service.crt
 | 
			
		||||
GATEWAY_TLS_SERVER_KEY=tls-certs/service.key
 | 
			
		||||
 | 
			
		||||
### LOG SETTINGS
 | 
			
		||||
GATEWAY_LOG_LEVEL=debug
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,3 +1,5 @@
 | 
			
		||||
.env
 | 
			
		||||
*.pem
 | 
			
		||||
*.sum
 | 
			
		||||
*.crt
 | 
			
		||||
*.key
 | 
			
		||||
*.mmdb
 | 
			
		||||
@ -7,10 +7,7 @@ COPY go.sum ./
 | 
			
		||||
RUN go mod download
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
RUN openssl req -x509 -newkey rsa:4096 \
 | 
			
		||||
    -nodes -keyout /app/SSL/privkey.pem \
 | 
			
		||||
    -out /app/SSL/fullchain.pem -days 365 \
 | 
			
		||||
    -subj "/CN=localhost"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
RUN CGO_ENABLED=0 GOOS=linux go build -o gateway ./cmd/gateway/main.go
 | 
			
		||||
 | 
			
		||||
@ -21,7 +18,6 @@ RUN adduser -HD server
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
COPY --from=builder /app/gateway /app/
 | 
			
		||||
COPY --from=builder /app/SSL /app/SSL
 | 
			
		||||
COPY ./configs /app/configs
 | 
			
		||||
COPY ./geodb /app/geodb
 | 
			
		||||
COPY ./.env /app/
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										138
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										138
									
								
								README.md
									
									
									
									
									
								
							@ -1,138 +0,0 @@
 | 
			
		||||
# Yobble Gateway (Go)
 | 
			
		||||
 | 
			
		||||
## О проекте
 | 
			
		||||
 | 
			
		||||
Это высокопроизводительный API-шлюз, написанный на Go. Он служит единой точкой входа для всех клиентских запросов и направляет их в соответствующие микросервисы.
 | 
			
		||||
 | 
			
		||||
## Основные возможности
 | 
			
		||||
 | 
			
		||||
* **Динамическая маршрутизация:** Маршруты определяются в файле `configs/routes.json` и могут быть легко изменены без перезапуска шлюза.
 | 
			
		||||
* **Балансировка нагрузки:** Простая балансировка нагрузки (в настоящее время случайный выбор) между несколькими экземплярами сервиса.
 | 
			
		||||
* **SSL/TLS Termination:** Шлюз обрабатывает HTTPS-запросы, снимая нагрузку по шифрованию с внутренних сервисов.
 | 
			
		||||
* **Фильтрация по GeoIP:** Возможность блокировать запросы из определенных стран.
 | 
			
		||||
* **Конфигурация через переменные окружения:** Удобная настройка для различных сред (разработка, продакшн).
 | 
			
		||||
* **Логирование:** Структурированное логирование с использованием `slog`.
 | 
			
		||||
* **Graceful Shutdown:** Корректное завершение работы приложения, позволяющее завершить обработку текущих запросов.
 | 
			
		||||
 | 
			
		||||
## Технологический стек
 | 
			
		||||
 | 
			
		||||
* **Go (Golang)**
 | 
			
		||||
* **Gorilla Mux** для маршрутизации
 | 
			
		||||
* **maxmind/geoip2-golang** для GeoIP
 | 
			
		||||
* **YAML** и **JSON** для конфигурации
 | 
			
		||||
* **Docker** для контейнеризации
 | 
			
		||||
 | 
			
		||||
## Структура проекта
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
.
 | 
			
		||||
├── cmd/gateway/main.go      # Точка входа в приложение
 | 
			
		||||
├── configs/                   # Файлы конфигурации
 | 
			
		||||
│   ├── config.yml             # Основной файл конфигурации
 | 
			
		||||
│   └── routes.json            # Файл с маршрутами
 | 
			
		||||
├── internal/                  # Внутренняя логика приложения
 | 
			
		||||
│   ├── config/                # Работа с конфигурацией
 | 
			
		||||
│   ├── logger/                # Логирование
 | 
			
		||||
│   ├── middleware/            # Промежуточное ПО (например, для получения Real IP)
 | 
			
		||||
│   ├── proxy/                 # Логика проксирования и маршрутизации
 | 
			
		||||
│   └── server/                # Настройка и запуск HTTP-сервера
 | 
			
		||||
├── pkg/                       # Пакеты, которые могут быть использованы в других проектах
 | 
			
		||||
│   └── geoip/                 # Работа с GeoIP
 | 
			
		||||
├── geodb/                     # База данных GeoIP
 | 
			
		||||
├── SSL/                       # SSL-сертификаты
 | 
			
		||||
├── .env.example               # Пример файла с переменными окружения
 | 
			
		||||
├── Dockerfile                 # Файл для сборки Docker-образа
 | 
			
		||||
└── go.mod                     # Зависимости проекта
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Быстрый старт
 | 
			
		||||
 | 
			
		||||
### 1. Клонирование репозитория
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
git clone <URL репозитория>
 | 
			
		||||
cd go-yobble-gateway
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 2. Конфигурация
 | 
			
		||||
 | 
			
		||||
Скопируйте файл с примером переменных окружения:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
cp .env.example .env
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Отредактируйте `.env` и `configs/config.yml` при необходимости.
 | 
			
		||||
 | 
			
		||||
### 3. Запуск с помощью Docker
 | 
			
		||||
 | 
			
		||||
Для сборки и запуска приложения в Docker-контейнере выполните:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
docker-compose up --build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 4. Локальный запуск (без Docker)
 | 
			
		||||
 | 
			
		||||
#### а) Установка зависимостей
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
go mod tidy
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### б) Запуск приложения
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
go run cmd/gateway/main.go --config=configs/config.yml
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Конфигурация
 | 
			
		||||
 | 
			
		||||
### Основная конфигурация (`config.yml`)
 | 
			
		||||
 | 
			
		||||
* `server`: Настройки хоста и порта сервера.
 | 
			
		||||
* `gateway`: Настройки шлюза, включая путь к файлу с маршрутами и базу GeoIP.
 | 
			
		||||
* `tls`: Пути к SSL-сертификатам.
 | 
			
		||||
* `logging`: Уровень и формат логирования.
 | 
			
		||||
 | 
			
		||||
### Переменные окружения (`.env`)
 | 
			
		||||
 | 
			
		||||
Переменные окружения, определенные в `.env`, переопределяют значения по умолчанию в `config.yml`.
 | 
			
		||||
 | 
			
		||||
### Маршрутизация (`routes.json`)
 | 
			
		||||
 | 
			
		||||
Файл `routes.json` определяет, как шлюз будет перенаправлять запросы.
 | 
			
		||||
 | 
			
		||||
**Пример:**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "v1": {
 | 
			
		||||
    "/auth": [
 | 
			
		||||
      {
 | 
			
		||||
        "url": "https://auth-service:5201",
 | 
			
		||||
        "role": "master",
 | 
			
		||||
        "allow_self_signed": true
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "default": {
 | 
			
		||||
    "/docs": [
 | 
			
		||||
      {
 | 
			
		||||
        "url": "https://docs-service:5199",
 | 
			
		||||
        "role": "slave",
 | 
			
		||||
        "allow_self_signed": true
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
* **Ключи верхнего уровня (`v1`, `default`)** - это версии API. Запросы, начинающиеся с `/v1/...`, будут сопоставляться с маршрутами в секции `"v1"`. Если версия не указана, используется секция `"default"`.
 | 
			
		||||
* **Ключи второго уровня (`/auth`, `/docs`)** - это префиксы путей. Шлюз ищет наиболее длинное совпадение префикса.
 | 
			
		||||
* **`url`** - URL внутреннего сервиса.
 | 
			
		||||
* **`allow_self_signed`** - Разрешает использование самоподписанных сертификатов для внутреннего сервиса.
 | 
			
		||||
 | 
			
		||||
## GeoIP Фильтрация
 | 
			
		||||
 | 
			
		||||
Шлюз может блокировать доступ для стран, перечисленных в `blocked_countries` в файле `config.yml`. Для работы этой функции необходима база данных `GeoLite2-Country.mmdb` от MaxMind.
 | 
			
		||||
@ -24,25 +24,24 @@ func main() {
 | 
			
		||||
		configPath = os.Getenv("CONFIG_PATH")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.InitLogger("info", "text")
 | 
			
		||||
 | 
			
		||||
	op := "main.main"
 | 
			
		||||
	log := logger.NewLoggerWithOp(op)
 | 
			
		||||
 | 
			
		||||
	cfg, err := config.Load(configPath)
 | 
			
		||||
	cfg, err := config.Load(log, configPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("failed to load configuration", slog.String("path", configPath), slog.Any("error", err))
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Повторно инициализируем логгер с настройками из конфигурации
 | 
			
		||||
	logger.InitLogger(cfg.Logging.Level, cfg.Logging.Format)
 | 
			
		||||
	log = logger.NewLoggerWithOp(op) // Повторно создаем логгер с обновленным уровнем и форматом
 | 
			
		||||
	logger.InitLogger(&cfg.Logger)
 | 
			
		||||
	log = logger.NewLoggerWithOp(op)
 | 
			
		||||
 | 
			
		||||
	log.Info("configuration loaded successfully")
 | 
			
		||||
	log.Debug("ROUTE CFG: ",
 | 
			
		||||
		slog.Any("routes", cfg.RouteConfig))
 | 
			
		||||
 | 
			
		||||
	// Инициализируем сервис GeoIP
 | 
			
		||||
	geoipService, err := geoip.NewGeoIPService(cfg.Gateway.GeoIPDB, cfg.Gateway.BlockedCountries)
 | 
			
		||||
	geoipService, err := geoip.NewGeoIPService(&cfg.Gateway)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("failed to initialize GeoIP service", slog.Any("error", err))
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
@ -54,7 +53,7 @@ func main() {
 | 
			
		||||
 | 
			
		||||
	// Запускаем сервер в отдельной горутине
 | 
			
		||||
	done := make(chan struct{})
 | 
			
		||||
	errChan := srv.Start(cfg.TLS.FullChain, cfg.TLS.PrivateKey)
 | 
			
		||||
	errChan := srv.Start(&cfg.TLS)
 | 
			
		||||
 | 
			
		||||
	// Настраиваем перехват системных сигналов для graceful shutdown
 | 
			
		||||
	sigChan := make(chan os.Signal, 1)
 | 
			
		||||
 | 
			
		||||
@ -1,18 +1,21 @@
 | 
			
		||||
server:
 | 
			
		||||
  host: ${SERVER_HOST:0.0.0.0}
 | 
			
		||||
  port: ${SERVER_PORT:5150}
 | 
			
		||||
  host: ${GATEWAY_SERVER_HOST:0.0.0.0}
 | 
			
		||||
  port: ${GATEWAY_SERVER_PORT:5150}
 | 
			
		||||
 | 
			
		||||
gateway:
 | 
			
		||||
  debug: false
 | 
			
		||||
  route_file: ${ROUTE_FILE_PATH:configs/routes.json}
 | 
			
		||||
  geoip_db: ${GEO_DB_FILE_PATH:geodb/GeoLite2-Country.mmdb}
 | 
			
		||||
  route_file: ${GATEWAY_ROUTE_FILE_PATH:configs/routes.json}
 | 
			
		||||
  geoip_db: ${GATEWAY_GEO_DB_FILE_PATH:geodb/GeoLite2-Country.mmdb}
 | 
			
		||||
  blocked_countries:
 | 
			
		||||
    - US
 | 
			
		||||
 | 
			
		||||
tls:
 | 
			
		||||
  full_chain: ${TLS_FULLCHAIN_CERT:SSL/fullchain.pem}
 | 
			
		||||
  private_key: ${TLS_PRIVATE_KEY:SSL/privkey.pem}
 | 
			
		||||
  root_ca: ${GATEWAY_TLS_ROOT_CA:tls-certs/ca.crt}
 | 
			
		||||
  client_cert: ${GATEWAY_TLS_CLIENT_CERT:tls-certs/client.crt}
 | 
			
		||||
  client_key: ${GATEWAY_TLS_CLIENT_KEY:tls-certs/client.key}
 | 
			
		||||
  server_cert: ${GATEWAY_TLS_SERVER_CERT:tls-certs/service.crt}
 | 
			
		||||
  server_key: ${GATEWAY_TLS_SERVER_KEY:tls-certs/service.key}
 | 
			
		||||
 | 
			
		||||
logging:
 | 
			
		||||
  level: ${LOG_LEVEL:debug} # debug, info, warn, error
 | 
			
		||||
  format: ${LOG_FORMAT:text} # text, json
 | 
			
		||||
  level: ${GATEWAY_LOG_LEVEL:info} # debug, info, warn, error
 | 
			
		||||
  format: ${GATEWAY_LOG_FORMAT:text} # text, json
 | 
			
		||||
 | 
			
		||||
@ -2,37 +2,42 @@
 | 
			
		||||
  "v1": {
 | 
			
		||||
    "/auth": [
 | 
			
		||||
      {
 | 
			
		||||
        "url": "https://0.0.0.0:5201",
 | 
			
		||||
        "url": "https://yobble-auth-service:5201",
 | 
			
		||||
        "role": "master",
 | 
			
		||||
        "allow_self_signed": true
 | 
			
		||||
        "yobble_signed": true,
 | 
			
		||||
        "allow_untrusted": false
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "/user": [
 | 
			
		||||
      {
 | 
			
		||||
        "url": "https://yobble-user-service:5202",
 | 
			
		||||
        "role": "master",
 | 
			
		||||
        "allow_self_signed": true
 | 
			
		||||
        "yobble_signed": true,
 | 
			
		||||
        "allow_untrusted": false
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "/profile": [
 | 
			
		||||
      {
 | 
			
		||||
        "url": "https://yobble-profile-service:5203",
 | 
			
		||||
        "role": "master",
 | 
			
		||||
        "allow_self_signed": true
 | 
			
		||||
        "yobble_signed": true,
 | 
			
		||||
        "allow_untrusted": false
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "/feed": [
 | 
			
		||||
      {
 | 
			
		||||
        "url": "https://yobble-feed-service:5204",
 | 
			
		||||
        "role": "master",
 | 
			
		||||
        "allow_self_signed": true
 | 
			
		||||
        "yobble_signed": true,
 | 
			
		||||
        "allow_untrusted": false
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "/chat/private": [
 | 
			
		||||
      {
 | 
			
		||||
        "url": "https://localhost:5205",
 | 
			
		||||
        "role": "master",
 | 
			
		||||
        "allow_self_signed": true
 | 
			
		||||
        "yobble_signed": true,
 | 
			
		||||
        "allow_untrusted": false
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
@ -41,7 +46,8 @@
 | 
			
		||||
      {
 | 
			
		||||
        "url": "http://auth_v2_service:5301",
 | 
			
		||||
        "role": "master",
 | 
			
		||||
        "allow_self_signed": false
 | 
			
		||||
        "yobble_signed": true,
 | 
			
		||||
        "allow_untrusted": false
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
@ -50,14 +56,16 @@
 | 
			
		||||
      {
 | 
			
		||||
        "url": "https://yobble-docs-service:5199",
 | 
			
		||||
        "role": "slave",
 | 
			
		||||
        "allow_self_signed": true
 | 
			
		||||
        "yobble_signed": true,
 | 
			
		||||
        "allow_untrusted": false
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "/test": [
 | 
			
		||||
      {
 | 
			
		||||
        "url": "https://localhost:9097",
 | 
			
		||||
        "role": "test",
 | 
			
		||||
        "allow_self_signed": true
 | 
			
		||||
        "yobble_signed": true,
 | 
			
		||||
        "allow_untrusted": false
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ services:
 | 
			
		||||
    env_file:
 | 
			
		||||
      - .env
 | 
			
		||||
#    ПИТОНИСТ ЧТОБЫ ПРОКИНУТЬ SSL или иные конфиги примонтируй соответствующие volume
 | 
			
		||||
#    volumes:
 | 
			
		||||
#      - ./configs:/app/configs
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./tls-certs:/app/tls-certs
 | 
			
		||||
#      - ./geodb:/app/geodb
 | 
			
		||||
#      - ./SSL:/app/SSL
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										24
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 | 
			
		||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
			
		||||
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
 | 
			
		||||
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
 | 
			
		||||
github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w=
 | 
			
		||||
github.com/oschwald/geoip2-golang v1.11.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
 | 
			
		||||
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
 | 
			
		||||
github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 | 
			
		||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
 | 
			
		||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
 | 
			
		||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
 | 
			
		||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 | 
			
		||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
 | 
			
		||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
@ -3,19 +3,18 @@ package config
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"github.com/joho/godotenv"
 | 
			
		||||
	"gopkg.in/yaml.v3"
 | 
			
		||||
	"log/slog"
 | 
			
		||||
	"os"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"yobble-gateway-go/internal/logger"
 | 
			
		||||
 | 
			
		||||
	"gopkg.in/yaml.v3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Backend представляет собой отдельный сервер бэкенда.
 | 
			
		||||
type Backend struct {
 | 
			
		||||
	URL            string `json:"url"`
 | 
			
		||||
	Role           string `json:"role"`
 | 
			
		||||
	AllowSelfSigned bool   `json:"allow_self_signed"`
 | 
			
		||||
	YobbleSigned   bool   `json:"yobble_signed"`
 | 
			
		||||
	AllowUntrusted bool   `json:"allow_untrusted"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Routes сопоставляет строки версий с префиксами маршрутов и бэкендами.
 | 
			
		||||
@ -36,8 +35,11 @@ type GatewayConfig struct {
 | 
			
		||||
 | 
			
		||||
// TLSConfig содержит настройки, связанные с TLS.
 | 
			
		||||
type TLSConfig struct {
 | 
			
		||||
	FullChain  string `yaml:"full_chain"`
 | 
			
		||||
	PrivateKey string `yaml:"private_key"`
 | 
			
		||||
	RootCA     string `yaml:"root_ca"`
 | 
			
		||||
	ClientCert string `yaml:"client_cert"`
 | 
			
		||||
	ClientKey  string `yaml:"client_key"`
 | 
			
		||||
	ServerCert string `yaml:"server_cert"`
 | 
			
		||||
	ServerKey  string `yaml:"server_key"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LogConfig содержит настройки, связанные с логированием.
 | 
			
		||||
@ -51,15 +53,15 @@ type Settings struct {
 | 
			
		||||
	Server  ServerConfig  `yaml:"server"`
 | 
			
		||||
	Gateway GatewayConfig `yaml:"gateway"`
 | 
			
		||||
	TLS     TLSConfig     `yaml:"tls"`
 | 
			
		||||
	Logging LogConfig     `yaml:"logging"`
 | 
			
		||||
	Logger  LogConfig     `yaml:"logging"`
 | 
			
		||||
 | 
			
		||||
	RouteConfig Routes `yaml:"-"` // Загружается отдельно, не из корня YAML
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Load возвращает новый объект конфигурации, считывая YAML-файл.
 | 
			
		||||
func Load(configPath string) (*Settings, error) {
 | 
			
		||||
	op := "config.Load"
 | 
			
		||||
	log := logger.NewLoggerWithOp(op)
 | 
			
		||||
func Load(log *slog.Logger, configPath string) (*Settings, error) {
 | 
			
		||||
	const op = "config.Load"
 | 
			
		||||
	log = log.With(slog.String("op", op))
 | 
			
		||||
 | 
			
		||||
	if err := godotenv.Load(); err != nil {
 | 
			
		||||
		log.Warn("[Attention]: No .env file found or failed to load, using STANDARD settings!")
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ import (
 | 
			
		||||
	"log/slog"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"yobble-gateway-go/internal/config"
 | 
			
		||||
 | 
			
		||||
	"github.com/lmittmann/tint"
 | 
			
		||||
)
 | 
			
		||||
@ -11,9 +12,9 @@ import (
 | 
			
		||||
var defaultLogger *slog.Logger
 | 
			
		||||
 | 
			
		||||
// InitLogger инициализирует логгер slog по умолчанию на основе предоставленного уровня и формата.
 | 
			
		||||
func InitLogger(levelStr, format string) *slog.Logger {
 | 
			
		||||
func InitLogger(cfg *config.LogConfig) *slog.Logger {
 | 
			
		||||
	var level slog.Level
 | 
			
		||||
	switch strings.ToLower(levelStr) {
 | 
			
		||||
	switch strings.ToLower(cfg.Level) {
 | 
			
		||||
	case "debug":
 | 
			
		||||
		level = slog.LevelDebug
 | 
			
		||||
	case "info":
 | 
			
		||||
@ -29,7 +30,7 @@ func InitLogger(levelStr, format string) *slog.Logger {
 | 
			
		||||
	var handler slog.Handler
 | 
			
		||||
	output := os.Stdout
 | 
			
		||||
 | 
			
		||||
	switch strings.ToLower(format) {
 | 
			
		||||
	switch strings.ToLower(cfg.Format) {
 | 
			
		||||
	case "json":
 | 
			
		||||
		handler = slog.NewJSONHandler(output, &slog.HandlerOptions{
 | 
			
		||||
			Level: level,
 | 
			
		||||
@ -57,7 +58,11 @@ func InitLogger(levelStr, format string) *slog.Logger {
 | 
			
		||||
func NewLoggerWithOp(op string) *slog.Logger {
 | 
			
		||||
	if defaultLogger == nil {
 | 
			
		||||
		// Fallback if InitLogger was not called, though it should be.
 | 
			
		||||
		defaultLogger = InitLogger("info", "text")
 | 
			
		||||
		logConfig := config.LogConfig{
 | 
			
		||||
			Level:  "info",
 | 
			
		||||
			Format: "text",
 | 
			
		||||
		}
 | 
			
		||||
		defaultLogger = InitLogger(&logConfig)
 | 
			
		||||
	}
 | 
			
		||||
	return defaultLogger.With(slog.String("op", op))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,13 @@ package proxy
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log/slog"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httputil"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"yobble-gateway-go/internal/config"
 | 
			
		||||
	"yobble-gateway-go/internal/logger"
 | 
			
		||||
@ -21,6 +24,7 @@ func NewProxyHandler(cfg *config.Settings, geoIPService *geoip.GeoIPService) htt
 | 
			
		||||
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		// Проверка GeoIP
 | 
			
		||||
		realIP := middleware.GetRealIP(r)
 | 
			
		||||
		log.Debug("real IP", slog.String("ip", realIP))
 | 
			
		||||
		if realIP != "" {
 | 
			
		||||
			isBlocked, countryCode := geoIPService.IsCountryBlocked(realIP)
 | 
			
		||||
			if isBlocked {
 | 
			
		||||
@ -73,10 +77,21 @@ func NewProxyHandler(cfg *config.Settings, geoIPService *geoip.GeoIPService) htt
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Пользовательский транспорт для обработки allow_self_signed
 | 
			
		||||
		if backend.AllowSelfSigned {
 | 
			
		||||
		if backend.YobbleSigned {
 | 
			
		||||
			tlsConfig, err := newTLSConfig(&cfg.TLS, backendURL.Host, backend.AllowUntrusted)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Error("failed to create TLS config", slog.Any("error", err))
 | 
			
		||||
				http.Error(w, "Upstream error", http.StatusBadGateway)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			proxy.Transport = &http.Transport{
 | 
			
		||||
				TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
 | 
			
		||||
				TLSClientConfig: tlsConfig,
 | 
			
		||||
			}
 | 
			
		||||
		} else if backend.AllowUntrusted {
 | 
			
		||||
			proxy.Transport = &http.Transport{
 | 
			
		||||
				TLSClientConfig: &tls.Config{
 | 
			
		||||
					InsecureSkipVerify: true,
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -84,3 +99,27 @@ func NewProxyHandler(cfg *config.Settings, geoIPService *geoip.GeoIPService) htt
 | 
			
		||||
		proxy.ServeHTTP(w, r)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newTLSConfig(cfg *config.TLSConfig, serverName string, allowUntrusted bool) (*tls.Config, error) {
 | 
			
		||||
	clientCert, err := tls.LoadX509KeyPair(cfg.ClientCert, cfg.ClientKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to load client certificate: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	caCert, err := os.ReadFile(cfg.RootCA)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to read CA certificate: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	caCertPool := x509.NewCertPool()
 | 
			
		||||
	if !caCertPool.AppendCertsFromPEM(caCert) {
 | 
			
		||||
		return nil, fmt.Errorf("failed to append CA certificate to pool")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &tls.Config{
 | 
			
		||||
		Certificates:       []tls.Certificate{clientCert},
 | 
			
		||||
		RootCAs:            caCertPool,
 | 
			
		||||
		ServerName:         serverName,
 | 
			
		||||
		InsecureSkipVerify: allowUntrusted,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -57,14 +57,14 @@ func NewServer(cfg *config.Settings, geoIPService *geoip.GeoIPService) *Server {
 | 
			
		||||
 | 
			
		||||
// Start запускает HTTP/HTTPS сервер в отдельной горутине.
 | 
			
		||||
// Он возвращает канал, который будет закрыт, когда сервер завершит работу.
 | 
			
		||||
func (s *Server) Start(fullchain, privkey string) <-chan error {
 | 
			
		||||
func (s *Server) Start(cfg *config.TLSConfig) <-chan error {
 | 
			
		||||
	errChan := make(chan error, 1)
 | 
			
		||||
 | 
			
		||||
	s.log.Info("server starting with TLS and HTTP/2", slog.String("address", s.httpServer.Addr))
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		defer close(errChan)
 | 
			
		||||
		if err := s.httpServer.ListenAndServeTLS(fullchain, privkey); err != nil && !errors.Is(err, http.ErrServerClosed) {
 | 
			
		||||
		if err := s.httpServer.ListenAndServeTLS(cfg.ServerCert, cfg.ServerKey); err != nil && !errors.Is(err, http.ErrServerClosed) {
 | 
			
		||||
			s.log.Error("server failed to listen and serve", slog.Any("error", err))
 | 
			
		||||
			errChan <- err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ package geoip
 | 
			
		||||
import (
 | 
			
		||||
	"log/slog"
 | 
			
		||||
	"net"
 | 
			
		||||
	"yobble-gateway-go/internal/config"
 | 
			
		||||
	"yobble-gateway-go/internal/logger"
 | 
			
		||||
 | 
			
		||||
	"github.com/oschwald/geoip2-golang"
 | 
			
		||||
@ -15,22 +16,22 @@ type GeoIPService struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewGeoIPService создает новый GeoIPService.
 | 
			
		||||
func NewGeoIPService(dbPath string, blockedCountries []string) (*GeoIPService, error) {
 | 
			
		||||
func NewGeoIPService(cfg *config.GatewayConfig) (*GeoIPService, error) {
 | 
			
		||||
	op := "geoip.NewGeoIPService"
 | 
			
		||||
	log := logger.NewLoggerWithOp(op)
 | 
			
		||||
 | 
			
		||||
	reader, err := geoip2.Open(dbPath)
 | 
			
		||||
	reader, err := geoip2.Open(cfg.GeoIPDB)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("failed to open GeoIP database", slog.String("db_path", dbPath), slog.Any("error", err))
 | 
			
		||||
		log.Error("failed to open GeoIP database", slog.String("db_path", cfg.GeoIPDB), slog.Any("error", err))
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	blockedMap := make(map[string]struct{})
 | 
			
		||||
	for _, country := range blockedCountries {
 | 
			
		||||
	for _, country := range cfg.BlockedCountries {
 | 
			
		||||
		blockedMap[country] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Info("GeoIP service initialized", slog.String("db_path", dbPath), slog.Any("blocked_countries", blockedCountries))
 | 
			
		||||
	log.Info("GeoIP service initialized", slog.String("db_path", cfg.GeoIPDB), slog.Any("blocked_countries", cfg.BlockedCountries))
 | 
			
		||||
	return &GeoIPService{
 | 
			
		||||
			reader:           reader,
 | 
			
		||||
			blockedCountries: blockedMap,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user