feat: add security system with system-wide proxy, DNS protection and encryption

- System-wide proxy: automatic Windows proxy configuration for all apps
- DNS leak protection: force all DNS queries through VPN
- Config encryption: AES-256-GCM encryption for all config files
- File protection: strict access permissions for config directory
- Leak detection: built-in security check system
- Kill Switch: temporarily disabled (will be improved in next version)

Security features:
✓ Automatic system proxy setup
✓ DNS leak protection (optional)
✓ AES-256-GCM config encryption
✓ File and directory protection
✓ Security leak checker
⚠ Kill Switch disabled (caused internet blocking issues)

Emergency recovery scripts included:
- ОТКЛЮЧИТЬ_KILLSWITCH.bat
- EMERGENCY_FIX_INTERNET.bat
- ЕСЛИ_СЛОМАЛСЯ_ИНТЕРНЕТ.txt

Documentation:
- Markdown/SECURITY_GUIDE.md - full security guide
- БЕЗОПАСНОСТЬ_БЫСТРЫЙ_СТАРТ.md - quick start guide
- CHANGELOG_SECURITY.md - detailed changelog
This commit is contained in:
2026-04-12 19:01:24 +06:00
parent 20d24a3639
commit b809e84220
18 changed files with 2063 additions and 31 deletions

240
internal/security/dns.go Normal file
View File

