// ------------------------------------------- // Hashicorp Vault wrap/unwrap web service // Distributed under GNU Public License // Author: Sergey Kalinin svk@nuk-svk.ru // Home page: https://nuk-svk.ru https://git.nuk-svk.ru // ------------------------------------------- package main import ( // "context" "log" // "time" "os" "fmt" "flag" "regexp" "bytes" "strconv" "strings" "time" "encoding/json" "crypto/tls" "net/http" "html/template" "path/filepath" "github.com/gorilla/mux" "github.com/sethvargo/go-password/password" ) // { // "request_id": "540486b5-80b6-4250-1ba3-ec562984c58c", // "lease_id": "", // "renewable": false, // "lease_duration": 0, // "data": { // "user": "password" // }, // "wrap_info": null, // "warnings": null, // "auth": null, // "mount_type": "system" // } // var ( Debug bool TemplateDir string TemplateFile string ActionAddress string VaultAddress string VaultToken string Data string ListenPort string TlsEnable bool TlsCertFile string TlsKeyFile string MaxTextLength int TokenTTL string ) type TemplateData struct { URL string TEXT string MAXTEXTLENGTH int TOKENTTL string } type UnwrappedData struct { Rerquest_id string `json: "request_id"` Lease_id string `json: "lease_id"` Renewable bool `json: "renewable"` Lease_daration int `json:"lease_duration"` Data map[string]string `json: "data"` Wrap_info *WrapInfo `json: "wrap_info"` Warnings string `json: "warnings"` Auth string `json: "auth"` Mount_type string `json: "mount_type"` Errors string `json: "errors"` } type WrapInfo struct { Token string `json:"token"` Ttl int `json:"ttl"` CreationTime time.Time `json:"creation_time"` CreationPath string `json:"creation_path"` } // const MaxTextLength = 100 func vaultDataWrap(vaultAddr string, text string) string { secret := make(map[string]string) secret["wrapped_data"] = text postBody, err := json.Marshal(secret) if err != nil { log.Println("Errored marshaling the text:", text) } customTransport := &(*http.DefaultTransport.(*http.Transport)) customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} client := &http.Client{Transport: customTransport} // client := &http.Client{} req, _ := http.NewRequest("POST", vaultAddr, bytes.NewBuffer(postBody)) req.Header.Add("Accept", "application/json") req.Header.Add("X-Vault-Token", VaultToken) req.Header.Add("X-Vault-Wrap-TTL", TokenTTL) resp, err := client.Do(req) if err != nil { log.Println("Errored when sending request to the Vault server") } var result UnwrappedData json.NewDecoder(resp.Body).Decode(&result) // wrappedToken := result.Data if Debug { log.Println(resp) log.Println("result", result) log.Println("token", result.Wrap_info.Token) } return result.Wrap_info.Token } func vaultDataUnWrap(vaultAddr string, vaultWrapToken string) map[string]string { log.Printf("Vault address: %s ", vaultAddr) customTransport := &(*http.DefaultTransport.(*http.Transport)) customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} client := &http.Client{Transport: customTransport} // client := &http.Client{} log.Println(vaultAddr, vaultWrapToken) req, _ := http.NewRequest("POST", vaultAddr, nil) req.Header.Add("Accept", "application/json") req.Header.Add("X-Vault-Token", vaultWrapToken) resp, err := client.Do(req) if err != nil { log.Println("Errored when sending request to the Vault server", err) } if Debug { log.Println(resp) } var result UnwrappedData json.NewDecoder(resp.Body).Decode(&result) secret := result.Data if Debug { log.Println("result", result) log.Println("secret", secret) } // log.Println("Length=", len(secret)) // if len(secret) == 0 { // log.Println("Error:", result.Errors) // } // fmt.Sprint(secret) // for v, k := range secret { // log.Println(k, v) // } return secret } func ParseTemplate(templateFileName string, data interface{}) (body string, err error) { body = "" t, err := template.ParseFiles(templateFileName) if err != nil { log.Println("Ошибка преобразования html шаблона", templateFileName, err) return } buf := new(bytes.Buffer) if err = t.Execute(buf, data); err != nil { log.Println("Ошибка преобразования html шаблона", templateFileName, err) body = "" } else { body = buf.String() } return } func getStaticPage(w http.ResponseWriter, r *http.Request) { var ( templateData TemplateData ) template := filepath.Join(TemplateDir, TemplateFile) // templateData.UUID = uuid templateData.URL = ActionAddress templateData.TEXT = Data templateData.MAXTEXTLENGTH = MaxTextLength templateData.TOKENTTL = TokenTTL // templateData.URL = FishingUrl + "/" + arrUsers[i].messageUUID if body, err := ParseTemplate(template, templateData); err == nil { w.Write([]byte(body)) } } // 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.ReplaceAll(token, "\r", "") token = strings.ReplaceAll(token, "\n", "") token = strings.ReplaceAll(token, " ", "") token = strings.TrimSpace(token) 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) } Data = b.String() if Data == "" { Data = "Ошибка! Токен не найден." } if Debug { log.Println(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) { // params := mux.Vars(r) // passLength := params["passLength"] Data = "" r.ParseForm() passLength := r.FormValue("passlength") if Debug { log.Printf(r.FormValue("passlength"), passLength) } if len(passLength) == 0 { passLength = "32" } // w.Write([]byte("Длина пароля " + passLength + "/n")) passwordLength, err := strconv.Atoi(passLength) if passwordLength > MaxTextLength { log.Printf("Oversized password length") Data = "Превышена длина пароля" getStaticPage(w, r) return } if err != nil { log.Println(err) } res, err := password.Generate(passwordLength, 5, 5, false, true) if err != nil { log.Println(err) } if Debug { log.Printf(res) } Data = res // w.Write([]byte(res)) getStaticPage(w, r) } func genPasswordDefault(w http.ResponseWriter, r *http.Request) { res, err := password.Generate(64, 10, 5, false, false) if err != nil { log.Fatal(err) } log.Printf(res) // w.Write([]byte(res)) Data = res // w.Write([]byte(res)) getStaticPage(w, r) } func wrapDataFromHtmlForm(w http.ResponseWriter, r *http.Request) { r.ParseForm() secret := r.FormValue("input_token") if len([]rune(secret)) > MaxTextLength { log.Println("Длина текста превышает заданные ограничения:", MaxTextLength) Data = fmt.Sprintf("Длина текста превышает заданные ограничения: %s > %s", strconv.Itoa(len([]rune(secret))), strconv.Itoa(MaxTextLength)) } else { vaultPath := VaultAddress + "/v1/sys/wrapping/wrap" Data = "" if Debug { fmt.Println("Введен текст:", secret) } if secret != "" { Data = vaultDataWrap(vaultPath, secret) if Data == "" { Data = "Ошибка! Токен не найден." } if Debug { log.Println(Data) } // Переводим секунды в часы и дабавляем к токену для информации. ttl, _ := strconv.Atoi(TokenTTL) ttl = ttl / 3600 Data = fmt.Sprintf("%s\n\n---\nВремя жизни токена %s ч.", Data, strconv.Itoa(ttl)) } else if secret != "" { Data = "Введите текст для шифровки." } } getStaticPage(w, r) } func main() { var ( logFile string ) flag.BoolVar(&Debug, "debug", false, "Вывод отладочных сообщений в консоль") flag.StringVar(&logFile, "log-file", "vault-unwrap.log", "Путь до лог-файла ") flag.StringVar(&TemplateDir, "template-dir", "html-template", "Каталог с шаблонами") flag.StringVar(&TemplateFile, "template-file", "index.html", "Файл-шаблон для ВЭБ-странцы") flag.StringVar(&VaultAddress, "vault-url", "", "Адрес сервера Hashicorp Vault (https://host.name:8200)") flag.StringVar(&ActionAddress, "action-address", "", "Адрес данного сервиса (https://host.name)") flag.StringVar(&ListenPort, "listen-port", "8080", "Номер порта сервиса") flag.StringVar(&TlsCertFile, "tls-cert", "", "TLS сертификат (файл)") flag.StringVar(&TlsKeyFile, "tls-key", "", "TLS ключ (файл)") flag.BoolVar(&TlsEnable, "tls", false, "Использовать SSL/TLS") flag.IntVar(&MaxTextLength, "max-text-length", 100 , "Максимальная длина текста для шифрования и длина пароля для генератора") flag.StringVar(&TokenTTL, "token-ttl", "3600", "Время жизни wrap-токена в секундах") flag.Parse() if os.Getenv("logFile") != "" { logFile = os.Getenv("logFile") } fLog, err := os.OpenFile(logFile, os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666) if err != nil { log.Fatalf("error opening file: %v", err) } defer fLog.Close() if Debug { log.SetOutput(os.Stdout) } else { log.SetOutput(fLog) } if os.Getenv("VAULT_ADDRESS") == "" && VaultAddress == "" { log.Println("Send error: make sure environment variables `VAULT_ADDRESS` was set") } else if os.Getenv("VAULT_ADDRESS") != "" && VaultAddress == "" { VaultAddress = os.Getenv("VAULT_ADDRESS") } if os.Getenv("VAULT_TOKEN") == "" && VaultToken == "" { log.Println("Send error: make sure environment variables `VAULT_TOKEN` was set") } else if os.Getenv("VAULT_TOKEN") != "" && VaultToken == "" { VaultToken = os.Getenv("VAULT_TOKEN") } 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")) } } if Debug { log.Printf("Адрес сервера Hashicorp Vault: %s ", VaultAddress) } rtr := mux.NewRouter() rtr.HandleFunc("/unwrap", unwrapDataFromHtmlForm) rtr.HandleFunc("/wrap", wrapDataFromHtmlForm) rtr.HandleFunc("/genpassword/{passLength:[0-9]+}", genPassword) rtr.HandleFunc("/genpassword", genPassword) rtr.HandleFunc("/", unwrapDataFromHtmlForm) rtr.PathPrefix("/").Handler(http.FileServer(http.Dir("./static"))) http.Handle("/", rtr) if os.Getenv("LISTEN_PORT") != "" { ListenPort = os.Getenv("LISTEN_PORT") } else { if TlsEnable && ListenPort == ""{ ListenPort = "8443" } } listenAddr := ":" + ListenPort // ActionAddress = "https://" + ActionAddress if Debug { log.Printf("Адрес сервиса: %s%s ", ActionAddress, listenAddr) } log.Println("Listening...") if TlsEnable { log.Fatal(http.ListenAndServeTLS(listenAddr, TlsCertFile, TlsKeyFile, nil)) } else { http.ListenAndServe(listenAddr, nil) } }