init
This commit is contained in:
258
back/cmd/crypto_routes.go
Normal file
258
back/cmd/crypto_routes.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// setupCryptoRoutes настраивает роуты для шифрования/дешифрования
|
||||
func setupCryptoRoutes(api *gin.RouterGroup, passHash [32]byte) {
|
||||
// Encrypt: вход YAML (и опционально path). Если path указан: можно не передавать yaml,
|
||||
// тогда читаем из файла и пишем шифровку в этот же путь
|
||||
api.POST("/encrypt", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Yaml string `json:"yaml"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
if err := c.BindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": fmt.Sprintf("bad json: %v", err)})
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(req.Yaml) == "" {
|
||||
c.JSON(400, gin.H{"error": "yaml is required"})
|
||||
return
|
||||
}
|
||||
|
||||
// Извлекаем path из YAML для автоматической записи файла
|
||||
var yamlPath string
|
||||
if yamlStr := strings.TrimSpace(req.Yaml); yamlStr != "" {
|
||||
var config map[string]interface{}
|
||||
if err := yaml.Unmarshal([]byte(yamlStr), &config); err == nil {
|
||||
if pathVal, ok := config["path"].(string); ok {
|
||||
yamlPath = strings.TrimSpace(pathVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Используем path из запроса или из YAML
|
||||
targetPath := strings.TrimSpace(req.Path)
|
||||
if targetPath == "" && yamlPath != "" {
|
||||
targetPath = yamlPath
|
||||
}
|
||||
|
||||
encrypted, err := encryptBytes([]byte(req.Yaml), passHash[:])
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
out := "ENCRYPTED:" + encrypted
|
||||
|
||||
if targetPath != "" {
|
||||
if !isFileIOEnabled() {
|
||||
c.JSON(403, gin.H{"error": "file I/O is disabled; set GO_KNOCKER_ENABLE_FILE_IO=1 or remove GO_KNOCKER_ENABLE_FILE_IO=0"})
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(targetPath, []byte(out), 0600); err != nil {
|
||||
c.JSON(500, gin.H{"error": fmt.Sprintf("write failed: %v", err)})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"status": "ok", "encrypted": out, "path": targetPath})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"encrypted": out})
|
||||
})
|
||||
|
||||
// Decrypt: вход ENCRYPTED:... (и опционально path). Если path указан: можно не передавать encrypted,
|
||||
// тогда читаем из файла и пишем расшифровку в этот же путь
|
||||
api.POST("/decrypt", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Encrypted string `json:"encrypted"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
if err := c.BindJSON(&req); err != nil {
|
||||
c.JSON(400, gin.H{"error": fmt.Sprintf("bad json: %v", err)})
|
||||
return
|
||||
}
|
||||
var enc string
|
||||
if strings.TrimSpace(req.Path) != "" && strings.TrimSpace(req.Encrypted) == "" {
|
||||
if !isFileIOEnabled() {
|
||||
c.JSON(403, gin.H{"error": "file I/O is disabled; set GO_KNOCKER_ENABLE_FILE_IO=1 or remove GO_KNOCKER_ENABLE_FILE_IO=0"})
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(req.Path)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": fmt.Sprintf("read failed: %v", err)})
|
||||
return
|
||||
}
|
||||
enc = string(data)
|
||||
} else {
|
||||
enc = req.Encrypted
|
||||
}
|
||||
if !strings.HasPrefix(enc, "ENCRYPTED:") {
|
||||
c.JSON(400, gin.H{"error": "input must start with ENCRYPTED:"})
|
||||
return
|
||||
}
|
||||
plain, err := decryptString(enc[10:], passHash[:])
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Извлекаем path из расшифрованного YAML
|
||||
var yamlPath string
|
||||
if plainStr := string(plain); strings.TrimSpace(plainStr) != "" {
|
||||
var config map[string]interface{}
|
||||
if err := yaml.Unmarshal(plain, &config); err == nil {
|
||||
if pathVal, ok := config["path"].(string); ok {
|
||||
yamlPath = strings.TrimSpace(pathVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Используем path из запроса или из YAML
|
||||
targetPath := strings.TrimSpace(req.Path)
|
||||
if targetPath == "" && yamlPath != "" {
|
||||
targetPath = yamlPath
|
||||
}
|
||||
|
||||
if targetPath != "" {
|
||||
if !isFileIOEnabled() {
|
||||
c.JSON(403, gin.H{"error": "file I/O is disabled; set GO_KNOCKER_ENABLE_FILE_IO=1 or remove GO_KNOCKER_ENABLE_FILE_IO=0"})
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(targetPath, plain, 0600); err != nil {
|
||||
c.JSON(500, gin.H{"error": fmt.Sprintf("write failed: %v", err)})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"status": "ok", "yaml": string(plain), "path": targetPath})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"yaml": string(plain)})
|
||||
})
|
||||
|
||||
// Encrypt file on server filesystem
|
||||
api.POST("/encrypt-file", func(c *gin.Context) {
|
||||
if !isFileIOEnabled() {
|
||||
c.JSON(403, gin.H{"error": "file I/O is disabled; set GO_KNOCKER_ENABLE_FILE_IO=1 or remove GO_KNOCKER_ENABLE_FILE_IO=0"})
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
if err := c.BindJSON(&req); err != nil || strings.TrimSpace(req.Path) == "" {
|
||||
c.JSON(400, gin.H{"error": "invalid path"})
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(req.Path)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": fmt.Sprintf("read failed: %v", err)})
|
||||
return
|
||||
}
|
||||
encrypted, err := encryptBytes(data, passHash[:])
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
out := "ENCRYPTED:" + encrypted
|
||||
if err := os.WriteFile(req.Path, []byte(out), 0600); err != nil {
|
||||
c.JSON(500, gin.H{"error": fmt.Sprintf("write failed: %v", err)})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"status": "ok", "encrypted": out})
|
||||
})
|
||||
|
||||
// Decrypt file on server filesystem
|
||||
api.POST("/decrypt-file", func(c *gin.Context) {
|
||||
if !isFileIOEnabled() {
|
||||
c.JSON(403, gin.H{"error": "file I/O is disabled; set GO_KNOCKER_ENABLE_FILE_IO=1 or remove GO_KNOCKER_ENABLE_FILE_IO=0"})
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
if err := c.BindJSON(&req); err != nil || strings.TrimSpace(req.Path) == "" {
|
||||
c.JSON(400, gin.H{"error": "invalid path"})
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(req.Path)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": fmt.Sprintf("read failed: %v", err)})
|
||||
return
|
||||
}
|
||||
enc := string(data)
|
||||
if !strings.HasPrefix(enc, "ENCRYPTED:") {
|
||||
c.JSON(400, gin.H{"error": "file must start with ENCRYPTED:"})
|
||||
return
|
||||
}
|
||||
plain, err := decryptString(enc[10:], passHash[:])
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(req.Path, plain, 0600); err != nil {
|
||||
c.JSON(500, gin.H{"error": fmt.Sprintf("write failed: %v", err)})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{"status": "ok", "yaml": string(plain)})
|
||||
})
|
||||
}
|
||||
|
||||
// encryptBytes шифрует байты с помощью AES-GCM
|
||||
func encryptBytes(data []byte, key []byte) (string, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nonce := make([]byte, gcm.NonceSize())
|
||||
if _, err := rand.Read(nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ciphertext := gcm.Seal(nonce, nonce, data, nil)
|
||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
// decryptString дешифрует строку с помощью AES-GCM
|
||||
func decryptString(encrypted string, key []byte) ([]byte, error) {
|
||||
data, err := base64.StdEncoding.DecodeString(encrypted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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(data) < nonceSize {
|
||||
return nil, fmt.Errorf("ciphertext too short")
|
||||
}
|
||||
|
||||
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
||||
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return plaintext, nil
|
||||
}
|
Reference in New Issue
Block a user