keylocker 0.3.5__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- keylocker-0.4.2/PKG-INFO +154 -0
- keylocker-0.4.2/README.md +136 -0
- keylocker-0.4.2/keylocker/__init__.py +207 -0
- {keylocker-0.3.5 → keylocker-0.4.2}/keylocker/__main__.py +2 -2
- keylocker-0.4.2/keylocker/requirements.txt +5 -0
- keylocker-0.4.2/pyproject.toml +28 -0
- keylocker-0.3.5/PKG-INFO +0 -52
- keylocker-0.3.5/README.md +0 -31
- keylocker-0.3.5/keylocker/__init__.py +0 -99
- keylocker-0.3.5/pyproject.toml +0 -25
- keylocker-0.3.5/setup.py +0 -31
keylocker-0.4.2/PKG-INFO
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: keylocker
|
|
3
|
+
Version: 0.4.2
|
|
4
|
+
Summary: Инструмент CLI и библиотека для управления секретами в YAML файлах с шифрованием.
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: vpuhoff
|
|
7
|
+
Author-email: vpuhoff@live.ru
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Topic :: Utilities
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Keylocker CLI (YAML Edition)
|
|
19
|
+
|
|
20
|
+
Инструмент командной строки (CLI) и библиотека Python для управления секретами непосредственно в YAML файлах конфигурации. Позволяет шифровать отдельные значения с помощью тега `!SEC` и загружать значения из переменных окружения с помощью тега `!ENV`.
|
|
21
|
+
|
|
22
|
+
## Пример YAML файла (`config.yaml`):
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
# config.yaml
|
|
26
|
+
database:
|
|
27
|
+
host: db.example.com
|
|
28
|
+
username: user
|
|
29
|
+
# Зашифрованный пароль с помощью keylocker encrypt
|
|
30
|
+
password: !SEC gAAAAABh...[rest of encrypted data]...
|
|
31
|
+
api:
|
|
32
|
+
endpoint: [https://api.example.com](https://api.example.com)
|
|
33
|
+
# Загрузка ключа из переменной окружения API_KEY
|
|
34
|
+
key: !ENV API_KEY
|
|
35
|
+
deployment:
|
|
36
|
+
region: us-east-1
|
|
37
|
+
# Еще один секрет
|
|
38
|
+
ssh_key_pass: !SEC gAAAAABh...[another encrypted data]...
|
|
39
|
+
````
|
|
40
|
+
|
|
41
|
+
## Использование в CLI:
|
|
42
|
+
|
|
43
|
+
1. **Инициализация (создание ключа):**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
keylocker init
|
|
47
|
+
# Будет создан файл storage.key (если не существует)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
2. **Шифрование значения для вставки в YAML:**
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
keylocker encrypt "my_secret_value"
|
|
54
|
+
# Вывод:
|
|
55
|
+
# Add the following line to your YAML file:
|
|
56
|
+
# !SEC gAAAAABh...[encrypted data]...
|
|
57
|
+
|
|
58
|
+
# Скопируйте строку !SEC ... в ваш YAML файл
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
3. **Просмотр расшифрованного YAML:**
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Установите переменную окружения (если используется !ENV)
|
|
65
|
+
export API_KEY="your_actual_api_key"
|
|
66
|
+
|
|
67
|
+
keylocker view config.yaml
|
|
68
|
+
# Выведет YAML с расшифрованными значениями !SEC
|
|
69
|
+
# и подставленными значениями !ENV
|
|
70
|
+
# Пример вывода:
|
|
71
|
+
# database:
|
|
72
|
+
# host: db.example.com
|
|
73
|
+
# username: user
|
|
74
|
+
# password: my_secret_value # Расшифровано!
|
|
75
|
+
# api:
|
|
76
|
+
# endpoint: [https://api.example.com](https://api.example.com)
|
|
77
|
+
# key: your_actual_api_key # Подставлено из env!
|
|
78
|
+
# deployment:
|
|
79
|
+
# region: us-east-1
|
|
80
|
+
# ssh_key_pass: decrypted_pass # Расшифровано!
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
*(Примечание: Команда `edit` может быть добавлена в будущем для интерактивного редактирования)*
|
|
84
|
+
|
|
85
|
+
## Использование в коде Python:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import os
|
|
89
|
+
from keylocker import load_yaml_secrets
|
|
90
|
+
|
|
91
|
+
# Установите переменные окружения, если нужно
|
|
92
|
+
os.environ['API_KEY'] = 'your_actual_api_key_for_python'
|
|
93
|
+
|
|
94
|
+
config_file = 'config.yaml'
|
|
95
|
+
key_file = 'storage.key' # Убедитесь, что ключ существует
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
# Загрузка и автоматическая расшифровка/подстановка
|
|
99
|
+
secrets = load_yaml_secrets(config_file, key_file)
|
|
100
|
+
|
|
101
|
+
if secrets:
|
|
102
|
+
db_password = secrets.get('database', {}).get('password')
|
|
103
|
+
api_key = secrets.get('api', {}).get('key')
|
|
104
|
+
|
|
105
|
+
print(f"Database Password: {db_password}")
|
|
106
|
+
print(f"API Key: {api_key}")
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f"An error occurred: {e}")
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Использование в Bash:
|
|
114
|
+
|
|
115
|
+
Для извлечения конкретных значений из расшифрованного YAML можно использовать утилиты вроде `yq` или стандартные инструменты Unix.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
#!/bin/bash
|
|
119
|
+
|
|
120
|
+
# Убедитесь, что переменная окружения установлена, если она нужна
|
|
121
|
+
export API_KEY="your_bash_api_key"
|
|
122
|
+
|
|
123
|
+
# Получаем расшифрованный YAML
|
|
124
|
+
CONFIG_YAML=$(keylocker view config.yaml)
|
|
125
|
+
|
|
126
|
+
# Проверка, что команда выполнилась успешно
|
|
127
|
+
if [ $? -ne 0 ]; then
|
|
128
|
+
echo "Ошибка при выполнении keylocker view"
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# Пример извлечения с помощью yq (требует установки yq)
|
|
133
|
+
# DB_PASS=$(echo "$CONFIG_YAML" | yq e '.database.password' -)
|
|
134
|
+
# API_KEY_FROM_YAML=$(echo "$CONFIG_YAML" | yq e '.api.key' -)
|
|
135
|
+
|
|
136
|
+
# Пример извлечения с помощью grep/sed (менее надежно)
|
|
137
|
+
DB_PASS=$(echo "$CONFIG_YAML" | grep -A 1 'database:' | grep 'password:' | awk '{print $2}')
|
|
138
|
+
API_KEY_FROM_YAML=$(echo "$CONFIG_YAML" | grep -A 1 'api:' | grep 'key:' | awk '{print $2}')
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
echo "Извлеченный пароль БД: $DB_PASS"
|
|
142
|
+
echo "Извлеченный API ключ: $API_KEY_FROM_YAML"
|
|
143
|
+
|
|
144
|
+
# Дальнейшее использование переменных...
|
|
145
|
+
# poetry publish --username user --password "${DB_PASS}" --build
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
## Source Code:
|
|
150
|
+
* [https://github.com/vpuhoff/keylocker](https://github.com/vpuhoff/keylocker)
|
|
151
|
+
|
|
152
|
+
## Travis CI Deploys:
|
|
153
|
+
* [https://travis-ci.com/vpuhoff/keylocker](https://travis-ci.com/vpuhoff/keylocker) [](https://travis-ci.com/vpuhoff/keylocker)
|
|
154
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Keylocker CLI (YAML Edition)
|
|
2
|
+
|
|
3
|
+
Инструмент командной строки (CLI) и библиотека Python для управления секретами непосредственно в YAML файлах конфигурации. Позволяет шифровать отдельные значения с помощью тега `!SEC` и загружать значения из переменных окружения с помощью тега `!ENV`.
|
|
4
|
+
|
|
5
|
+
## Пример YAML файла (`config.yaml`):
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
# config.yaml
|
|
9
|
+
database:
|
|
10
|
+
host: db.example.com
|
|
11
|
+
username: user
|
|
12
|
+
# Зашифрованный пароль с помощью keylocker encrypt
|
|
13
|
+
password: !SEC gAAAAABh...[rest of encrypted data]...
|
|
14
|
+
api:
|
|
15
|
+
endpoint: [https://api.example.com](https://api.example.com)
|
|
16
|
+
# Загрузка ключа из переменной окружения API_KEY
|
|
17
|
+
key: !ENV API_KEY
|
|
18
|
+
deployment:
|
|
19
|
+
region: us-east-1
|
|
20
|
+
# Еще один секрет
|
|
21
|
+
ssh_key_pass: !SEC gAAAAABh...[another encrypted data]...
|
|
22
|
+
````
|
|
23
|
+
|
|
24
|
+
## Использование в CLI:
|
|
25
|
+
|
|
26
|
+
1. **Инициализация (создание ключа):**
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
keylocker init
|
|
30
|
+
# Будет создан файл storage.key (если не существует)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. **Шифрование значения для вставки в YAML:**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
keylocker encrypt "my_secret_value"
|
|
37
|
+
# Вывод:
|
|
38
|
+
# Add the following line to your YAML file:
|
|
39
|
+
# !SEC gAAAAABh...[encrypted data]...
|
|
40
|
+
|
|
41
|
+
# Скопируйте строку !SEC ... в ваш YAML файл
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
3. **Просмотр расшифрованного YAML:**
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Установите переменную окружения (если используется !ENV)
|
|
48
|
+
export API_KEY="your_actual_api_key"
|
|
49
|
+
|
|
50
|
+
keylocker view config.yaml
|
|
51
|
+
# Выведет YAML с расшифрованными значениями !SEC
|
|
52
|
+
# и подставленными значениями !ENV
|
|
53
|
+
# Пример вывода:
|
|
54
|
+
# database:
|
|
55
|
+
# host: db.example.com
|
|
56
|
+
# username: user
|
|
57
|
+
# password: my_secret_value # Расшифровано!
|
|
58
|
+
# api:
|
|
59
|
+
# endpoint: [https://api.example.com](https://api.example.com)
|
|
60
|
+
# key: your_actual_api_key # Подставлено из env!
|
|
61
|
+
# deployment:
|
|
62
|
+
# region: us-east-1
|
|
63
|
+
# ssh_key_pass: decrypted_pass # Расшифровано!
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
*(Примечание: Команда `edit` может быть добавлена в будущем для интерактивного редактирования)*
|
|
67
|
+
|
|
68
|
+
## Использование в коде Python:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import os
|
|
72
|
+
from keylocker import load_yaml_secrets
|
|
73
|
+
|
|
74
|
+
# Установите переменные окружения, если нужно
|
|
75
|
+
os.environ['API_KEY'] = 'your_actual_api_key_for_python'
|
|
76
|
+
|
|
77
|
+
config_file = 'config.yaml'
|
|
78
|
+
key_file = 'storage.key' # Убедитесь, что ключ существует
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
# Загрузка и автоматическая расшифровка/подстановка
|
|
82
|
+
secrets = load_yaml_secrets(config_file, key_file)
|
|
83
|
+
|
|
84
|
+
if secrets:
|
|
85
|
+
db_password = secrets.get('database', {}).get('password')
|
|
86
|
+
api_key = secrets.get('api', {}).get('key')
|
|
87
|
+
|
|
88
|
+
print(f"Database Password: {db_password}")
|
|
89
|
+
print(f"API Key: {api_key}")
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
print(f"An error occurred: {e}")
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Использование в Bash:
|
|
97
|
+
|
|
98
|
+
Для извлечения конкретных значений из расшифрованного YAML можно использовать утилиты вроде `yq` или стандартные инструменты Unix.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
#!/bin/bash
|
|
102
|
+
|
|
103
|
+
# Убедитесь, что переменная окружения установлена, если она нужна
|
|
104
|
+
export API_KEY="your_bash_api_key"
|
|
105
|
+
|
|
106
|
+
# Получаем расшифрованный YAML
|
|
107
|
+
CONFIG_YAML=$(keylocker view config.yaml)
|
|
108
|
+
|
|
109
|
+
# Проверка, что команда выполнилась успешно
|
|
110
|
+
if [ $? -ne 0 ]; then
|
|
111
|
+
echo "Ошибка при выполнении keylocker view"
|
|
112
|
+
exit 1
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Пример извлечения с помощью yq (требует установки yq)
|
|
116
|
+
# DB_PASS=$(echo "$CONFIG_YAML" | yq e '.database.password' -)
|
|
117
|
+
# API_KEY_FROM_YAML=$(echo "$CONFIG_YAML" | yq e '.api.key' -)
|
|
118
|
+
|
|
119
|
+
# Пример извлечения с помощью grep/sed (менее надежно)
|
|
120
|
+
DB_PASS=$(echo "$CONFIG_YAML" | grep -A 1 'database:' | grep 'password:' | awk '{print $2}')
|
|
121
|
+
API_KEY_FROM_YAML=$(echo "$CONFIG_YAML" | grep -A 1 'api:' | grep 'key:' | awk '{print $2}')
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
echo "Извлеченный пароль БД: $DB_PASS"
|
|
125
|
+
echo "Извлеченный API ключ: $API_KEY_FROM_YAML"
|
|
126
|
+
|
|
127
|
+
# Дальнейшее использование переменных...
|
|
128
|
+
# poetry publish --username user --password "${DB_PASS}" --build
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
## Source Code:
|
|
133
|
+
* [https://github.com/vpuhoff/keylocker](https://github.com/vpuhoff/keylocker)
|
|
134
|
+
|
|
135
|
+
## Travis CI Deploys:
|
|
136
|
+
* [https://travis-ci.com/vpuhoff/keylocker](https://travis-ci.com/vpuhoff/keylocker) [](https://travis-ci.com/vpuhoff/keylocker)
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import base64
|
|
4
|
+
import yaml # Added
|
|
5
|
+
import fire
|
|
6
|
+
from cryptography.fernet import Fernet, InvalidToken
|
|
7
|
+
from cryptography.hazmat.backends import default_backend
|
|
8
|
+
from cryptography.hazmat.primitives import hashes
|
|
9
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
10
|
+
|
|
11
|
+
# --- YAML Tag Handling ---
|
|
12
|
+
|
|
13
|
+
# Custom class to represent secrets before saving
|
|
14
|
+
class SecureString(str):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
# Custom class to represent env vars before saving
|
|
18
|
+
class EnvVariable(str):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
# Loader: Decrypt !SEC tags, Resolve !ENV tags
|
|
22
|
+
def keylocker_loader(key_file='storage.key'):
|
|
23
|
+
try:
|
|
24
|
+
with open(key_file, 'rb') as kf:
|
|
25
|
+
key = kf.read()
|
|
26
|
+
fernet = Fernet(key)
|
|
27
|
+
except FileNotFoundError:
|
|
28
|
+
print(f"ERROR: Key file '{key_file}' not found for decryption!")
|
|
29
|
+
sys.exit(998)
|
|
30
|
+
except (InvalidToken, ValueError, TypeError): # Catch potential Fernet init errors
|
|
31
|
+
print(f"ERROR: Invalid key in '{key_file}'. Cannot initialize decryption.")
|
|
32
|
+
sys.exit(997)
|
|
33
|
+
|
|
34
|
+
class KeylockerLoader(yaml.SafeLoader):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def sec_constructor(loader, node):
|
|
38
|
+
value = loader.construct_scalar(node)
|
|
39
|
+
try:
|
|
40
|
+
decrypted_value = fernet.decrypt(value.encode()).decode()
|
|
41
|
+
return decrypted_value
|
|
42
|
+
except InvalidToken:
|
|
43
|
+
print(f"ERROR: Failed to decrypt value tagged !SEC. Invalid token or key mismatch.")
|
|
44
|
+
# Decide error handling: return placeholder, raise error, etc.
|
|
45
|
+
return f"DECRYPTION_ERROR[InvalidToken]"
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f"ERROR: Unexpected error during !SEC decryption: {e}")
|
|
48
|
+
return f"DECRYPTION_ERROR[{type(e).__name__}]"
|
|
49
|
+
|
|
50
|
+
def env_constructor(loader, node):
|
|
51
|
+
var_name = loader.construct_scalar(node)
|
|
52
|
+
value = os.environ.get(var_name)
|
|
53
|
+
if value is None:
|
|
54
|
+
print(f"WARNING: Environment variable '{var_name}' (tagged !ENV) not found. Returning None.")
|
|
55
|
+
# Or raise an error: raise ValueError(f"!ENV variable '{var_name}' not set")
|
|
56
|
+
return value # Return the raw value from env
|
|
57
|
+
|
|
58
|
+
KeylockerLoader.add_constructor('!SEC', sec_constructor)
|
|
59
|
+
KeylockerLoader.add_constructor('!ENV', env_constructor)
|
|
60
|
+
return KeylockerLoader
|
|
61
|
+
|
|
62
|
+
# Dumper: Encrypt SecureString, Format EnvVariable
|
|
63
|
+
def keylocker_dumper(key_file='storage.key'):
|
|
64
|
+
try:
|
|
65
|
+
with open(key_file, 'rb') as kf:
|
|
66
|
+
key = kf.read()
|
|
67
|
+
fernet = Fernet(key)
|
|
68
|
+
except FileNotFoundError:
|
|
69
|
+
print(f"ERROR: Key file '{key_file}' not found for encryption!")
|
|
70
|
+
sys.exit(998)
|
|
71
|
+
except (InvalidToken, ValueError, TypeError):
|
|
72
|
+
print(f"ERROR: Invalid key in '{key_file}'. Cannot initialize encryption.")
|
|
73
|
+
sys.exit(997)
|
|
74
|
+
|
|
75
|
+
class KeylockerDumper(yaml.SafeDumper):
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
def sec_representer(dumper, data):
|
|
79
|
+
encrypted_value = fernet.encrypt(str(data).encode()).decode()
|
|
80
|
+
return dumper.represent_scalar('!SEC', encrypted_value)
|
|
81
|
+
|
|
82
|
+
def env_representer(dumper, data):
|
|
83
|
+
return dumper.represent_scalar('!ENV', str(data))
|
|
84
|
+
|
|
85
|
+
KeylockerDumper.add_representer(SecureString, sec_representer)
|
|
86
|
+
KeylockerDumper.add_representer(EnvVariable, env_representer)
|
|
87
|
+
return KeylockerDumper
|
|
88
|
+
|
|
89
|
+
# --- Core Functions ---
|
|
90
|
+
|
|
91
|
+
def load_yaml_secrets(file_path, key_file='storage.key'):
|
|
92
|
+
"""Loads YAML, decrypting !SEC and resolving !ENV."""
|
|
93
|
+
try:
|
|
94
|
+
with open(file_path, 'r') as f:
|
|
95
|
+
# Pass the custom loader initialized with the key file
|
|
96
|
+
data = yaml.load(f, Loader=keylocker_loader(key_file))
|
|
97
|
+
return data
|
|
98
|
+
except FileNotFoundError:
|
|
99
|
+
print(f"ERROR: Input YAML file '{file_path}' not found.")
|
|
100
|
+
return None
|
|
101
|
+
except yaml.YAMLError as e:
|
|
102
|
+
print(f"ERROR: Failed to parse YAML file '{file_path}': {e}")
|
|
103
|
+
return None
|
|
104
|
+
except Exception as e:
|
|
105
|
+
print(f"ERROR: An unexpected error occurred during YAML load: {e}")
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
def encrypt_string_value(value_to_encrypt, key_file='storage.key'):
|
|
109
|
+
"""Encrypts a string and returns the !SEC tagged value."""
|
|
110
|
+
try:
|
|
111
|
+
with open(key_file, 'rb') as kf:
|
|
112
|
+
key = kf.read()
|
|
113
|
+
fernet = Fernet(key)
|
|
114
|
+
encrypted = fernet.encrypt(str(value_to_encrypt).encode()).decode()
|
|
115
|
+
# Return the full tag representation ready for YAML
|
|
116
|
+
return f"!SEC {encrypted}"
|
|
117
|
+
except FileNotFoundError:
|
|
118
|
+
print(f"ERROR: Key file '{key_file}' not found for encryption!")
|
|
119
|
+
return None
|
|
120
|
+
except (InvalidToken, ValueError, TypeError):
|
|
121
|
+
print(f"ERROR: Invalid key in '{key_file}'. Cannot initialize encryption.")
|
|
122
|
+
return None
|
|
123
|
+
except Exception as e:
|
|
124
|
+
print(f"ERROR: An unexpected error occurred during encryption: {e}")
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
# Note: Saving YAML with encrypted values requires representing the data
|
|
128
|
+
# correctly *before* calling yaml.dump (e.g., using SecureString).
|
|
129
|
+
# A full 'edit' command is more complex and not fully implemented here.
|
|
130
|
+
|
|
131
|
+
# --- CLI Manager ---
|
|
132
|
+
|
|
133
|
+
class Manager(object):
|
|
134
|
+
def __init__(self, key_file='storage.key'):
|
|
135
|
+
self.key_file = key_file
|
|
136
|
+
# Check if key exists on init for relevant commands
|
|
137
|
+
# self._check_key_exists() # Optional: Check early
|
|
138
|
+
|
|
139
|
+
def _check_key_exists(self):
|
|
140
|
+
if not os.path.exists(self.key_file):
|
|
141
|
+
print(f"ERROR: Key file '{self.key_file}' not found.")
|
|
142
|
+
print("Please run 'keylocker init' first.")
|
|
143
|
+
sys.exit(998)
|
|
144
|
+
|
|
145
|
+
def init(self, force=False):
|
|
146
|
+
"""Initializes storage and generates the master key file."""
|
|
147
|
+
if os.path.exists(self.key_file) and not force:
|
|
148
|
+
print(f"Key file '{self.key_file}' already exists.")
|
|
149
|
+
print("Use --force to overwrite.")
|
|
150
|
+
return f"Key file '{self.key_file}' already exists."
|
|
151
|
+
|
|
152
|
+
print(f"Generating new key file: '{self.key_file}'...")
|
|
153
|
+
# Using original key generation logic for consistency
|
|
154
|
+
password = Fernet.generate_key() # Use a generated key as the 'password'
|
|
155
|
+
salt = os.urandom(16)
|
|
156
|
+
kdf = PBKDF2HMAC(
|
|
157
|
+
algorithm=hashes.SHA256(),
|
|
158
|
+
length=32,
|
|
159
|
+
salt=salt,
|
|
160
|
+
iterations=100000, # Keep iterations reasonable
|
|
161
|
+
backend=default_backend()
|
|
162
|
+
)
|
|
163
|
+
# Derive a key and encode it for Fernet
|
|
164
|
+
key = base64.urlsafe_b64encode(kdf.derive(password))
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
with open(self.key_file, 'wb') as f:
|
|
168
|
+
f.write(key)
|
|
169
|
+
print(f"Successfully created key file: '{self.key_file}'")
|
|
170
|
+
return f"Key file '{self.key_file}' created."
|
|
171
|
+
except IOError as e:
|
|
172
|
+
print(f"ERROR: Could not write key file '{self.key_file}': {e}")
|
|
173
|
+
sys.exit(996)
|
|
174
|
+
|
|
175
|
+
def encrypt(self, value):
|
|
176
|
+
"""Encrypts a string value using the key file and prints the !SEC tag."""
|
|
177
|
+
self._check_key_exists()
|
|
178
|
+
encrypted_tag = encrypt_string_value(value, self.key_file)
|
|
179
|
+
if encrypted_tag:
|
|
180
|
+
print("Add the following line to your YAML file:")
|
|
181
|
+
print(encrypted_tag)
|
|
182
|
+
else:
|
|
183
|
+
# Error message already printed by encrypt_string_value
|
|
184
|
+
sys.exit(1) # Exit with error code
|
|
185
|
+
|
|
186
|
+
def view(self, yaml_file):
|
|
187
|
+
"""Loads, decrypts (!SEC), resolves (!ENV) a YAML file and prints it."""
|
|
188
|
+
self._check_key_exists() # Needed for decryption
|
|
189
|
+
data = load_yaml_secrets(yaml_file, self.key_file)
|
|
190
|
+
if data is not None:
|
|
191
|
+
# Dump back to YAML string for viewing (without custom tags)
|
|
192
|
+
print(yaml.dump(data, Dumper=yaml.SafeDumper, default_flow_style=False, sort_keys=False))
|
|
193
|
+
else:
|
|
194
|
+
sys.exit(1) # Exit with error code if loading failed
|
|
195
|
+
|
|
196
|
+
# --- Removed old methods ---
|
|
197
|
+
# def write(self,key, value): ...
|
|
198
|
+
# def remove(self,key): ...
|
|
199
|
+
# def read(self,key): ...
|
|
200
|
+
# def list(self): ...
|
|
201
|
+
|
|
202
|
+
def main():
|
|
203
|
+
# Pass Manager class directly to Fire
|
|
204
|
+
fire.Fire(Manager, name='keylocker')
|
|
205
|
+
|
|
206
|
+
if __name__ == "__main__":
|
|
207
|
+
main()
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
from keylocker import *
|
|
2
|
-
main()
|
|
1
|
+
from keylocker import *
|
|
2
|
+
main()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "keylocker"
|
|
3
|
+
version = "0.4.2" # Убедитесь, что версия актуальна
|
|
4
|
+
description = "Инструмент CLI и библиотека для управления секретами в YAML файлах с шифрованием." # Описание обновлено
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "vpuhoff", email = "vpuhoff@live.ru" }
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.10" # Явно указываем поддержку Python
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Development Status :: 4 - Beta", # Примерный статус
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
18
|
+
"Topic :: Utilities",
|
|
19
|
+
]
|
|
20
|
+
dependencies = []
|
|
21
|
+
|
|
22
|
+
# Стандартная секция для определения скриптов/точек входа
|
|
23
|
+
[project.scripts]
|
|
24
|
+
keylocker = "keylocker:main"
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["poetry-core>=1.0.0"]
|
|
28
|
+
build-backend = "poetry.core.masonry.api"
|
keylocker-0.3.5/PKG-INFO
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: keylocker
|
|
3
|
-
Version: 0.3.5
|
|
4
|
-
Summary: Library with the CLI to save the encrypted secrets in the configuration file, but a transparent read and write the new settings in the app.
|
|
5
|
-
License: MIT
|
|
6
|
-
Author: vpuhoff
|
|
7
|
-
Author-email: vpuhoff@live.ru
|
|
8
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
-
Classifier: Programming Language :: Python :: 2
|
|
10
|
-
Classifier: Programming Language :: Python :: 2.7
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.4
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.5
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
-
Requires-Dist: cryptography (>=2.4,<3.0)
|
|
17
|
-
Requires-Dist: fire (>=0.1.3,<0.2.0)
|
|
18
|
-
Requires-Dist: pickleDB (>=0.9.2,<0.10.0)
|
|
19
|
-
Description-Content-Type: text/markdown
|
|
20
|
-
|
|
21
|
-
# Keylocker CLI
|
|
22
|
-
Library with the CLI to save the encrypted secrets in the configuration file, but a transparent read and write the new settings in the app.
|
|
23
|
-
|
|
24
|
-
## Simple usage in CLI:
|
|
25
|
-
```
|
|
26
|
-
keylocker init
|
|
27
|
-
keylocker list
|
|
28
|
-
keylocker read <keyname>
|
|
29
|
-
keylocker remove <keyname>
|
|
30
|
-
keylocker write <keyname> <value>
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Simple usage in code:
|
|
34
|
-
```
|
|
35
|
-
from keylocker import Storage
|
|
36
|
-
secrets = Storage()
|
|
37
|
-
print(secrets['test'])
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Simple usage in bash:
|
|
41
|
-
```
|
|
42
|
-
PYPIPASS="$(keylocker read pypi_password)"
|
|
43
|
-
PYPIUSER="$(keylocker read pypi_user)"
|
|
44
|
-
poetry publish --username "${PYPIUSER}" --password "${PYPIPASS}" --build
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Source Code:
|
|
48
|
-
* [https://github.com/vpuhoff/keylocker](https://github.com/vpuhoff/keylocker)
|
|
49
|
-
|
|
50
|
-
## Travis CI Deploys:
|
|
51
|
-
* [https://travis-ci.com/vpuhoff/keylocker](https://travis-ci.com/vpuhoff/keylocker) [](https://travis-ci.com/vpuhoff/keylocker)
|
|
52
|
-
|
keylocker-0.3.5/README.md
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# Keylocker CLI
|
|
2
|
-
Library with the CLI to save the encrypted secrets in the configuration file, but a transparent read and write the new settings in the app.
|
|
3
|
-
|
|
4
|
-
## Simple usage in CLI:
|
|
5
|
-
```
|
|
6
|
-
keylocker init
|
|
7
|
-
keylocker list
|
|
8
|
-
keylocker read <keyname>
|
|
9
|
-
keylocker remove <keyname>
|
|
10
|
-
keylocker write <keyname> <value>
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## Simple usage in code:
|
|
14
|
-
```
|
|
15
|
-
from keylocker import Storage
|
|
16
|
-
secrets = Storage()
|
|
17
|
-
print(secrets['test'])
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Simple usage in bash:
|
|
21
|
-
```
|
|
22
|
-
PYPIPASS="$(keylocker read pypi_password)"
|
|
23
|
-
PYPIUSER="$(keylocker read pypi_user)"
|
|
24
|
-
poetry publish --username "${PYPIUSER}" --password "${PYPIPASS}" --build
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Source Code:
|
|
28
|
-
* [https://github.com/vpuhoff/keylocker](https://github.com/vpuhoff/keylocker)
|
|
29
|
-
|
|
30
|
-
## Travis CI Deploys:
|
|
31
|
-
* [https://travis-ci.com/vpuhoff/keylocker](https://travis-ci.com/vpuhoff/keylocker) [](https://travis-ci.com/vpuhoff/keylocker)
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import pickledb
|
|
2
|
-
import fire
|
|
3
|
-
from cryptography.fernet import Fernet, InvalidToken
|
|
4
|
-
|
|
5
|
-
class Storage(object):
|
|
6
|
-
def __init__(self, filename='secrets.json', key_file='storage.key', key=None):
|
|
7
|
-
self.db = pickledb.load(filename,True)
|
|
8
|
-
try:
|
|
9
|
-
if not key:
|
|
10
|
-
with open(key_file,'rb') as keyfile:
|
|
11
|
-
key = keyfile.read()
|
|
12
|
-
self.fernet = Fernet(key)
|
|
13
|
-
except FileNotFoundError as e:
|
|
14
|
-
print('ERROR: Key file not found!')
|
|
15
|
-
|
|
16
|
-
def __setitem__(self, key, value):
|
|
17
|
-
self.db[key] = self.fernet.encrypt(str(value).encode()).decode()
|
|
18
|
-
|
|
19
|
-
def __getitem__(self, key):
|
|
20
|
-
try:
|
|
21
|
-
raw= self.db[key]
|
|
22
|
-
if raw:
|
|
23
|
-
raw = str(raw)
|
|
24
|
-
return self.fernet.decrypt(raw.encode()).decode()
|
|
25
|
-
else:
|
|
26
|
-
print('ERROR: Key not found')
|
|
27
|
-
exit(999)
|
|
28
|
-
except InvalidToken as e:
|
|
29
|
-
print('ERROR: Invalid key file!')
|
|
30
|
-
exit(999)
|
|
31
|
-
except FileNotFoundError as e:
|
|
32
|
-
print('ERROR: Key file not found!')
|
|
33
|
-
exit(999)
|
|
34
|
-
|
|
35
|
-
def keys(self):
|
|
36
|
-
return self.db.getall()
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
from . import Storage
|
|
40
|
-
|
|
41
|
-
import fire
|
|
42
|
-
class Manager(object):
|
|
43
|
-
def __init__(self):
|
|
44
|
-
self.storage = Storage()
|
|
45
|
-
#return super().__init__()
|
|
46
|
-
|
|
47
|
-
def init(self):
|
|
48
|
-
import base64
|
|
49
|
-
import os
|
|
50
|
-
from cryptography.fernet import Fernet
|
|
51
|
-
from cryptography.hazmat.backends import default_backend
|
|
52
|
-
from cryptography.hazmat.primitives import hashes
|
|
53
|
-
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
54
|
-
key = Fernet.generate_key()
|
|
55
|
-
longpass = key
|
|
56
|
-
salt = os.urandom(16)
|
|
57
|
-
kdf = PBKDF2HMAC(
|
|
58
|
-
algorithm=hashes.SHA256(),
|
|
59
|
-
length=32,
|
|
60
|
-
salt=salt,
|
|
61
|
-
iterations=100000,
|
|
62
|
-
backend=default_backend()
|
|
63
|
-
)
|
|
64
|
-
key = base64.urlsafe_b64encode(kdf.derive(longpass))
|
|
65
|
-
with open('storage.key','wb') as f:
|
|
66
|
-
f.write(key)
|
|
67
|
-
return 'storage.key'
|
|
68
|
-
# key = Fernet.generate_key()
|
|
69
|
-
# return (key.decode())
|
|
70
|
-
def write(self,key, value):
|
|
71
|
-
self.storage[key]=value
|
|
72
|
-
return 'OK'
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def remove(self,key):
|
|
76
|
-
if key =='*':
|
|
77
|
-
for key,value in self.storage.db.dgetall():
|
|
78
|
-
self.storage.db.drem(key)
|
|
79
|
-
else:
|
|
80
|
-
try:
|
|
81
|
-
self.storage.db.drem(key)
|
|
82
|
-
return 'OK'
|
|
83
|
-
except KeyError as e:
|
|
84
|
-
print('ERROR: Key not found')
|
|
85
|
-
exit(888)
|
|
86
|
-
|
|
87
|
-
def read(self,key):
|
|
88
|
-
return self.storage[key]
|
|
89
|
-
|
|
90
|
-
def list(self):
|
|
91
|
-
for item in list(self.storage.keys()):
|
|
92
|
-
print(item)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def main():
|
|
96
|
-
fire.Fire(Manager, name='keylocker')
|
|
97
|
-
|
|
98
|
-
if __name__ == "__main__":
|
|
99
|
-
main()
|
keylocker-0.3.5/pyproject.toml
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "keylocker"
|
|
3
|
-
version = "0.3.5"
|
|
4
|
-
description = "Library with the CLI to save the encrypted secrets in the configuration file, but a transparent read and write the new settings in the app."
|
|
5
|
-
readme = "README.md"
|
|
6
|
-
license = "MIT"
|
|
7
|
-
authors = ["vpuhoff <vpuhoff@live.ru>"]
|
|
8
|
-
|
|
9
|
-
[tool.poetry.dependencies]
|
|
10
|
-
pickleDB = "^0.9.2"
|
|
11
|
-
cryptography = "^2.4"
|
|
12
|
-
fire = "^0.1.3"
|
|
13
|
-
|
|
14
|
-
[tool.poetry.dev-dependencies]
|
|
15
|
-
pickleDB = "^0.9.2"
|
|
16
|
-
cryptography = "^2.7"
|
|
17
|
-
fire = "^0.1.3"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
[build-system]
|
|
21
|
-
requires = ["poetry>=0.12"]
|
|
22
|
-
build-backend = "poetry.masonry.api"
|
|
23
|
-
|
|
24
|
-
[tool.poetry.scripts]
|
|
25
|
-
keylocker = 'keylocker:main'
|
keylocker-0.3.5/setup.py
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
from distutils.core import setup
|
|
3
|
-
|
|
4
|
-
packages = \
|
|
5
|
-
['keylocker']
|
|
6
|
-
|
|
7
|
-
package_data = \
|
|
8
|
-
{'': ['*']}
|
|
9
|
-
|
|
10
|
-
install_requires = \
|
|
11
|
-
['cryptography>=2.4,<3.0', 'fire>=0.1.3,<0.2.0', 'pickleDB>=0.9.2,<0.10.0']
|
|
12
|
-
|
|
13
|
-
entry_points = \
|
|
14
|
-
{'console_scripts': ['keylocker = keylocker:main']}
|
|
15
|
-
|
|
16
|
-
setup_kwargs = {
|
|
17
|
-
'name': 'keylocker',
|
|
18
|
-
'version': '0.3.5',
|
|
19
|
-
'description': 'Library with the CLI to save the encrypted secrets in the configuration file, but a transparent read and write the new settings in the app.',
|
|
20
|
-
'long_description': '# Keylocker CLI\nLibrary with the CLI to save the encrypted secrets in the configuration file, but a transparent read and write the new settings in the app.\n\n## Simple usage in CLI:\n```\nkeylocker init\nkeylocker list\nkeylocker read <keyname>\nkeylocker remove <keyname>\nkeylocker write <keyname> <value>\n```\n\n## Simple usage in code:\n```\nfrom keylocker import Storage\nsecrets = Storage()\nprint(secrets[\'test\'])\n```\n\n## Simple usage in bash:\n```\nPYPIPASS="$(keylocker read pypi_password)"\nPYPIUSER="$(keylocker read pypi_user)"\npoetry publish --username "${PYPIUSER}" --password "${PYPIPASS}" --build\n```\n\n## Source Code:\n* [https://github.com/vpuhoff/keylocker](https://github.com/vpuhoff/keylocker)\n\n## Travis CI Deploys:\n* [https://travis-ci.com/vpuhoff/keylocker](https://travis-ci.com/vpuhoff/keylocker) [](https://travis-ci.com/vpuhoff/keylocker)\n',
|
|
21
|
-
'author': 'vpuhoff',
|
|
22
|
-
'author_email': 'vpuhoff@live.ru',
|
|
23
|
-
'url': None,
|
|
24
|
-
'packages': packages,
|
|
25
|
-
'package_data': package_data,
|
|
26
|
-
'install_requires': install_requires,
|
|
27
|
-
'entry_points': entry_points,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
setup(**setup_kwargs)
|