feat(devops): complete phase 3 infrastructure
This commit is contained in:
@@ -0,0 +1,396 @@
|
||||
Kita akan mulai dengan **REPO 1: `nexus-server-core`**, karena ini adalah "Jantung" dari seluruh sistem. Tanpa ini, Repo 2 dan 3 tidak bisa berfungsi.
|
||||
|
||||
Saya akan memberikan kodingan awal untuk **Database Schema**, **NFTables Manager (Jantung Isolasi)**, dan **Struktur API Provisioning**.
|
||||
|
||||
---
|
||||
|
||||
### 1. Database Schema (GORM Models)
|
||||
File: `internal/models/models.go`
|
||||
|
||||
```go
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primaryKey"`
|
||||
Username string `gorm:"unique;not null"`
|
||||
PasswordHash string `gorm:"not null"`
|
||||
Devices []Device
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primaryKey"`
|
||||
UserID uuid.UUID `gorm:"type:uuid;index"`
|
||||
Name string `gorm:"size:100"`
|
||||
HWID string `gorm:"unique;index"`
|
||||
InternalIP string `gorm:"unique"` // Misal: 10.8.0.5
|
||||
PublicKey string
|
||||
PrivateKey string // Encrypted at rest
|
||||
PresharedKey string
|
||||
AllowInternet bool `gorm:"default:false"`
|
||||
LastHandshake *time.Time
|
||||
IsActive bool `gorm:"default:true"`
|
||||
FirewallRules []FirewallRule
|
||||
}
|
||||
|
||||
type FirewallRule struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primaryKey"`
|
||||
DeviceID uuid.UUID `gorm:"type:uuid;index"`
|
||||
DestIPRange string `gorm:"size:50"` // 192.168.1.1 atau 192.168.1.0/24
|
||||
DestPortRange string `gorm:"size:50"` // 80 atau 8000-9000
|
||||
Protocol string `gorm:"size:10;default:'tcp'"` // tcp, udp
|
||||
Action string `gorm:"size:10;default:'accept'"`
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. NFTables Manager (Zero-Trust Logic)
|
||||
Ini adalah bagian paling krusial yang mengatur isolasi antar user menggunakan library native Go.
|
||||
File: `internal/firewall/nftables.go`
|
||||
|
||||
```go
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/google/nftables"
|
||||
"github.com/google/nftables/expr"
|
||||
"net"
|
||||
)
|
||||
|
||||
type NetManager struct {
|
||||
Conn *nftables.Conn
|
||||
}
|
||||
|
||||
func NewNetManager() *NetManager {
|
||||
return &NetManager{Conn: &nftables.Conn{}}
|
||||
}
|
||||
|
||||
// InitNetwork dasar: Buat Table dan Chain Forward dengan Policy DROP
|
||||
func (m *NetManager) InitNetwork() error {
|
||||
table := m.Conn.AddTable(&nftables.Table{
|
||||
Family: nftables.TableFamilyIPv4,
|
||||
Name: "nexusguard",
|
||||
})
|
||||
|
||||
m.Conn.AddChain(&nftables.Chain{
|
||||
Name: "forward",
|
||||
Table: table,
|
||||
Type: nftables.ChainTypeFilter,
|
||||
Hooknum: nftables.ChainHookForward,
|
||||
Priority: nftables.ChainPriorityFilter,
|
||||
Policy: nftables.ChainPolicyDrop, // ZERO TRUST: Default DROP
|
||||
})
|
||||
|
||||
return m.Conn.Flush()
|
||||
}
|
||||
|
||||
// AddUserIsolation membuat SET khusus untuk UserID agar antar IP user tersebut bisa saling lirik
|
||||
func (m *NetManager) AddUserIsolation(userID string, ipList []string) error {
|
||||
table := &nftables.Table{Name: "nexusguard", Family: nftables.TableFamilyIPv4}
|
||||
|
||||
// Buat Set untuk IP User
|
||||
setName := fmt.Sprintf("user_set_%s", userID[:8])
|
||||
set := &nftables.Set{
|
||||
Table: table,
|
||||
Name: setName,
|
||||
KeyType: nftables.TypeIPAddr,
|
||||
Interval: true,
|
||||
}
|
||||
|
||||
if err := m.Conn.AddSet(set, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Masukkan IP ke dalam Set
|
||||
var elements []nftables.SetElement
|
||||
for _, ip := range ipList {
|
||||
elements = append(elements, nftables.SetElement{Key: net.ParseIP(ip).To4()})
|
||||
}
|
||||
|
||||
return m.Conn.SetAddElements(set, elements)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. API Provisioning (Go-Gin)
|
||||
File: `api/provisioning.go`
|
||||
|
||||
```go
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/gin-gonic/gin"
|
||||
"nexus-server-core/internal/models"
|
||||
)
|
||||
|
||||
type ProvisioningRequest struct {
|
||||
Token string `json:"token" binding:"required"`
|
||||
HWID string `json:"hwid" binding:"required"`
|
||||
}
|
||||
|
||||
func RegisterDevice(c *gin.Context) {
|
||||
var req ProvisioningRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Validasi Token (Cek di DB)
|
||||
// 2. Jika valid, cari Device yang di-assign token tersebut
|
||||
// 3. Bind HWID ke Device tersebut
|
||||
// 4. Generate WG Keys jika belum ada
|
||||
|
||||
resp := gin.H{
|
||||
"internal_ip": "10.8.0.5",
|
||||
"private_key": "ENCRYPTED_PRIV_KEY", // AES-GCM Encrypted
|
||||
"server_pub": "SERVER_PUBLIC_KEY",
|
||||
"endpoint": "wg.yourdomain.com:51820",
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Dokumentasi API untuk Divisi Lain (OpenCode)
|
||||
File: `docs/API_SPEC.md`
|
||||
|
||||
```markdown
|
||||
# NexusGuard API Specification
|
||||
|
||||
## 1. Device Provisioning
|
||||
**Endpoint:** `POST /api/v1/provisioning`
|
||||
**Auth:** None (Use One-time Token)
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"token": "REG-123456",
|
||||
"hwid": "CPU-SERIAL-ABC-XYZ"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (Encrypted):**
|
||||
Data sensitif dienkripsi menggunakan AES-256-GCM dengan `SharedSecret` yang diturunkan dari HWID.
|
||||
|
||||
---
|
||||
|
||||
## 2. Firewall Rule Sync (Internal Only)
|
||||
**Endpoint:** `POST /api/v1/internal/sync-firewall`
|
||||
**Auth:** `X-Internal-Secret`
|
||||
|
||||
**Logic:**
|
||||
Memerintahkan Hook Engine untuk merefresh rule nftables dari database.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Review untuk Divisi:
|
||||
|
||||
1. **Divisi Backend (Repo 1):** Anda sekarang punya boilerplate untuk `nftables`. Tugas selanjutnya adalah membuat *worker* yang membaca tabel `firewall_rules` dan mengubahnya menjadi `nftables vmap`.
|
||||
2. **Divisi UI (Repo 2):** Anda bisa mulai membuat Vue page yang mengirim POST request ke `/provisioning`. Gunakan Mock data berdasarkan `API_SPEC.md` di atas.
|
||||
3. **Divisi Agent (Repo 3):** Anda fokus pada pengambilan HWID. Gunakan `os/exec` untuk ambil serial di Linux, lalu kirim ke endpoint `/provisioning`.
|
||||
|
||||
**Langkah selanjutnya:**
|
||||
Apakah Anda ingin saya buatkan **Logic AES-256-GCM** untuk enkripsi config di Go, atau **Bash script installer** untuk Agent dari Gitea?
|
||||
|
||||
---
|
||||
# Provisioning prompt
|
||||
Berikut adalah implementasi teknis untuk **Logic Keamanan (AES-256-GCM)** dan **Bash Installer Pintar** yang mampu mendeteksi serta menginstal dependensi OS secara otomatis.
|
||||
|
||||
---
|
||||
|
||||
### 1. Logic Enkripsi: AES-256-GCM (Go)
|
||||
Data konfigurasi WireGuard sangat sensitif. Kita tidak mengirimnya dalam teks biasa. Kita gunakan **AES-GCM** karena ia menyediakan *Authenticated Encryption* (menjamin data tidak dimanipulasi).
|
||||
|
||||
File: `shared/crypto/encryptor.go` (Digunakan di Repo 1 & 3)
|
||||
|
||||
```go
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// EncryptConfig menggunakan HWID sebagai kunci dasar untuk mengenkripsi data
|
||||
func Encrypt(plaintext []byte, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Seal(dst, nonce, plaintext, additionalData)
|
||||
return gcm.Seal(nonce, nonce, plaintext, nil), nil
|
||||
}
|
||||
|
||||
func Decrypt(ciphertext []byte, key []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nonceSize := gcm.NonceSize()
|
||||
if len(ciphertext) < nonceSize {
|
||||
return nil, errors.New("ciphertext too short")
|
||||
}
|
||||
|
||||
nonce, actualCiphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||
return gcm.Open(nil, nonce, actualCiphertext, nil)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Bash Installer: NexusGuard Agent
|
||||
Script ini dirancang untuk dijalankan di perangkat target (CCTV/Server). Ia akan mengecek dependensi dan menarik binary dari Gitea.
|
||||
|
||||
File: `scripts/install_agent.sh`
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Konfigurasi Gitea
|
||||
GITEA_URL="https://gitea.yourdomain.com"
|
||||
API_TOKEN="YOUR_X_TOKEN_AUTH"
|
||||
REPO_PATH="nexusguard/device-agent"
|
||||
BINARY_NAME="sys-bridge"
|
||||
|
||||
# Colors
|
||||
RED='\033[0-1;31m'
|
||||
GREEN='\033[0-1;32m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${GREEN}== NexusGuard Agent Installer ==${NC}"
|
||||
|
||||
# 1. Cek User (Harus Root)
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${RED}Harus dijalankan sebagai root!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. Deteksi OS & Install Dependensi
|
||||
echo "Mengecek dependensi OS..."
|
||||
OS_TYPE=$(lsb_release -is 2>/dev/null || cat /etc/os-release | grep ^ID= | cut -d= -f2)
|
||||
|
||||
install_deps() {
|
||||
case $OS_TYPE in
|
||||
"Ubuntu"|"Debian"|"raspbian")
|
||||
apt-get update
|
||||
apt-get install -y nftables curl iproute2 wireguard-tools
|
||||
;;
|
||||
"CentOS"|"rhel"|"fedora")
|
||||
yum install -y nftables curl iproute-tc wireguard-tools
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}OS tidak didukung otomatis. Silakan install nftables & curl manual.${NC}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Cek apakah nftables terinstall
|
||||
if ! command -v nft &> /dev/null; then
|
||||
echo "nftables tidak ditemukan. Menginstall..."
|
||||
install_deps
|
||||
fi
|
||||
|
||||
# 3. Download Binary dari Gitea Private
|
||||
echo "Menarik binary dari Gitea..."
|
||||
ARCH=$(uname -m)
|
||||
case $ARCH in
|
||||
x86_64) PKG_ARCH="amd64" ;;
|
||||
aarch64) PKG_ARCH="arm64" ;;
|
||||
armv7l) PKG_ARCH="arm" ;;
|
||||
*) echo "Arsitektur $ARCH tidak didukung"; exit 1 ;;
|
||||
esac
|
||||
|
||||
curl -H "X-Token-Auth: ${API_TOKEN}" -L \
|
||||
-o /usr/local/bin/${BINARY_NAME} \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO_PATH}/releases/latest/download/${BINARY_NAME}-${PKG_ARCH}"
|
||||
|
||||
chmod +x /usr/local/bin/${BINARY_NAME}
|
||||
|
||||
# 4. Buat Systemd Service agar auto-start
|
||||
echo "Mendaftarkan ke systemd..."
|
||||
cat <<EOF > /etc/systemd/system/${BINARY_NAME}.service
|
||||
[Unit]
|
||||
Description=NexusGuard Stealth Bridge
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/bin/${BINARY_NAME}
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=${BINARY_NAME}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
echo -e "${GREEN}Instalasi Selesai! Gunakan 'systemctl start ${BINARY_NAME}' untuk memulai.${NC}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Review Logic & Mekanisme "Self-Healing"
|
||||
|
||||
Untuk **Agent AI** yang akan melanjutkan pengembangan, berikut adalah poin-poin optimasi:
|
||||
|
||||
1. **Dependency Handling:**
|
||||
* Meskipun Agent menggunakan `wireguard-go` (embedded), kita tetap menginstal `wireguard-tools` (wg) di OS hanya sebagai utilitas bantuan (helper) untuk debugging manual jika diperlukan.
|
||||
* `nftables` wajib ada karena Server Hook akan memanipulasi rule secara remote.
|
||||
|
||||
2. **Keamanan HWID-Based Encryption:**
|
||||
* Saat Agent pertama kali jalan, ia membuat kunci AES dari `SHA256(HardwareID + Salt)`.
|
||||
* Key ini **tidak pernah dikirim ke jaringan**.
|
||||
* Server Core juga memiliki algoritma SHA256 yang sama. Jadi Server mengenkripsi config dengan key tersebut, dan Agent bisa membukanya. Ini mencegah "Man-in-the-Middle" (intersepsi di tengah jalan).
|
||||
|
||||
3. **Proses Install Otomatis:**
|
||||
* Script di atas mendukung Ubuntu, Debian, CentOS, dan Raspberry Pi (Raspbian). Ini mencakup 90% perangkat CCTV/IoT di lapangan.
|
||||
|
||||
---
|
||||
|
||||
### 4. Tugas Selanjutnya untuk Divisi/Agent AI:
|
||||
|
||||
#### **Untuk Repo 1 (Server Core):**
|
||||
* Implementasikan endpoint `/api/v1/provisioning` yang menggunakan fungsi `crypto.Encrypt` sebelum mengirim JSON ke Agent.
|
||||
* Buat database trigger: Jika `Device` dihapus, kirim sinyal ke Hook untuk menghapus elemen di `nftables`.
|
||||
|
||||
#### **Untuk Repo 3 (Device Agent):**
|
||||
* Implementasikan fungsi `GetHWID()` yang mengambil serial dari `/sys/class/dmi/id/product_uuid` (untuk Linux Server) atau `/proc/cpuinfo` (untuk Raspberry Pi).
|
||||
* Gunakan `crypto.Decrypt` untuk membuka config yang diterima dari server.
|
||||
|
||||
**Apakah Anda ingin saya buatkan kodingan Go untuk "HWID Discovery" yang akurat di berbagai jenis Linux?**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user