diff --git a/html-template/file-viewer.html b/html-template/file-viewer.html new file mode 100644 index 0000000..261387e --- /dev/null +++ b/html-template/file-viewer.html @@ -0,0 +1,78 @@ + + + + {{.Title}} + + + +
+

{{.Title}}

+ + {{if .IsImage}} + File preview + {{else}} +
+

Этот файл не может быть отображен в браузере

+
+ {{end}} + +
+

Тип файла: {{.MimeType}}

+

Размер: {{.FileSize}} KB

+

Имя файла: {{.FileName}}

+ + + Скачать файл + + На главную + Назад +
+
+ + diff --git a/html-template/index-old.html b/html-template/index-old.html new file mode 100644 index 0000000..233cad2 --- /dev/null +++ b/html-template/index-old.html @@ -0,0 +1,57 @@ + + + + + + Data Unwrap Form + + + + + + + + + + + + + + + + +
+
+ + + + +
Введите текст или токен:
+ +
+ + +
+
+

+
+ + +
+

+
+ + +
+ Длина пароля (от 15 до {{.MAXTEXTLENGTH}}): + + +
+
+
+ + diff --git a/html-template/index.html b/html-template/index.html index cb38e53..cad59d6 100644 --- a/html-template/index.html +++ b/html-template/index.html @@ -1,39 +1,206 @@ - - - - Data Unwrap Form - - - - -
- -

-
- - - - - - - - - -
- Введите текст или токен: -
- -
- - - -

