diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d2e3fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.key +*.cert +*.pem +*.log + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e517f92 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:alpine AS build +RUN apk --no-cache add gcc g++ make git + +WORKDIR /go/src/app + +COPY . . +RUN go get ./... +RUN GOOS=linux go build -ldflags="-s -w" -o ./bin/vault-wrap ./vault.go +FROM alpine:3.20 +RUN apk add tzdata +#RUN apk --no-cache add ca-certificates +WORKDIR /usr/bin +COPY --from=build /go/src/app/bin /go/bin + +# COPY cronjobs /etc/crontabs/root + +./bin/vault-wrap -action-address "${ADDRESS}" -vault-url "${VAULT_ADDRESS}" -tls-cert "${TLS_CERT}" -tls-key "${TLS_KEY}" +# start crond with log level 8 in foreground, output to stderr +# CMD ["crond", "-f", "-d", "8"] diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5f2c5c9 --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module main + +go 1.21 + +toolchain go1.22.0 + +require github.com/hashicorp/vault-client-go v0.4.3 + +require ( + github.com/gorilla/mux v1.8.1 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/sethvargo/go-password v0.3.1 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/time v0.5.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7f6c795 --- /dev/null +++ b/go.sum @@ -0,0 +1,39 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= +github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/vault-client-go v0.4.3 h1:zG7STGVgn/VK6rnZc0k8PGbfv2x/sJExRKHSUg3ljWc= +github.com/hashicorp/vault-client-go v0.4.3/go.mod h1:4tDw7Uhq5XOxS1fO+oMtotHL7j4sB9cp0T7U6m4FzDY= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU= +github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/html-template/index.html b/html-template/index.html new file mode 100644 index 0000000..e4a773f --- /dev/null +++ b/html-template/index.html @@ -0,0 +1,32 @@ + + + + + + Data Unwrap Form + + + + + +
+ Расшифровать | + Сгенерировать пароль +

+
+ + + + + + +
+ +
+ +
+
+ + diff --git a/vault.go b/vault.go new file mode 100644 index 0000000..747906d --- /dev/null +++ b/vault.go @@ -0,0 +1,292 @@ +package main + +import ( + // "context" + "log" + // "time" + "os" + "fmt" + "flag" + "regexp" + "bytes" + "strconv" + "encoding/json" + "crypto/tls" + "net/http" + "html/template" + "path/filepath" + + "github.com/gorilla/mux" + "github.com/sethvargo/go-password/password" + // "io" + // "io/ioutil" + + // "github.com/hashicorp/vault-client-go" + // "github.com/hashicorp/vault-client-go/schema" +) + +// { + // "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 + Data string + ListenPort string + TlsCertFile string + TlsKeyFile string +) +type TemplateData struct { + URL string + TEXT 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 string `json: "wrap_info"` + Warnings string `json: "warnings"` + Auth string `json: "auth"` + Mount_type string `json: "mount_type"` + Error string `json: "errors"` +} + +func vaultDataWrap(vaultAddr string, vaultToken string, vaultSecretName string) string { + + customTransport := &(*http.DefaultTransport.(*http.Transport)) + customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + + client := &http.Client{Transport: customTransport} + // client := &http.Client{} + + req, _ := http.NewRequest("POST", vaultAddr, nil) + req.Header.Add("Accept", "application/json") + req.Header.Add("X-Vault-Token", vaultToken) + + resp, err := client.Do(req) + + if err != nil { + log.Println("Errored when sending request to the Vault server") + } + + var result map[string]interface{} + json.NewDecoder(resp.Body).Decode(&result) + secret := result["data"].(map[string]interface{})["data"].(map[string]interface{})[vaultSecretName] + log.Println(result) + return fmt.Sprint(secret) +} + +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) + } + + log.Println(resp) + + var result UnwrappedData + json.NewDecoder(resp.Body).Decode(&result) + secret := result.Data + if Debug { + log.Println(result) + log.Println(secret) + } + // 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 + ":" + ListenPort + + templateData.TEXT = Data + + // 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 getDataFromHtmlForm(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + token := r.FormValue("input_token") + + vaultPath := VaultAddress + "/v1/sys/wrapping/unwrap" + + // 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))) + } + 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 Debug { + log.Println(Data) + } + } else { + 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"] + // w.Write([]byte("Длина пароля " + passLength + "/n")) + passwordLength, err := strconv.Atoi(passLength) + if passwordLength > 1024 { + log.Printf("Oversized password length") + w.Write([]byte("Oversized password length")) + return + } + if err != nil { + log.Fatal(err) + } + res, err := password.Generate(passwordLength, 10, 5, false, true) + if err != nil { + log.Fatal(err) + } + log.Printf(res) + w.Write([]byte(res)) +} + +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)) +} + +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", "8443", "Номер порта сервиса") + flag.StringVar(&TlsCertFile, "tls-cert", "", "TLS сертификат (файл)") + flag.StringVar(&TlsKeyFile, "tls-key", "", "TLS ключ (файл)") + + 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 Debug { + log.Printf("Адрес сервера Hashicorp Vault: %s ", VaultAddress) + } + + rtr := mux.NewRouter() + rtr.HandleFunc("/unwrap", getDataFromHtmlForm) + rtr.HandleFunc("/genpassword/{passLength:[0-9]+}", genPassword) + rtr.HandleFunc("/genpassword", genPasswordDefault) + + rtr.HandleFunc("/", getDataFromHtmlForm) + rtr.PathPrefix("/").Handler(http.FileServer(http.Dir("./static"))) + + http.Handle("/", rtr) + if os.Getenv("LISTEN_PORT") != "" { + ListenPort = os.Getenv("LISTEN_PORT") + } + listenAddr := ":" + ListenPort + + log.Println("Listening...") + // http.ListenAndServe(":8080", nil) + log.Fatal(http.ListenAndServeTLS(listenAddr, TlsCertFile, TlsKeyFile, nil)) +}