@@ -0,0 +1,240 @@
package security
import (
"fmt"
"os/exec"
"runtime"
"strings"
)
// DNSProtection управляет защитой от DNS утечек
type DNSProtection struct {
originalDNS []string
enabled bool
}
// NewDNSProtection создает новый объект защиты DNS
func NewDNSProtection() *DNSProtection {
return &DNSProtection{
originalDNS: []string{},
enabled: false,
}
}
// Enable включает защиту от DNS утечек
func (dp *DNSProtection) Enable(vpnDNS []string) error {
if runtime.GOOS != "windows" {
return dp.enableUnix(vpnDNS)
}
return dp.enableWindows(vpnDNS)
}
// Disable отключает защиту и восстанавливает оригинальные DNS
func (dp *DNSProtection) Disable() error {
if runtime.GOOS != "windows" {
return dp.disableUnix()
}
return dp.disableWindows()
}
// enableWindows включает защиту DNS на Windows
func (dp *DNSProtection) enableWindows(vpnDNS []string) error {
// Получаем список активных сетевых интерфейсов
cmd := exec.Command("netsh", "interface", "show", "interface")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("ошибка получения списка интерфейсов: %w", err)
}
// Парсим вывод для получения имен интерфейсов
lines := strings.Split(string(output), "\n")
var activeInterfaces []string
for _, line := range lines {
if strings.Contains(line, "Connected") || strings.Contains(line, "Подключено") {
fields := strings.Fields(line)
if len(fields) >= 4 {
// Имя интерфейса обычно последнее поле
interfaceName := strings.Join(fields[3:], " ")
activeInterfaces = append(activeInterfaces, interfaceName)
}
}
}
// Сохраняем текущие DNS для каждого интерфейса
for _, iface := range activeInterfaces {
cmd = exec.Command("netsh", "interface", "ip", "show", "dns", iface)
output, err := cmd.Output()
if err == nil {
dp.originalDNS = append(dp.originalDNS, string(output))
}
}
// Устанавливаем VPN DNS для всех активных интерфейсов
for _, iface := range activeInterfaces {
// Устанавливаем первичный DNS
if len(vpnDNS) > 0 {
cmd = exec.Command("netsh", "interface", "ip", "set", "dns",
iface, "static", vpnDNS[0])
if err := cmd.Run(); err != nil {
fmt.Printf("Предупреждение: не удалось установить DNS для %s: %v\n", iface, err)
}
}
// Добавляем вторичные DNS
for i := 1; i < len(vpnDNS); i++ {
cmd = exec.Command("netsh", "interface", "ip", "add", "dns",
iface, vpnDNS[i], fmt.Sprintf("index=%d", i+1))
cmd.Run() // Игнорируем ошибки для дополнительных DNS
}
}
// Очищаем DNS кэш
exec.Command("ipconfig", "/flushdns").Run()
dp.enabled = true
return nil
}
// disableWindows отключает защиту DNS на Windows
func (dp *DNSProtection) disableWindows() error {
if !dp.enabled {
return nil
}
// Получаем список активных интерфейсов
cmd := exec.Command("netsh", "interface", "show", "interface")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("ошибка получения списка интерфейсов: %w", err)
}
lines := strings.Split(string(output), "\n")
var activeInterfaces []string
for _, line := range lines {
if strings.Contains(line, "Connected") || strings.Contains(line, "Подключено") {
fields := strings.Fields(line)
if len(fields) >= 4 {
interfaceName := strings.Join(fields[3:], " ")
activeInterfaces = append(activeInterfaces, interfaceName)
}
}
}
// Восстанавливаем автоматическое получение DNS
for _, iface := range activeInterfaces {
cmd = exec.Command("netsh", "interface", "ip", "set", "dns",
iface, "dhcp")
cmd.Run() // Игнорируем ошибки
}
// Очищаем DNS кэш
exec.Command("ipconfig", "/flushdns").Run()
dp.enabled = false
dp.originalDNS = []string{}
return nil
}
// enableUnix включает защиту DNS на Unix системах
func (dp *DNSProtection) enableUnix(vpnDNS []string) error {
// Сохраняем оригинальный resolv.conf
cmd := exec.Command("cat", "/etc/resolv.conf")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("ошибка чтения resolv.conf: %w", err)
}
dp.originalDNS = strings.Split(string(output), "\n")
// Создаем новый resolv.conf с VPN DNS
var newResolv strings.Builder
for _, dns := range vpnDNS {
newResolv.WriteString(fmt.Sprintf("nameserver %s\n", dns))
}
// Записываем новый resolv.conf
cmd = exec.Command("sh", "-c", fmt.Sprintf("echo '%s' > /etc/resolv.conf", newResolv.String()))
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка записи resolv.conf: %w", err)
}
dp.enabled = true
return nil
}
// disableUnix отключает защиту DNS на Unix системах
func (dp *DNSProtection) disableUnix() error {
if !dp.enabled || len(dp.originalDNS) == 0 {
return nil
}
// Восстанавливаем оригинальный resolv.conf
content := strings.Join(dp.originalDNS, "\n")
cmd := exec.Command("sh", "-c", fmt.Sprintf("echo '%s' > /etc/resolv.conf", content))
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка восстановления resolv.conf: %w", err)
}
dp.enabled = false
dp.originalDNS = []string{}
return nil
}
// IsEnabled проверяет, включена ли защита DNS
func (dp *DNSProtection) IsEnabled() bool {
return dp.enabled
}
// GetCurrentDNS возвращает текущие DNS серверы
func GetCurrentDNS() ([]string, error) {
if runtime.GOOS == "windows" {
return getCurrentDNSWindows()
}
return getCurrentDNSUnix()
}
func getCurrentDNSWindows() ([]string, error) {
cmd := exec.Command("nslookup", "localhost")
output, err := cmd.Output()
if err != nil {
return nil, err
}
var dnsServers []string
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "Address:") {
parts := strings.Split(line, ":")
if len(parts) >= 2 {
dns := strings.TrimSpace(parts[1])
if dns != "" && dns != "127.0.0.1" {
dnsServers = append(dnsServers, dns)
}
}
}
}
return dnsServers, nil
}
func getCurrentDNSUnix() ([]string, error) {
cmd := exec.Command("cat", "/etc/resolv.conf")
output, err := cmd.Output()
if err != nil {
return nil, err
}
var dnsServers []string
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.HasPrefix(strings.TrimSpace(line), "nameserver") {
parts := strings.Fields(line)
if len(parts) >= 2 {
dnsServers = append(dnsServers, parts[1])
}
}
}
return dnsServers, nil
}