Добавлена поддержка wrap
This commit is contained in:
parent
ee921f6ae3
commit
897bb3de01
|
@ -1,6 +1,6 @@
|
|||
# Vault Wrap/Unwrap
|
||||
|
||||
ВЭБ-интерфейс к сервису vault/unwrap для безопасной передачи секретов. Также генератор паролей.
|
||||
ВЭБ-интерфейс к сервису Hashicorp Vault wrap/unwrap для безопасной передачи секретов. Также генератор паролей.
|
||||
|
||||
## Запуск
|
||||
|
||||
|
@ -19,18 +19,21 @@ vault-wrap -action-address "http://saecret.example.ru:8080" -vault-url "https://
|
|||
|
||||
Если сервис запущен за обратным прокси, то в качестве адреса сервиса -action-address требуется указать адрес прокси, т.е. адрес (FQDN) на который будут приходить запросы.
|
||||
|
||||
Для работы шифрования (wrap) нужен vault токен с доступом к vault wrap/unwrap. Токен задается через переменную окружения VAULT_TOKEN.
|
||||
|
||||
## Ключи командной строки
|
||||
|
||||
- action-address string - Адрес данного сервиса (https://secret.example.ru). Адрес который будет подставляться в форму в html-шаблон
|
||||
- debug - Вывод отладочных сообщений
|
||||
- listen-port string - Номер порта сервиса (default "8080")
|
||||
- log-file string - Путь до лог-файла (default "vault-unwrap.log")
|
||||
- max-text-length - Максимальная длина текста для шифрования и длина пароля для генератора (default 100)
|
||||
- template-dir string -Каталог с шаблонами (default "html-template")
|
||||
- template-file string Файл-шаблон для ВЭБ-странцы (default "index.html")
|
||||
- tls - Использовать SSL/TLS
|
||||
- tls-cert string - TLS сертификат (полный путь к файлу)
|
||||
- tls-key string - TLS ключ (полный путь к файлу)
|
||||
- vault-url string - Адрес сервера Hashicorp Vault (https://host.name:8200)
|
||||
- token-ttl - Время жизни wrap-токена в секундах (default "3600")
|
||||
- vault-url string - Адрес сервера Hashicorp Vault (https://host.name:8200)
|
||||
- help - Вывод справочной информации
|
||||
|
||||
|
||||
|
|
|
@ -8,10 +8,13 @@ services:
|
|||
environment:
|
||||
- ACTION_ADDRESS=${ACTION_ADDRESS:-https://secret.example.ru}
|
||||
- VAULT_ADDRESS=${VAULT_ADDRESS}
|
||||
- VAULT_TOKEN=${WRAP_TOKEN}
|
||||
- LISTEN_PORT=8080
|
||||
- TLS_KEY_FILE=${TLS_KEY_FILE}
|
||||
- TLS_CERT_FILE=${TLS_CERT_FILE}
|
||||
- TZ=Europe/Moscow
|
||||
- MAX_TEXT_LENGTH=${MAX_TEXT_LENGTH:-100}
|
||||
- TOKEN_TTL=${TOKEN_TTL:-3600}
|
||||
restart: always
|
||||
# ports:
|
||||
# - 1234:8080
|
||||
|
@ -26,16 +29,48 @@ services:
|
|||
max-size: "10m"
|
||||
max-file: "5"
|
||||
labels:
|
||||
- "tra.enable=true"
|
||||
- "tra.http.routers.secret.rule=Host(`secret.example.ru`)"
|
||||
- "tra.http.services.secret.loadbalancer.server.port=8080"
|
||||
- "tra.docker.network=reverse-proxy"
|
||||
- "tra.http.routers.secret.tls=true"
|
||||
- "tra.http.services.secret.loadbalancer.server.scheme=http"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.secret.rule=Host(`secret.example.ru`)"
|
||||
- "traefik.http.services.secret.loadbalancer.server.port=8080"
|
||||
- "traefik.docker.network=reverse-proxy"
|
||||
- "traefik.http.routers.secret.tls=true"
|
||||
- "traefik.http.services.secret.loadbalancer.server.scheme=http"
|
||||
networks:
|
||||
- default
|
||||
- vault-wrap
|
||||
|
||||
traefik:
|
||||
image: traefik:v3.0
|
||||
container_name: traefik
|
||||
command:
|
||||
# - --entrypoints.web.address=:80
|
||||
# - --entrypoints.web-secure.address=:443
|
||||
# - --providers.docker=true
|
||||
- --providers.file.directory=/configuration/
|
||||
- --providers.file.watch=true
|
||||
volumes:
|
||||
- traefik-dynamic-conf:/configuration/
|
||||
- /home/gitlab-runner/traefik/traefik.yml:/traefik.yml:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- traefik-ssl:/ssl/:ro
|
||||
ports:
|
||||
- 80:80
|
||||
# - 8080:8080
|
||||
- 888:888
|
||||
- 443:443
|
||||
restart: always
|
||||
networks:
|
||||
- default
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.traefik.entrypoints=https"
|
||||
- "traefik.http.routers.traefik.rule=Host(`somehost.example.ru`)"
|
||||
- "traefik.http.routers.traefik.tls=true"
|
||||
# - "traefik.http.routers.traefik.tls.certresolver=letsEncrypt"
|
||||
- "traefik.http.routers.traefik.service=api@internal"
|
||||
- "traefik.http.services.traefik.loadbalancer.server.port=888"
|
||||
- "traefik.http.services.traefik.loadbalancer.server.scheme=https"
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: reverse-proxy
|
||||
|
@ -46,3 +81,5 @@ networks:
|
|||
volumes:
|
||||
vault-wrap-log:
|
||||
vault-wrap-conf:
|
||||
traefik-dynamic-conf:
|
||||
traefik-ssl:
|
||||
|
|
|
@ -3,6 +3,6 @@ set -u
|
|||
|
||||
while true ;do
|
||||
# /go/bin/vault-wrap -action-address "${ACTION_ADDRESS}" -vault-url "${VAULT_ADDRESS}" -tls-cert "/usr/local/share/vault-wrap/${TLS_CERT_FILE}" -tls-key "/usr/local/share/vault-wrap/${TLS_KEY_FILE}" -template-dir /usr/local/share/vault-wrap -log-file /var/log/vault-wrap/vault-wrap.log -listen-port "${LISTEN_PORT}" -tls
|
||||
/go/bin/vault-wrap -action-address "${ACTION_ADDRESS}" -template-dir /usr/local/share/vault-wrap -log-file /var/log/vault-wrap/vault-wrap.log
|
||||
/go/bin/vault-wrap -action-address "${ACTION_ADDRESS}" -template-dir /usr/local/share/vault-wrap -log-file /var/log/vault-wrap/vault-wrap.log -token-ttl ${TOKEN_TTL}
|
||||
sleep 120
|
||||
done
|
||||
|
|
127
vault.go
127
vault.go
|
@ -1,3 +1,10 @@
|
|||
// -------------------------------------------
|
||||
// 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 (
|
||||
|
@ -10,6 +17,8 @@ import (
|
|||
"regexp"
|
||||
"bytes"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"encoding/json"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
|
@ -41,15 +50,19 @@ var (
|
|||
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
|
||||
}
|
||||
type UnwrappedData struct {
|
||||
Rerquest_id string `json: "request_id"`
|
||||
|
@ -57,24 +70,41 @@ type UnwrappedData struct {
|
|||
Renewable bool `json: "renewable"`
|
||||
Lease_daration int `json:"lease_duration"`
|
||||
Data map[string]string `json: "data"`
|
||||
Wrap_info string `json: "wrap_info"`
|
||||
Wrap_info *WrapInfo `json: "wrap_info"`
|
||||
Warnings string `json: "warnings"`
|
||||
Auth string `json: "auth"`
|
||||
Mount_type string `json: "mount_type"`
|
||||
Error string `json: "errors"`
|
||||
Errors string `json: "errors"`
|
||||
}
|
||||
|
||||
func vaultDataWrap(vaultAddr string, vaultToken string, vaultSecretName string) string {
|
||||
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, nil)
|
||||
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-Token", VaultToken)
|
||||
req.Header.Add("X-Vault-Wrap-TTL", TokenTTL)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
|
||||
|
@ -82,11 +112,16 @@ func vaultDataWrap(vaultAddr string, vaultToken string, vaultSecretName string)
|
|||
log.Println("Errored when sending request to the Vault server")
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
var result UnwrappedData
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
secret := result["data"].(map[string]interface{})["data"].(map[string]interface{})[vaultSecretName]
|
||||
log.Println(result)
|
||||
return fmt.Sprint(secret)
|
||||
// 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 {
|
||||
|
@ -115,9 +150,13 @@ func vaultDataUnWrap(vaultAddr string, vaultWrapToken string) map[string]string
|
|||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
secret := result.Data
|
||||
if Debug {
|
||||
log.Println(result)
|
||||
log.Println(secret)
|
||||
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)
|
||||
|
@ -151,8 +190,8 @@ func getStaticPage(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// templateData.UUID = uuid
|
||||
templateData.URL = ActionAddress
|
||||
|
||||
templateData.TEXT = Data
|
||||
templateData.MAXTEXTLENGTH = MaxTextLength
|
||||
|
||||
// templateData.URL = FishingUrl + "/" + arrUsers[i].messageUUID
|
||||
if body, err := ParseTemplate(template, templateData); err == nil {
|
||||
|
@ -163,7 +202,7 @@ func getStaticPage(w http.ResponseWriter, r *http.Request) {
|
|||
// hvs.CAES - 95
|
||||
// s.Dj7kZS - 26
|
||||
|
||||
func getDataFromHtmlForm(w http.ResponseWriter, r *http.Request) {
|
||||
func unwrapDataFromHtmlForm(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
token := r.FormValue("input_token")
|
||||
|
||||
|
@ -173,7 +212,7 @@ func getDataFromHtmlForm(w http.ResponseWriter, r *http.Request) {
|
|||
// fmt.Fprintln(w, r.URL.RawQuery)
|
||||
// log.Println(w, r.URL.RawQuery)
|
||||
if Debug {
|
||||
log.Printf("Текст для расшифровки: %s ", token)
|
||||
log.Printf("Текст для шифровки: %s ", token)
|
||||
log.Printf("Адрес сервера Hashicorp Vault: %s ", vaultPath)
|
||||
}
|
||||
// Проверка текста на соответствие шаблону
|
||||
|
@ -181,15 +220,26 @@ func getDataFromHtmlForm(w http.ResponseWriter, r *http.Request) {
|
|||
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)
|
||||
|
@ -209,7 +259,7 @@ func genPassword(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
// w.Write([]byte("Длина пароля " + passLength + "/n"))
|
||||
passwordLength, err := strconv.Atoi(passLength)
|
||||
if passwordLength > 1024 {
|
||||
if passwordLength > MaxTextLength {
|
||||
log.Printf("Oversized password length")
|
||||
Data = "Превышена длина пароля"
|
||||
getStaticPage(w, r)
|
||||
|
@ -242,6 +292,33 @@ func genPasswordDefault(w http.ResponseWriter, r *http.Request) {
|
|||
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)
|
||||
}
|
||||
} else if secret != "" {
|
||||
Data = "Введите текст для шифровки."
|
||||
}
|
||||
}
|
||||
getStaticPage(w, r)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var (
|
||||
logFile string
|
||||
|
@ -256,6 +333,8 @@ func main() {
|
|||
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()
|
||||
|
||||
|
@ -281,17 +360,30 @@ func main() {
|
|||
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", getDataFromHtmlForm)
|
||||
rtr.HandleFunc("/unwrap", unwrapDataFromHtmlForm)
|
||||
rtr.HandleFunc("/wrap", wrapDataFromHtmlForm)
|
||||
rtr.HandleFunc("/genpassword/{passLength:[0-9]+}", genPassword)
|
||||
rtr.HandleFunc("/genpassword", genPassword)
|
||||
|
||||
rtr.HandleFunc("/", getDataFromHtmlForm)
|
||||
rtr.HandleFunc("/", unwrapDataFromHtmlForm)
|
||||
rtr.PathPrefix("/").Handler(http.FileServer(http.Dir("./static")))
|
||||
|
||||
http.Handle("/", rtr)
|
||||
|
@ -316,4 +408,3 @@ func main() {
|
|||
http.ListenAndServe(listenAddr, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user