commit d6660a9d78c9c3a83651029d4b8156acf4d94745cfed419bb60c90e898648df1 Author: admin Date: Sat Oct 3 16:20:30 2026 +0000 Добавить readme.md diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..2525f47 --- /dev/null +++ b/readme.md @@ -0,0 +1,303 @@ + +--- + +# Часть 1. Среда разработки (АРМ / Сборочный сервер) + +На сервере разработки производятся написание, компиляция микросервиса на Go и подготовка артефактов для деплоя на продакшн-сервера РЕД ОС. На продакшн-сервера средства разработки (компиляторы, заголовки, git) не переносятся. + +### Подготовка сборочного стенда (РЕД ОС / Linux) + +```bash +# Установка Go и git на сервере разработки +wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz +sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz +export PATH=$PATH:/usr/local/go/bin + +``` + +### Исходный код микросервиса (`main.go`) + +Создайте проект и файл `main.go`: + +```bash +mkdir -p /opt/bastion-auth && cd /opt/bastion-auth +go mod init bastion-auth + +``` + +Содержимое файла `main.go`: + +```go +package main + +import ( + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "log" + "net/http" + "strings" + "time" + + "github.com/redis/go-redis/v9" +) + +const BastionSecret = "super-secret-key-known-only-by-django-and-proxy" + +var ctx = context.Background() +var rdb *redis.Client + +type TokenPayload struct { + User string `json:"user"` + Groups []string `json:"groups"` + Exp int64 `json:"exp"` + JTI string `json:"jti"` +} + +func authHandler(w http.ResponseWriter, r *http.Request) { + cookie, err := r.Cookie("X-Bastion-Auth") + if err != nil { + http.Error(w, "No auth cookie", http.StatusUnauthorized) + return + } + + targetGroup := r.Header.Get("X-Target-Group") + if targetGroup == "" { + http.Error(w, "Target group missing", http.StatusBadRequest) + return + } + + parts := strings.Split(cookie.Value, ".") + if len(parts) != 2 { + http.Error(w, "Invalid token format", http.StatusUnauthorized) + return + } + payloadB64, signature := parts[0], parts[1] + + mac := hmac.New(sha256.New, []byte(BastionSecret)) + mac.Write([]byte(payloadB64)) + expectedSig := fmt.Sprintf("%x", mac.Sum(nil)) + + if !hmac.Equal([]byte(expectedSig), []byte(signature)) { + http.Error(w, "Invalid signature", http.StatusUnauthorized) + return + } + + payloadBytes, err := base64.RawURLEncoding.DecodeString(payloadB64) + if err != nil { + payloadBytes, err = base64.URLEncoding.DecodeString(payloadB64) + if err != nil { + http.Error(w, "Invalid base64", http.StatusUnauthorized) + return + } + } + + var payload TokenPayload + if err := json.Unmarshal(payloadBytes, &payload); err != nil { + http.Error(w, "Invalid JSON", http.StatusUnauthorized) + return + } + + if payload.Exp < time.Now().Unix() { + http.Error(w, "Token expired", http.StatusUnauthorized) + return + } + + val, err := rdb.Exists(ctx, "revoked:"+payload.JTI).Result() + if err == nil && val > 0 { + http.Error(w, "Token revoked", http.StatusUnauthorized) + return + } + + allowed := false + for _, g := range payload.Groups { + if strings.EqualFold(g, targetGroup) { + allowed = true + break + } + } + + if !allowed { + http.Error(w, "Forbidden: group mismatch", http.StatusForbidden) + return + } + + w.Header().Set("X-Remote-User", payload.User) + w.Header().Set("X-Remote-Group", targetGroup) + w.WriteHeader(http.StatusOK) +} + +func main() { + rdb = redis.NewClient(&redis.Options{ + Addr: "127.0.0.1:6379", + }) + http.HandleFunc("/internal/auth", authHandler) + log.Println("Go Auth Service started on 127.0.0.1:8080...") + log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil)) +} + +``` + +### Сборка исполняемого файла + +```go +go get github.com/redis/go-redis/v9 +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bastion-auth main.go + +``` + +Полученный бинарник `bastion-auth` (вместе с конфигурациями) переносится на продакшн-серверы. + +--- + +# Часть 2. Продакшн-среда (Node 1 и Node 2 на РЕД ОС) + +На продакшн-серверах устанавливаются **только** исполняемые компоненты без средств разработки. + +## Автоматизированный установочный скрипт для Продакшн-ноды (РЕД ОС) + +Скопируйте этот скрипт на каждый из двух серверов (предварительно изменив переменные `NODE_ROLE` на `MASTER` или `BACKUP`), а также разместите рядом скомпилированный на этапе разработки файл `bastion-auth`. + +```bash +#!/bin/bash +set -e + +# --- ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ ПРОДАКШЕНА --- +NODE_ROLE="MASTER" # Укажите MASTER для первой ноды и BACKUP для второй +VIP_IP="192.168.1.100/24" # Плавающий VIP-адрес кластера +INTERFACE_NAME="eth0" # Сетевой интерфейс в РЕД ОС + +echo "=== [1/5] Базовая настройка ОС (Отключение SELinux и настройка sysctl) ===" +# Полное отключение SELinux +sudo sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config || true +sudo setenforce 0 || true + +# Настройка net.ipv4.ip_nonlocal_bind для работы VIP на backup-ноде +echo "net.ipv4.ip_nonlocal_bind = 1" | sudo tee /etc/sysctl.d/99-ip-nonlocal-bind.conf +sudo sysctl --system + +echo "=== [2/5] Установка только рантантим-пакетов (без средств разработки) ===" +sudo dnf update -y +sudo dnf install -y haproxy redis keepalived curl + +echo "=== [3/5] Развертывание Go-микросервиса авторизации ===" +sudo mkdir -p /opt/bastion-auth +# Предполагается, что скомпилированный на этапе разработки файл bastion-auth скопирован в текущую директорию +sudo cp bastion-auth /opt/bastion-auth/bastion-auth +sudo chmod +x /opt/bastion-auth/bastion-auth + +# Создание systemd юнита для микросервиса +cat << 'EOF' > /etc/systemd/system/bastion-auth.service +[Unit] +Description=AdminBastion Auth Microservice +After=network.target redis.service + +[Service] +ExecStart=/opt/bastion-auth/bastion-auth +Restart=always +User=root + +[Install] +WantedBy=multi-user.target +EOF + +sudo systemctl daemon-reload +sudo systemctl enable --now bastion-auth + +echo "=== [4/5] Настройка конфигурации HAProxy ===" +cat << 'EOF' > /etc/haproxy/haproxy.cfg +global + log /dev/log local0 + user haproxy + group haproxy + daemon + +defaults + log global + mode http + option httplog + option dontlognull + timeout connect 5s + timeout client 50s + timeout server 50s + +frontend https_in + bind *:443 ssl crt /etc/ssl/bastion/bastion.pem + + acl is_django path_beg /login /logout /dashboard /api /static + use_backend django_sso if is_django + + acl uri_format path_reg ^/([^/]+)/([^/]+)(/.*)?$ + http-request deny deny_status 400 unless uri_format + + http-request set-var(tx.url_group) path,regsub(^/([^/]+)/([^/]+).*,\1) + http-request set-var(tx.url_resource) path,regsub(^/([^/]+)/([^/]+).*,\2) + + http-request set-header X-Target-Group %[var(tx.url_group)] + auth-url /internal/auth auth_backend go_auth_service + + http-request redirect location /login if { status 401 } + http-request deny deny_status 403 if { status 403 } + + http-request set-path %[path,regsub(^/[^/]+/[^/]+,,g)] + http-request set-path / if { path -m str "" } + + use_backend grafana_corp if { var(tx.url_resource) -m str grafana } + use_backend gitlab_corp if { var(tx.url_resource) -m str gitlab } + + default_backend 404_backend + +backend go_auth_service + server auth1 127.0.0.1:8080 check + +backend django_sso + server django1 127.0.0.1:8000 check + +backend grafana_corp + http-request set-header Host grafana.corp.local + server grafana 10.0.1.10:443 ssl verify none + +backend gitlab_corp + http-request set-header Host gitlab.corp.local + server gitlab 10.0.1.20:443 ssl verify none + +backend 404_backend + errorfile 404 /etc/haproxy/errors/404.http +EOF + +sudo mkdir -p /etc/ssl/bastion +# (Не забудьте положить действующий файл сертификата /etc/ssl/bastion/bastion.pem) + +echo "=== [5/5] Настройка Keepalived для высокой доступности (HA) ===" +PRIORITY=101 +if [ "$NODE_ROLE" == "BACKUP" ]; then + PRIORITY=100 +fi + +cat << EOF > /etc/keepalived/keepalived.conf +vrrp_instance VI_1 { + state $NODE_ROLE + interface $INTERFACE_NAME + virtual_router_id 51 + priority $PRIORITY + advert_int 1 + authentication { + auth_type PASS + auth_pass bastion_cluster_secret + } + virtual_ipaddress { + $VIP_IP + } +} +EOF + +sudo systemctl enable --now redis +sudo systemctl enable --now keepalived +sudo systemctl restart haproxy + +echo "=== Настройка продакшн ноды AdminBastion успешно завершена! ===" + +``` \ No newline at end of file