SHA256
Добавить readme.md
This commit is contained in:
commit
d6660a9d78
1 file changed
+303
@@ -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 успешно завершена! ==="
|
||||||
|
|
||||||
|
```
|
||||||
Reference in new issue
Block a user