- Длина пароля (от 15 до {{ .MAXTEXTLENGTH }}) - - -
- + + + + Data Wrap/Unwrap Form + + + +
+ + +
+
Введите текст или токен:
+
+ +
+ + + +
+
+
+ +
+
Выберите файл (до 100кб):
+
+ +
+ +
+
+
+ +
+
Генерация пароля:
+
+
+
+ Длина пароля (от 15 до {{.MAXTEXTLENGTH}}): + +
+ +
+
+
+
+ +
+
© SVK
+
+ + + + diff --git a/vault.go b/vault.go index 305c4b7..d79114e 100644 --- a/vault.go +++ b/vault.go @@ -11,6 +11,7 @@ import ( // "context" "log" // "time" + "io" "os" "fmt" "flag" @@ -20,6 +21,7 @@ import ( "strings" "time" "encoding/json" + "encoding/base64" "crypto/tls" "net/http" "html/template" @@ -85,15 +87,45 @@ type WrapInfo struct { CreationPath string `json:"creation_path"` } +type FileViewerData struct { + Title string + Base64Data string + MimeType string + FileSize string + FileName string + IsImage bool +} // const MaxTextLength = 100 -func vaultDataWrap(vaultAddr string, text string) string { - secret := make(map[string]string) - secret["wrapped_data"] = text - +func vaultDataWrap(vaultAddr string, dataType string, content string) string { + secret := make(map[string]string) + // switch dataType { + // case "text": + // secret["wrapped_data"] = text + // case "file": + // secret["file"] = text + // default: + // secret["wrapped_data"] = text + // } + // Определяем ключ для данных + if Debug { + log.Println("Тип данныж:", dataType) + } + if dataType == "text" { + // Для текста используем ключ "text" + secret["text"] = content + } else { + // Для файла используем имя файла как ключ + // Если dataType не "text" и не пустое, считаем его именем файла + if dataType == "" { + dataType = "file" // fallback, если имя не указано + } + secret[dataType] = content + } + postBody, err := json.Marshal(secret) if err != nil { - log.Println("Errored marshaling the text:", text) + log.Println("Errored marshaling the text:", content) } customTransport := &(*http.DefaultTransport.(*http.Transport)) @@ -201,50 +233,100 @@ func getStaticPage(w http.ResponseWriter, r *http.Request) { } } +func showFileViewer(w http.ResponseWriter, r *http.Request, fileData []byte, fileName string) { + // Определяем MIME-тип файла + mimeType := http.DetectContentType(fileData) + isImage := strings.HasPrefix(mimeType, "image/") + + // Кодируем в base64 + base64Data := base64.StdEncoding.EncodeToString(fileData) + + // Подготавливаем данные для шаблона + data := FileViewerData{ + Title: "Просмотр файла", + Base64Data: base64Data, + MimeType: mimeType, + FileSize: fmt.Sprintf("%.2f", float64(len(fileData))/1024), + FileName: fileName, + IsImage: isImage, + } + + // Парсим шаблон + tmpl, err := template.ParseFiles(filepath.Join(TemplateDir, "file-viewer.html")) + if err != nil { + http.Error(w, "Error loading template", http.StatusInternalServerError) + return + } + + // Отображаем шаблон + w.Header().Set("Content-Type", "text/html; charset=utf-8") + err = tmpl.Execute(w, data) + if err != nil { + http.Error(w, "Error rendering template", http.StatusInternalServerError) + } +} // hvs.CAES - 95 // s.Dj7kZS - 26 + func unwrapDataFromHtmlForm(w http.ResponseWriter, r *http.Request) { r.ParseForm() token := r.FormValue("input_token") vaultPath := VaultAddress + "/v1/sys/wrapping/unwrap" - Data = "" - // fmt.Fprintln(w, r.URL.RawQuery) - // log.Println(w, r.URL.RawQuery) - if Debug { - log.Printf("Текст для шифровки: %s ", token) - log.Printf("Адрес сервера Hashicorp Vault: %s ", vaultPath) - } - // Проверка текста на соответствие шаблону + + // Нормализация токена re := regexp.MustCompile(`^(hvs|s)\.[\w\-\_]+`) - if Debug { - fmt.Println(re.Match([]byte(token))) - } + token = strings.TrimSpace(strings.ReplaceAll(token, "\r\n", "")) - token = strings.ReplaceAll(token, "\r", "") - token = strings.ReplaceAll(token, "\n", "") - token = strings.ReplaceAll(token, " ", "") - token = strings.TrimSpace(token) + if token != "" && re.MatchString(token) { + unwrappedData := vaultDataUnWrap(vaultPath, token) + + // 1. Сначала проверяем наличие текстовых данных + if textData, exists := unwrappedData["text"]; exists { + Data = textData + } else { + // 2. Если текста нет, ищем файловые данные (любой ключ, кроме "text") + var fileData []byte + var fileName string + + for key, value := range unwrappedData { + if key != "text" { // Пропускаем текстовые данные, если они есть + // Декодируем base64 + decoded, err := base64.StdEncoding.DecodeString(value) + if err != nil { + log.Printf("Ошибка декодирования файла %s: %v", key, err) + continue + } + fileData = decoded + fileName = key + break + } + } - if token != "" && re.Match([]byte(token)) { - b := new(bytes.Buffer) - for key, value := range vaultDataUnWrap(vaultPath, token) { - fmt.Fprintf(b, "%s: %s\n", key, value) + if fileData != nil { + // Если нашли файл - показываем просмотрщик + showFileViewer(w, r, fileData, fileName) + return + } else { + // Если не нашли ни текста, ни файла - выводим все данные как текст + b := new(bytes.Buffer) + for key, value := range unwrappedData { + fmt.Fprintf(b, "%s: %s\n", key, value) + } + Data = b.String() + } } - Data = b.String() + if Data == "" { - Data = "Ошибка! Токен не найден." - } - if Debug { - log.Println(Data) + Data = "Ошибка! Токен не содержит данных." } } else if token != "" { Data = "Введенные данные не соответствуют формату. Введите корректный токен." } + getStaticPage(w, r) - // http.Redirect(w, r, "http://"+r.Host, http.StatusMovedPermanently) } func genPassword(w http.ResponseWriter, r *http.Request) { @@ -307,7 +389,7 @@ func wrapDataFromHtmlForm(w http.ResponseWriter, r *http.Request) { fmt.Println("Введен текст:", secret) } if secret != "" { - Data = vaultDataWrap(vaultPath, secret) + Data = vaultDataWrap(vaultPath, "text", secret) if Data == "" { Data = "Ошибка! Токен не найден." } @@ -325,6 +407,80 @@ func wrapDataFromHtmlForm(w http.ResponseWriter, r *http.Request) { getStaticPage(w, r) } +func wrapDataFromFile(w http.ResponseWriter, r *http.Request) { + MaxFileLength := 100000 // Максимальный размер файла (100KB) + + // 1. Проверяем метод (должен быть POST) + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // 2. Проверяем Content-Type (должен быть multipart/form-data) + contentType := r.Header.Get("Content-Type") + if !strings.HasPrefix(contentType, "multipart/form-data") { + http.Error(w, "Expected multipart/form-data", http.StatusBadRequest) + return + } + + // 3. Парсим форму с ограничением размера (10 МБ) + maxMemory := 10 << 20 // 10 MB + if err := r.ParseMultipartForm(int64(maxMemory)); err != nil { + log.Printf("Failed to parse multipart form: %v", err) + http.Error(w, "Unable to parse form (file too big?)", http.StatusBadRequest) + return + } + + // 4. Получаем файл из формы (включая метаданные) + file, fileHeader, err := r.FormFile("file") + if err != nil { + log.Printf("Failed to get file from form: %v", err) + http.Error(w, "Error retrieving the file", http.StatusBadRequest) + return + } + defer file.Close() + + // Получаем оригинальное имя файла + fileName := fileHeader.Filename + if fileName == "" { + fileName = "unnamed_file" // Запасной вариант, если имя не указано + } + + if Debug { + log.Printf("Processing file: %s (size: %d bytes)", fileName, fileHeader.Size) + } + + // 5. Читаем содержимое файла + fileBytes, err := io.ReadAll(file) + if err != nil { + log.Printf("Failed to read file: %v", err) + http.Error(w, "Error reading the file", http.StatusInternalServerError) + return + } + + // 6. Проверяем размер файла + if len(fileBytes) > MaxFileLength { + errMsg := fmt.Sprintf("File too large (%d > %d)", len(fileBytes), MaxFileLength) + http.Error(w, errMsg, http.StatusBadRequest) + return + } + + // 7. Кодируем в base64 + encoded := base64.StdEncoding.EncodeToString(fileBytes) + + // 8. Обёртываем данные в Vault с именем файла + vaultPath := VaultAddress + "/v1/sys/wrapping/wrap" + token := vaultDataWrap(vaultPath, fileName, encoded) + if token == "" { + http.Error(w, "Failed to wrap data in Vault", http.StatusInternalServerError) + return + } + ttl, _ := strconv.Atoi(TokenTTL) + ttl = ttl / 3600 + Data = fmt.Sprintf("%s\n\n---\nВремя жизни токена %s ч.", token, strconv.Itoa(ttl)) + getStaticPage(w, r) +} + func main() { var ( logFile string @@ -371,8 +527,8 @@ func main() { } else if os.Getenv("VAULT_TOKEN") != "" && VaultToken == "" { VaultToken = os.Getenv("VAULT_TOKEN") } - - if os.Getenv("MAX_TEXT_LENGTH") != "" && MaxTextLength == 100 { + + if os.Getenv("MAX_TEXT_LENGTH") != "" && MaxTextLength == 100 { MaxTextLength, err = strconv.Atoi(os.Getenv("MAX_TEXT_LENGTH")) if err != nil { log.Printf("Ошибка преобразования значения ", os.Getenv("MAX_TEXT_LENGTH")) @@ -386,6 +542,7 @@ func main() { rtr := mux.NewRouter() rtr.HandleFunc("/unwrap", unwrapDataFromHtmlForm) rtr.HandleFunc("/wrap", wrapDataFromHtmlForm) + rtr.HandleFunc("/wrapfile", wrapDataFromFile).Methods("POST") rtr.HandleFunc("/genpassword/{passLength:[0-9]+}", genPassword) rtr.HandleFunc("/genpassword", genPassword)