Compare commits
5 Commits
ee921f6ae3
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c4ded33507 | ||
10c9635894 | |||
![]() |
7ecdf7a606 | ||
41e1a553f6 | |||
![]() |
897bb3de01 |
@@ -1,6 +1,6 @@
|
|||||||
# Vault Wrap/Unwrap
|
# Vault Wrap/Unwrap
|
||||||
|
|
||||||
ВЭБ-интерфейс к сервису vault/unwrap для безопасной передачи секретов. Также генератор паролей.
|
ВЭБ-интерфейс к сервису Hashicorp Vault wrap/unwrap для безопасной передачи секретов. Также генератор паролей.
|
||||||
|
|
||||||
## Запуск
|
## Запуск
|
||||||
|
|
||||||
@@ -14,23 +14,26 @@ vault-wrap -action-address "https://secret.example.ru:8443" -vault-url "https://
|
|||||||
|
|
||||||
Запуск с доступом по http:
|
Запуск с доступом по http:
|
||||||
```
|
```
|
||||||
vault-wrap -action-address "http://saecret.example.ru:8080" -vault-url "https://vault.example.ru:8200" -tls-cert cert.pem -tls-key privaty.key -listen-port 8080
|
vault-wrap -action-address "http://secret.example.ru:8080" -vault-url "https://vault.example.ru:8200" -tls-cert cert.pem -tls-key privaty.key -listen-port 8080
|
||||||
```
|
```
|
||||||
|
|
||||||
Если сервис запущен за обратным прокси, то в качестве адреса сервиса -action-address требуется указать адрес прокси, т.е. адрес (FQDN) на который будут приходить запросы.
|
Если сервис запущен за обратным прокси, то в качестве адреса сервиса -action-address требуется указать адрес прокси, т.е. адрес (FQDN) на который будут приходить запросы.
|
||||||
|
|
||||||
|
Для работы шифрования (wrap) нужен vault токен с доступом к vault wrap/unwrap. Токен задается через переменную окружения VAULT_TOKEN.
|
||||||
|
|
||||||
## Ключи командной строки
|
## Ключи командной строки
|
||||||
|
|
||||||
- action-address string - Адрес данного сервиса (https://secret.example.ru). Адрес который будет подставляться в форму в html-шаблон
|
- action-address string - Адрес данного сервиса (https://secret.example.ru). Адрес который будет подставляться в форму в html-шаблон
|
||||||
- debug - Вывод отладочных сообщений
|
- debug - Вывод отладочных сообщений
|
||||||
- listen-port string - Номер порта сервиса (default "8080")
|
- listen-port string - Номер порта сервиса (default "8080")
|
||||||
- log-file string - Путь до лог-файла (default "vault-unwrap.log")
|
- log-file string - Путь до лог-файла (default "vault-unwrap.log")
|
||||||
|
- max-text-length - Максимальная длина текста для шифрования и длина пароля для генератора (default 100)
|
||||||
- template-dir string -Каталог с шаблонами (default "html-template")
|
- template-dir string -Каталог с шаблонами (default "html-template")
|
||||||
- template-file string Файл-шаблон для ВЭБ-странцы (default "index.html")
|
- template-file string Файл-шаблон для ВЭБ-странцы (default "index.html")
|
||||||
- tls - Использовать SSL/TLS
|
- tls - Использовать SSL/TLS
|
||||||
- tls-cert string - TLS сертификат (полный путь к файлу)
|
- tls-cert string - TLS сертификат (полный путь к файлу)
|
||||||
- tls-key string - TLS ключ (полный путь к файлу)
|
- tls-key string - TLS ключ (полный путь к файлу)
|
||||||
|
- token-ttl - Время жизни wrap-токена в секундах (default "3600")
|
||||||
- vault-url string - Адрес сервера Hashicorp Vault (https://host.name:8200)
|
- vault-url string - Адрес сервера Hashicorp Vault (https://host.name:8200)
|
||||||
- help - Вывод справочной информации
|
- help - Вывод справочной информации
|
||||||
|
|
||||||
|
|
||||||
|
@@ -8,10 +8,13 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- ACTION_ADDRESS=${ACTION_ADDRESS:-https://secret.example.ru}
|
- ACTION_ADDRESS=${ACTION_ADDRESS:-https://secret.example.ru}
|
||||||
- VAULT_ADDRESS=${VAULT_ADDRESS}
|
- VAULT_ADDRESS=${VAULT_ADDRESS}
|
||||||
|
- VAULT_TOKEN=${WRAP_TOKEN}
|
||||||
- LISTEN_PORT=8080
|
- LISTEN_PORT=8080
|
||||||
- TLS_KEY_FILE=${TLS_KEY_FILE}
|
- TLS_KEY_FILE=${TLS_KEY_FILE}
|
||||||
- TLS_CERT_FILE=${TLS_CERT_FILE}
|
- TLS_CERT_FILE=${TLS_CERT_FILE}
|
||||||
- TZ=Europe/Moscow
|
- TZ=Europe/Moscow
|
||||||
|
- MAX_TEXT_LENGTH=${MAX_TEXT_LENGTH:-100}
|
||||||
|
- TOKEN_TTL=${TOKEN_TTL:-3600}
|
||||||
restart: always
|
restart: always
|
||||||
# ports:
|
# ports:
|
||||||
# - 1234:8080
|
# - 1234:8080
|
||||||
@@ -26,16 +29,48 @@ services:
|
|||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
max-file: "5"
|
max-file: "5"
|
||||||
labels:
|
labels:
|
||||||
- "tra.enable=true"
|
- "traefik.enable=true"
|
||||||
- "tra.http.routers.secret.rule=Host(`secret.example.ru`)"
|
- "traefik.http.routers.secret.rule=Host(`secret.example.ru`)"
|
||||||
- "tra.http.services.secret.loadbalancer.server.port=8080"
|
- "traefik.http.services.secret.loadbalancer.server.port=8080"
|
||||||
- "tra.docker.network=reverse-proxy"
|
- "traefik.docker.network=reverse-proxy"
|
||||||
- "tra.http.routers.secret.tls=true"
|
- "traefik.http.routers.secret.tls=true"
|
||||||
- "tra.http.services.secret.loadbalancer.server.scheme=http"
|
- "traefik.http.services.secret.loadbalancer.server.scheme=http"
|
||||||
networks:
|
networks:
|
||||||
- default
|
- default
|
||||||
- vault-wrap
|
- 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(`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:
|
networks:
|
||||||
default:
|
default:
|
||||||
name: reverse-proxy
|
name: reverse-proxy
|
||||||
@@ -46,3 +81,5 @@ networks:
|
|||||||
volumes:
|
volumes:
|
||||||
vault-wrap-log:
|
vault-wrap-log:
|
||||||
vault-wrap-conf:
|
vault-wrap-conf:
|
||||||
|
traefik-dynamic-conf:
|
||||||
|
traefik-ssl:
|
||||||
|
@@ -3,6 +3,6 @@ set -u
|
|||||||
|
|
||||||
while true ;do
|
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}" -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
|
sleep 120
|
||||||
done
|
done
|
||||||
|
78
html-template/file-viewer.html
Normal file
78
html-template/file-viewer.html
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{.Title}}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
.file-container {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.file-info {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.download-btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
.action-btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="file-container">
|
||||||
|
<h1>{{.Title}}</h1>
|
||||||
|
|
||||||
|
{{if .IsImage}}
|
||||||
|
<img src="data:{{.MimeType}};base64,{{.Base64Data}}" alt="File preview">
|
||||||
|
{{else}}
|
||||||
|
<div class="file-info">
|
||||||
|
<p>Этот файл не может быть отображен в браузере</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="file-info">
|
||||||
|
<p><strong>Тип файла:</strong> {{.MimeType}}</p>
|
||||||
|
<p><strong>Размер:</strong> {{.FileSize}} KB</p>
|
||||||
|
<p><strong>Имя файла:</strong> {{.FileName}}</p>
|
||||||
|
|
||||||
|
<a href="data:{{.MimeType}};base64,{{.Base64Data}}"
|
||||||
|
class="download-btn"
|
||||||
|
download="{{.FileName}}">
|
||||||
|
Скачать файл
|
||||||
|
</a>
|
||||||
|
<a href="/" class="action-btn">На главную</a>
|
||||||
|
<a href="javascript:history.back()" class="action-btn">Назад</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
57
html-template/index-old.html
Normal file
57
html-template/index-old.html
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Data Unwrap Form</title>
|
||||||
|
<style>
|
||||||
|
textarea { width: 100%; max-width: 500px; }
|
||||||
|
table { border-collapse: collapse; }
|
||||||
|
hr { margin: 15px 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<!-- Форма для wrap/unwrap текста -->
|
||||||
|
<tr><td>
|
||||||
|
<form method="post" action="{{.URL}}/wrap">
|
||||||
|
<table>
|
||||||
|
<tr><td>Введите текст или токен:</td></tr>
|
||||||
|
<tr><td>
|
||||||
|
<textarea name="input_token" cols="50" rows="10" maxlength="{{.MAXTEXTLENGTH}}">{{.TEXT}}</textarea>
|
||||||
|
</td></tr>
|
||||||
|
<tr><td align="right">
|
||||||
|
<button type="submit">Зашифровать</button>
|
||||||
|
<button type="submit" formaction="{{.URL}}/unwrap">Расшифровать</button>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
</td></tr>
|
||||||
|
|
||||||
|
<tr><td><hr></td></tr>
|
||||||
|
|
||||||
|
<!-- Отдельная форма для загрузки файла -->
|
||||||
|
<tr><td>
|
||||||
|
<form method="post" action="{{.URL}}/wrapfile" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="file" id="file" required>
|
||||||
|
<button type="submit">Зашифровать файл</button>
|
||||||
|
</form>
|
||||||
|
</td></tr>
|
||||||
|
|
||||||
|
<tr><td><hr></td></tr>
|
||||||
|
|
||||||
|
<!-- Форма для генерации пароля -->
|
||||||
|
<tr><td>
|
||||||
|
<form method="post" action="{{.URL}}/genpassword">
|
||||||
|
<table>
|
||||||
|
<tr><td align="right">
|
||||||
|
Длина пароля (от 15 до {{.MAXTEXTLENGTH}}):
|
||||||
|
<input type="number" name="passlength" min="15" max="{{.MAXTEXTLENGTH}}" value="32">
|
||||||
|
<button type="submit">Сгенерировать пароль</button>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -1,38 +1,206 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Data Unwrap Form</title>
|
<title>Data Wrap/Unwrap Form</title>
|
||||||
</head>
|
<style>
|
||||||
<body>
|
body {
|
||||||
<table>
|
font-family: Arial, sans-serif;
|
||||||
<tr><td>
|
max-width: 800px;
|
||||||
<!-- <a href={{.URL}}/unwrap>Расшифровать</a> |
|
margin: 0 auto;
|
||||||
<a href={{.URL}}/genpassword>Сгенерировать пароль</a>-->
|
padding: 20px;
|
||||||
<tr><td><p></p></td></tr>
|
background-color: #f9f9f9;
|
||||||
<tr><td>
|
display: flex;
|
||||||
<form method="post">
|
flex-direction: column;
|
||||||
<table>
|
min-height: 100vh;
|
||||||
<tr><td>
|
box-sizing: border-box; /* Добавлено */
|
||||||
Введите токен:
|
}
|
||||||
</td></tr>
|
.content {
|
||||||
<tr><td>
|
flex: 1;
|
||||||
<textarea id="wrapped_token" name="input_token" cols=50 rows=10>{{ .TEXT }}</textarea>
|
padding-bottom: 20px; /* Добавлено */
|
||||||
</td></tr>
|
}
|
||||||
<tr><td align=right>
|
.form-container {
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 15px; /* Уменьшено */
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 25px; /* Уменьшено */
|
||||||
|
font-size: 1.8em; /* Добавлено */
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
min-height: 120px; /* Уменьшено */
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
resize: vertical;
|
||||||
|
box-sizing: border-box; /* Добавлено */
|
||||||
|
}
|
||||||
|
input[type="file"] {
|
||||||
|
margin: 8px 0; /* Уменьшено */
|
||||||
|
width: 100%; /* Добавлено */
|
||||||
|
}
|
||||||
|
input[type="number"] {
|
||||||
|
width: 60px;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 4px; /* Уменьшено */
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #45a049;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
height: 1px;
|
||||||
|
background: #ddd;
|
||||||
|
margin: 15px 0; /* Уменьшено */
|
||||||
|
}
|
||||||
|
.form-title {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 8px; /* Уменьшено */
|
||||||
|
color: #555;
|
||||||
|
font-size: 0.95em; /* Добавлено */
|
||||||
|
}
|
||||||
|
.button-group {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 8px; /* Уменьшено */
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 12px 0;
|
||||||
|
color: #777;
|
||||||
|
font-size: 0.85em; /* Уменьшено */
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
background: white;
|
||||||
|
margin-top: auto; /* Важно для прижатия */
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.footer-links {
|
||||||
|
margin-top: 6px; /* Уменьшено */
|
||||||
|
}
|
||||||
|
.footer-links a {
|
||||||
|
color: #4CAF50;
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 0 6px; /* Уменьшено */
|
||||||
|
font-size: 0.85em; /* Уменьшено */
|
||||||
|
}
|
||||||
|
.footer-links a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
body {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
.form-container {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.btn-link {
|
||||||
|
background: #9C27B0;
|
||||||
|
}
|
||||||
|
.btn-link:hover {
|
||||||
|
background: #7B1FA2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<!-- <h1>Сервис передачи секретов</h1> -->
|
||||||
|
|
||||||
|
<div class="form-container">
|
||||||
|
<div class="form-title">Введите текст или токен:</div>
|
||||||
|
<form method="post" action="{{.URL}}/wrap">
|
||||||
|
<textarea id="tokenTextarea" name="input_token" maxlength="{{.MAXTEXTLENGTH}}" placeholder="Введите текст или токен...">{{.TEXT}}</textarea>
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="submit">Зашифровать</button>
|
||||||
<button type="submit" formaction="{{.URL}}/unwrap">Расшифровать</button>
|
<button type="submit" formaction="{{.URL}}/unwrap">Расшифровать</button>
|
||||||
<tr><td><hr></td></tr>
|
<button type="button" class="btn-copy" onclick="copyToken()">Скопировать</button>
|
||||||
</td></tr>
|
</div>
|
||||||
<tr><td align=right>
|
|
||||||
Длина пароля (от 15 до 1024)
|
|
||||||
<input type="text" name="passlength"/ size=4 pattern="[0-9]{2,4}">
|
|
||||||
<button type="submit" formaction="{{.URL}}/genpassword">Сгенерировать пароль</button>
|
|
||||||
</td></tr>
|
|
||||||
</form>
|
</form>
|
||||||
</td></tr>
|
</div>
|
||||||
<tr><td>
|
|
||||||
</td></tr>
|
<div class="form-container">
|
||||||
</table>
|
<div class="form-title">Выберите файл (до 100кб):</div>
|
||||||
</body>
|
<form method="post" action="{{.URL}}/wrapfile" enctype="multipart/form-data">
|
||||||
|
<input type="file" name="file" id="file" required>
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="submit">Зашифровать файл</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-container">
|
||||||
|
<div class="form-title">Генерация пароля:</div>
|
||||||
|
<form method="post" action="{{.URL}}/genpassword">
|
||||||
|
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;">
|
||||||
|
<div style="margin-bottom: 8px;">
|
||||||
|
Длина пароля (от 15 до {{.MAXTEXTLENGTH}}):
|
||||||
|
<input type="number" name="passlength" min="15" max="{{.MAXTEXTLENGTH}}" value="32">
|
||||||
|
</div>
|
||||||
|
<button type="submit" style="margin-left: auto;">Сгенерировать пароль</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<div>© <script>document.write(new Date().getFullYear())</script> SVK</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function copyToken() {
|
||||||
|
const textarea = document.getElementById('tokenTextarea');
|
||||||
|
const fullText = textarea.value;
|
||||||
|
const token = fullText.split('\n')[0].trim();
|
||||||
|
|
||||||
|
copyToClipboard(token, '.btn-copy');
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard(text, buttonSelector) {
|
||||||
|
const tempInput = document.createElement('input');
|
||||||
|
tempInput.value = text;
|
||||||
|
document.body.appendChild(tempInput);
|
||||||
|
tempInput.select();
|
||||||
|
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(tempInput);
|
||||||
|
|
||||||
|
const copyBtn = document.querySelector(buttonSelector);
|
||||||
|
const originalText = copyBtn.textContent;
|
||||||
|
copyBtn.textContent = 'Скопировано!';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
copyBtn.textContent = originalText;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
320
vault.go
320
vault.go
@@ -1,16 +1,27 @@
|
|||||||
|
// -------------------------------------------
|
||||||
|
// 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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "context"
|
// "context"
|
||||||
"log"
|
"log"
|
||||||
// "time"
|
// "time"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"fmt"
|
"fmt"
|
||||||
"flag"
|
"flag"
|
||||||
"regexp"
|
"regexp"
|
||||||
"bytes"
|
"bytes"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/base64"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net/http"
|
"net/http"
|
||||||
"html/template"
|
"html/template"
|
||||||
@@ -41,15 +52,20 @@ var (
|
|||||||
TemplateFile string
|
TemplateFile string
|
||||||
ActionAddress string
|
ActionAddress string
|
||||||
VaultAddress string
|
VaultAddress string
|
||||||
|
VaultToken string
|
||||||
Data string
|
Data string
|
||||||
ListenPort string
|
ListenPort string
|
||||||
TlsEnable bool
|
TlsEnable bool
|
||||||
TlsCertFile string
|
TlsCertFile string
|
||||||
TlsKeyFile string
|
TlsKeyFile string
|
||||||
|
MaxTextLength int
|
||||||
|
TokenTTL string
|
||||||
)
|
)
|
||||||
type TemplateData struct {
|
type TemplateData struct {
|
||||||
URL string
|
URL string
|
||||||
TEXT string
|
TEXT string
|
||||||
|
MAXTEXTLENGTH int
|
||||||
|
TOKENTTL string
|
||||||
}
|
}
|
||||||
type UnwrappedData struct {
|
type UnwrappedData struct {
|
||||||
Rerquest_id string `json: "request_id"`
|
Rerquest_id string `json: "request_id"`
|
||||||
@@ -57,14 +73,60 @@ type UnwrappedData struct {
|
|||||||
Renewable bool `json: "renewable"`
|
Renewable bool `json: "renewable"`
|
||||||
Lease_daration int `json:"lease_duration"`
|
Lease_daration int `json:"lease_duration"`
|
||||||
Data map[string]string `json: "data"`
|
Data map[string]string `json: "data"`
|
||||||
Wrap_info string `json: "wrap_info"`
|
Wrap_info *WrapInfo `json: "wrap_info"`
|
||||||
Warnings string `json: "warnings"`
|
Warnings string `json: "warnings"`
|
||||||
Auth string `json: "auth"`
|
Auth string `json: "auth"`
|
||||||
Mount_type string `json: "mount_type"`
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileViewerData struct {
|
||||||
|
Title string
|
||||||
|
Base64Data string
|
||||||
|
MimeType string
|
||||||
|
FileSize string
|
||||||
|
FileName string
|
||||||
|
IsImage bool
|
||||||
|
}
|
||||||
|
// const MaxTextLength = 100
|
||||||
|
|
||||||
|
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:", content)
|
||||||
|
}
|
||||||
|
|
||||||
customTransport := &(*http.DefaultTransport.(*http.Transport))
|
customTransport := &(*http.DefaultTransport.(*http.Transport))
|
||||||
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
@@ -72,9 +134,10 @@ func vaultDataWrap(vaultAddr string, vaultToken string, vaultSecretName string)
|
|||||||
client := &http.Client{Transport: customTransport}
|
client := &http.Client{Transport: customTransport}
|
||||||
// client := &http.Client{}
|
// 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("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)
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
@@ -82,11 +145,16 @@ func vaultDataWrap(vaultAddr string, vaultToken string, vaultSecretName string)
|
|||||||
log.Println("Errored when sending request to the Vault server")
|
log.Println("Errored when sending request to the Vault server")
|
||||||
}
|
}
|
||||||
|
|
||||||
var result map[string]interface{}
|
var result UnwrappedData
|
||||||
json.NewDecoder(resp.Body).Decode(&result)
|
json.NewDecoder(resp.Body).Decode(&result)
|
||||||
secret := result["data"].(map[string]interface{})["data"].(map[string]interface{})[vaultSecretName]
|
// wrappedToken := result.Data
|
||||||
log.Println(result)
|
if Debug {
|
||||||
return fmt.Sprint(secret)
|
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 {
|
func vaultDataUnWrap(vaultAddr string, vaultWrapToken string) map[string]string {
|
||||||
@@ -115,9 +183,13 @@ func vaultDataUnWrap(vaultAddr string, vaultWrapToken string) map[string]string
|
|||||||
json.NewDecoder(resp.Body).Decode(&result)
|
json.NewDecoder(resp.Body).Decode(&result)
|
||||||
secret := result.Data
|
secret := result.Data
|
||||||
if Debug {
|
if Debug {
|
||||||
log.Println(result)
|
log.Println("result", result)
|
||||||
log.Println(secret)
|
log.Println("secret", secret)
|
||||||
}
|
}
|
||||||
|
// log.Println("Length=", len(secret))
|
||||||
|
// if len(secret) == 0 {
|
||||||
|
// log.Println("Error:", result.Errors)
|
||||||
|
// }
|
||||||
// fmt.Sprint(secret)
|
// fmt.Sprint(secret)
|
||||||
// for v, k := range secret {
|
// for v, k := range secret {
|
||||||
// log.Println(k, v)
|
// log.Println(k, v)
|
||||||
@@ -151,8 +223,9 @@ func getStaticPage(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// templateData.UUID = uuid
|
// templateData.UUID = uuid
|
||||||
templateData.URL = ActionAddress
|
templateData.URL = ActionAddress
|
||||||
|
|
||||||
templateData.TEXT = Data
|
templateData.TEXT = Data
|
||||||
|
templateData.MAXTEXTLENGTH = MaxTextLength
|
||||||
|
templateData.TOKENTTL = TokenTTL
|
||||||
|
|
||||||
// templateData.URL = FishingUrl + "/" + arrUsers[i].messageUUID
|
// templateData.URL = FishingUrl + "/" + arrUsers[i].messageUUID
|
||||||
if body, err := ParseTemplate(template, templateData); err == nil {
|
if body, err := ParseTemplate(template, templateData); err == nil {
|
||||||
@@ -160,39 +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
|
// hvs.CAES - 95
|
||||||
// s.Dj7kZS - 26
|
// s.Dj7kZS - 26
|
||||||
|
|
||||||
func getDataFromHtmlForm(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
func unwrapDataFromHtmlForm(w http.ResponseWriter, r *http.Request) {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
token := r.FormValue("input_token")
|
token := r.FormValue("input_token")
|
||||||
|
|
||||||
vaultPath := VaultAddress + "/v1/sys/wrapping/unwrap"
|
vaultPath := VaultAddress + "/v1/sys/wrapping/unwrap"
|
||||||
|
|
||||||
Data = ""
|
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\-\_]+`)
|
re := regexp.MustCompile(`^(hvs|s)\.[\w\-\_]+`)
|
||||||
if Debug {
|
token = strings.TrimSpace(strings.ReplaceAll(token, "\r\n", ""))
|
||||||
fmt.Println(re.Match([]byte(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
|
||||||
}
|
}
|
||||||
if token != "" && re.Match([]byte(token)) {
|
fileData = decoded
|
||||||
|
fileName = key
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileData != nil {
|
||||||
|
// Если нашли файл - показываем просмотрщик
|
||||||
|
showFileViewer(w, r, fileData, fileName)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
// Если не нашли ни текста, ни файла - выводим все данные как текст
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
for key, value := range vaultDataUnWrap(vaultPath, token) {
|
for key, value := range unwrappedData {
|
||||||
fmt.Fprintf(b, "%s: %s\n", key, value)
|
fmt.Fprintf(b, "%s: %s\n", key, value)
|
||||||
}
|
}
|
||||||
Data = b.String()
|
Data = b.String()
|
||||||
if Debug {
|
|
||||||
log.Println(Data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Data == "" {
|
||||||
|
Data = "Ошибка! Токен не содержит данных."
|
||||||
|
}
|
||||||
|
} else if token != "" {
|
||||||
|
Data = "Введенные данные не соответствуют формату. Введите корректный токен."
|
||||||
|
}
|
||||||
|
|
||||||
getStaticPage(w, r)
|
getStaticPage(w, r)
|
||||||
// http.Redirect(w, r, "http://"+r.Host, http.StatusMovedPermanently)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func genPassword(w http.ResponseWriter, r *http.Request) {
|
func genPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -209,7 +343,7 @@ func genPassword(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
// w.Write([]byte("Длина пароля " + passLength + "/n"))
|
// w.Write([]byte("Длина пароля " + passLength + "/n"))
|
||||||
passwordLength, err := strconv.Atoi(passLength)
|
passwordLength, err := strconv.Atoi(passLength)
|
||||||
if passwordLength > 1024 {
|
if passwordLength > MaxTextLength {
|
||||||
log.Printf("Oversized password length")
|
log.Printf("Oversized password length")
|
||||||
Data = "Превышена длина пароля"
|
Data = "Превышена длина пароля"
|
||||||
getStaticPage(w, r)
|
getStaticPage(w, r)
|
||||||
@@ -218,7 +352,7 @@ func genPassword(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
res, err := password.Generate(passwordLength, 10, 5, false, true)
|
res, err := password.Generate(passwordLength, 5, 5, false, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
@@ -242,6 +376,111 @@ func genPasswordDefault(w http.ResponseWriter, r *http.Request) {
|
|||||||
getStaticPage(w, r)
|
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, "text", 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 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() {
|
func main() {
|
||||||
var (
|
var (
|
||||||
logFile string
|
logFile string
|
||||||
@@ -256,6 +495,8 @@ func main() {
|
|||||||
flag.StringVar(&TlsCertFile, "tls-cert", "", "TLS сертификат (файл)")
|
flag.StringVar(&TlsCertFile, "tls-cert", "", "TLS сертификат (файл)")
|
||||||
flag.StringVar(&TlsKeyFile, "tls-key", "", "TLS ключ (файл)")
|
flag.StringVar(&TlsKeyFile, "tls-key", "", "TLS ключ (файл)")
|
||||||
flag.BoolVar(&TlsEnable, "tls", false, "Использовать SSL/TLS")
|
flag.BoolVar(&TlsEnable, "tls", false, "Использовать SSL/TLS")
|
||||||
|
flag.IntVar(&MaxTextLength, "max-text-length", 100 , "Максимальная длина текста для шифрования и длина пароля для генератора")
|
||||||
|
flag.StringVar(&TokenTTL, "token-ttl", "3600", "Время жизни wrap-токена в секундах")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -281,17 +522,31 @@ func main() {
|
|||||||
VaultAddress = os.Getenv("VAULT_ADDRESS")
|
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 {
|
if Debug {
|
||||||
log.Printf("Адрес сервера Hashicorp Vault: %s ", VaultAddress)
|
log.Printf("Адрес сервера Hashicorp Vault: %s ", VaultAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
rtr := mux.NewRouter()
|
rtr := mux.NewRouter()
|
||||||
rtr.HandleFunc("/unwrap", getDataFromHtmlForm)
|
rtr.HandleFunc("/unwrap", unwrapDataFromHtmlForm)
|
||||||
|
rtr.HandleFunc("/wrap", wrapDataFromHtmlForm)
|
||||||
|
rtr.HandleFunc("/wrapfile", wrapDataFromFile).Methods("POST")
|
||||||
rtr.HandleFunc("/genpassword/{passLength:[0-9]+}", genPassword)
|
rtr.HandleFunc("/genpassword/{passLength:[0-9]+}", genPassword)
|
||||||
rtr.HandleFunc("/genpassword", genPassword)
|
rtr.HandleFunc("/genpassword", genPassword)
|
||||||
|
|
||||||
rtr.HandleFunc("/", getDataFromHtmlForm)
|
rtr.HandleFunc("/", unwrapDataFromHtmlForm)
|
||||||
rtr.PathPrefix("/").Handler(http.FileServer(http.Dir("./static")))
|
rtr.PathPrefix("/").Handler(http.FileServer(http.Dir("./static")))
|
||||||
|
|
||||||
http.Handle("/", rtr)
|
http.Handle("/", rtr)
|
||||||
@@ -316,4 +571,3 @@ func main() {
|
|||||||
http.ListenAndServe(listenAddr, nil)
|
http.ListenAndServe(listenAddr, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user