keysloth 0.1.1
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.
- checksums.yaml +7 -0
- data/.keyslothrc.example +14 -0
- data/.rspec +4 -0
- data/.rubocop.yml +98 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +45 -0
- data/Makefile +193 -0
- data/README.md +448 -0
- data/Rakefile +99 -0
- data/bin/keysloth +16 -0
- data/keysloth.gemspec +51 -0
- data/lib/keysloth/cli.rb +468 -0
- data/lib/keysloth/config.rb +129 -0
- data/lib/keysloth/crypto.rb +304 -0
- data/lib/keysloth/errors.rb +41 -0
- data/lib/keysloth/file_manager.rb +450 -0
- data/lib/keysloth/git_manager.rb +394 -0
- data/lib/keysloth/logger.rb +172 -0
- data/lib/keysloth/version.rb +6 -0
- data/lib/keysloth.rb +275 -0
- data/task/cr.md +59 -0
- data/task/plan.md +169 -0
- data/task/ragged_removing.md +103 -0
- data/task/task.md +43 -0
- data/task/test_plan.md +266 -0
- metadata +174 -0
data/lib/keysloth.rb
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'keysloth/version'
|
|
4
|
+
require_relative 'keysloth/crypto'
|
|
5
|
+
require_relative 'keysloth/git_manager'
|
|
6
|
+
require_relative 'keysloth/file_manager'
|
|
7
|
+
require_relative 'keysloth/config'
|
|
8
|
+
require_relative 'keysloth/logger'
|
|
9
|
+
require_relative 'keysloth/errors'
|
|
10
|
+
|
|
11
|
+
# Основной модуль KeySloth - Ruby gem для управления зашифрованными секретами
|
|
12
|
+
#
|
|
13
|
+
# KeySloth предоставляет инструменты для безопасного хранения и управления
|
|
14
|
+
# секретами (сертификаты, ключи, конфигурационные файлы) в зашифрованном виде
|
|
15
|
+
# в Git репозиториях.
|
|
16
|
+
#
|
|
17
|
+
# @example Базовое использование
|
|
18
|
+
# # Получение секретов из репозитория
|
|
19
|
+
# KeySloth.pull(repo_url: 'git@github.com:company/secrets.git',
|
|
20
|
+
# branch: 'main',
|
|
21
|
+
# password: 'secret_password',
|
|
22
|
+
# local_path: './secrets')
|
|
23
|
+
#
|
|
24
|
+
# # Отправка секретов в репозиторий
|
|
25
|
+
# KeySloth.push(repo_url: 'git@github.com:company/secrets.git',
|
|
26
|
+
# branch: 'main',
|
|
27
|
+
# password: 'secret_password',
|
|
28
|
+
# local_path: './secrets')
|
|
29
|
+
#
|
|
30
|
+
# @example Использование с конфигурационным файлом
|
|
31
|
+
# # Создайте файл .keyslothrc:
|
|
32
|
+
# # repo_url: "git@github.com:company/secrets.git"
|
|
33
|
+
# # branch: "main"
|
|
34
|
+
# # local_path: "./secrets"
|
|
35
|
+
#
|
|
36
|
+
# # Теперь достаточно указать только пароль:
|
|
37
|
+
# KeySloth.pull(password: 'secret_password', config_file: '.keyslothrc')
|
|
38
|
+
#
|
|
39
|
+
# @example Обработка ошибок
|
|
40
|
+
# begin
|
|
41
|
+
# KeySloth.pull(repo_url: repo_url, password: password, local_path: './secrets')
|
|
42
|
+
# rescue KeySloth::CryptoError => e
|
|
43
|
+
# puts "Ошибка расшифровки: #{e.message}"
|
|
44
|
+
# rescue KeySloth::RepositoryError => e
|
|
45
|
+
# puts "Ошибка репозитория: #{e.message}"
|
|
46
|
+
# rescue KeySloth::FileSystemError => e
|
|
47
|
+
# puts "Ошибка файловой системы: #{e.message}"
|
|
48
|
+
# end
|
|
49
|
+
#
|
|
50
|
+
# @example Использование в CI/CD с переменными окружения
|
|
51
|
+
# # Установите переменные окружения:
|
|
52
|
+
# # export SSH_PRIVATE_KEY="$(cat ~/.ssh/id_rsa)"
|
|
53
|
+
# # export SECRET_PASSWORD="your_password"
|
|
54
|
+
# # export GIT_AUTHOR_NAME="CI Bot"
|
|
55
|
+
# # export GIT_AUTHOR_EMAIL="ci@company.com"
|
|
56
|
+
#
|
|
57
|
+
# KeySloth.pull(
|
|
58
|
+
# repo_url: 'git@github.com:company/secrets.git',
|
|
59
|
+
# password: ENV['SECRET_PASSWORD'],
|
|
60
|
+
# local_path: './secrets'
|
|
61
|
+
# )
|
|
62
|
+
#
|
|
63
|
+
# @author KeySloth Team
|
|
64
|
+
# @since 0.1.0
|
|
65
|
+
module KeySloth
|
|
66
|
+
class << self
|
|
67
|
+
# Получает и расшифровывает секреты из удаленного Git репозитория
|
|
68
|
+
#
|
|
69
|
+
# @param repo_url [String] URL Git репозитория (SSH)
|
|
70
|
+
# @param branch [String] Ветка для получения секретов (по умолчанию 'main')
|
|
71
|
+
# @param password [String] Пароль для расшифровки секретов
|
|
72
|
+
# @param local_path [String] Локальный путь для сохранения расшифрованных секретов
|
|
73
|
+
# @param config_file [String, nil] Путь к файлу конфигурации (опционально)
|
|
74
|
+
# @return [Boolean] true при успешном выполнении
|
|
75
|
+
# @raise [KeySloth::RepositoryError] при ошибках работы с репозиторием
|
|
76
|
+
# @raise [KeySloth::CryptoError] при ошибках расшифровки
|
|
77
|
+
# @raise [KeySloth::FileSystemError] при ошибках файловой системы
|
|
78
|
+
def pull(repo_url:, password:, local_path:, branch: 'main', config_file: nil)
|
|
79
|
+
start_time = Time.now
|
|
80
|
+
logger = Logger.new
|
|
81
|
+
config = Config.load(config_file)
|
|
82
|
+
|
|
83
|
+
# Аудит логирование начала операции
|
|
84
|
+
logger.audit('pull_start', {
|
|
85
|
+
repo_url: repo_url,
|
|
86
|
+
branch: branch,
|
|
87
|
+
local_path: local_path,
|
|
88
|
+
config_file: config_file
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
# Объединяем параметры с конфигурацией (параметры имеют приоритет, nil не перетирает)
|
|
92
|
+
merged_config = config.merge({
|
|
93
|
+
repo_url: repo_url,
|
|
94
|
+
branch: branch,
|
|
95
|
+
local_path: local_path
|
|
96
|
+
}.compact)
|
|
97
|
+
|
|
98
|
+
logger.info("Начинаем получение секретов из репозитория: #{repo_url}")
|
|
99
|
+
|
|
100
|
+
git_manager = GitManager.new(merged_config[:repo_url], logger)
|
|
101
|
+
file_manager = FileManager.new(logger)
|
|
102
|
+
crypto = Crypto.new(password, logger)
|
|
103
|
+
|
|
104
|
+
# Создаем backup перед операцией
|
|
105
|
+
if File.exist?(merged_config[:local_path])
|
|
106
|
+
file_manager.create_backup(merged_config[:local_path])
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Клонируем/обновляем репозиторий и получаем зашифрованные файлы
|
|
110
|
+
encrypted_files = git_manager.pull_encrypted_files(merged_config[:branch])
|
|
111
|
+
|
|
112
|
+
# Создаем локальную директорию если не существует
|
|
113
|
+
file_manager.ensure_directory(merged_config[:local_path])
|
|
114
|
+
|
|
115
|
+
# Расшифровываем и сохраняем файлы с проверкой целостности
|
|
116
|
+
integrity_failures = []
|
|
117
|
+
|
|
118
|
+
encrypted_files.each do |encrypted_file|
|
|
119
|
+
original_filename = encrypted_file[:name].gsub(/\.enc$/, '')
|
|
120
|
+
|
|
121
|
+
# Проверяем целостность зашифрованного файла
|
|
122
|
+
integrity_check = crypto.verify_integrity_detailed(encrypted_file[:content])
|
|
123
|
+
|
|
124
|
+
unless integrity_check[:valid]
|
|
125
|
+
error_msg = "Ошибка целостности для #{original_filename}: #{integrity_check[:error] || 'структура данных повреждена'}"
|
|
126
|
+
logger.error(error_msg)
|
|
127
|
+
integrity_failures << { file: original_filename, error: integrity_check[:error] }
|
|
128
|
+
next
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
logger.debug("Проверка целостности пройдена для: #{original_filename}")
|
|
132
|
+
|
|
133
|
+
# Расшифровываем файл
|
|
134
|
+
decrypted_content = crypto.decrypt_file(encrypted_file[:content])
|
|
135
|
+
local_file_path = File.join(merged_config[:local_path], original_filename)
|
|
136
|
+
|
|
137
|
+
# Проверяем целостность расшифрованного файла
|
|
138
|
+
if decrypted_content.nil? || decrypted_content.empty?
|
|
139
|
+
logger.warn("Расшифрованный файл пустой: #{original_filename}")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
file_manager.write_file(local_file_path, decrypted_content)
|
|
143
|
+
logger.info("Расшифрован файл: #{original_filename} (размер: #{decrypted_content.length} байт)")
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Проверяем наличие ошибок целостности
|
|
147
|
+
unless integrity_failures.empty?
|
|
148
|
+
failure_details = integrity_failures.map { |f| "#{f[:file]}: #{f[:error]}" }.join('; ')
|
|
149
|
+
raise CryptoError, "Обнаружены ошибки целостности файлов: #{failure_details}"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
duration = Time.now - start_time
|
|
153
|
+
logger.info("Успешно получено и расшифровано #{encrypted_files.size} файлов")
|
|
154
|
+
|
|
155
|
+
# Аудит логирование успешного завершения
|
|
156
|
+
logger.security_log('pull', :success, duration: duration, details: {
|
|
157
|
+
files_count: encrypted_files.size,
|
|
158
|
+
repo_url: repo_url,
|
|
159
|
+
branch: merged_config[:branch]
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
true
|
|
163
|
+
rescue StandardError => e
|
|
164
|
+
duration = Time.now - start_time
|
|
165
|
+
logger.security_log('pull', :failure, duration: duration, details: {
|
|
166
|
+
error: e.class.name,
|
|
167
|
+
repo_url: repo_url,
|
|
168
|
+
branch: branch
|
|
169
|
+
})
|
|
170
|
+
raise
|
|
171
|
+
ensure
|
|
172
|
+
git_manager&.cleanup
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Шифрует и отправляет секреты в удаленный Git репозиторий
|
|
176
|
+
#
|
|
177
|
+
# @param repo_url [String] URL Git репозитория (SSH)
|
|
178
|
+
# @param branch [String] Ветка для отправки секретов (по умолчанию 'main')
|
|
179
|
+
# @param password [String] Пароль для шифрования секретов
|
|
180
|
+
# @param local_path [String] Локальный путь с секретами для шифрования
|
|
181
|
+
# @param config_file [String, nil] Путь к файлу конфигурации (опционально)
|
|
182
|
+
# @param commit_message [String] Сообщение коммита (опционально)
|
|
183
|
+
# @return [Boolean] true при успешном выполнении
|
|
184
|
+
# @raise [KeySloth::RepositoryError] при ошибках работы с репозиторием
|
|
185
|
+
# @raise [KeySloth::CryptoError] при ошибках шифрования
|
|
186
|
+
# @raise [KeySloth::FileSystemError] при ошибках файловой системы
|
|
187
|
+
def push(repo_url:, password:, local_path:, branch: 'main',
|
|
188
|
+
config_file: nil, commit_message: nil)
|
|
189
|
+
start_time = Time.now
|
|
190
|
+
logger = Logger.new
|
|
191
|
+
config = Config.load(config_file)
|
|
192
|
+
|
|
193
|
+
# Аудит логирование начала операции
|
|
194
|
+
logger.audit('push_start', {
|
|
195
|
+
repo_url: repo_url,
|
|
196
|
+
branch: branch,
|
|
197
|
+
local_path: local_path,
|
|
198
|
+
config_file: config_file,
|
|
199
|
+
commit_message: commit_message
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
# Объединяем параметры с конфигурацией (параметры имеют приоритет, nil не перетирает)
|
|
203
|
+
merged_config = config.merge({
|
|
204
|
+
repo_url: repo_url,
|
|
205
|
+
branch: branch,
|
|
206
|
+
local_path: local_path
|
|
207
|
+
}.compact)
|
|
208
|
+
|
|
209
|
+
logger.info("Начинаем отправку секретов в репозиторий: #{repo_url}")
|
|
210
|
+
|
|
211
|
+
git_manager = GitManager.new(merged_config[:repo_url], logger)
|
|
212
|
+
file_manager = FileManager.new(logger)
|
|
213
|
+
crypto = Crypto.new(password, logger)
|
|
214
|
+
|
|
215
|
+
# Проверяем существование локальной директории
|
|
216
|
+
unless file_manager.directory_exists?(merged_config[:local_path])
|
|
217
|
+
raise FileSystemError, "Локальная директория не существует: #{merged_config[:local_path]}"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Получаем список файлов для шифрования
|
|
221
|
+
local_files = file_manager.collect_secret_files(merged_config[:local_path])
|
|
222
|
+
|
|
223
|
+
if local_files.empty?
|
|
224
|
+
logger.warn('Не найдено файлов секретов для отправки')
|
|
225
|
+
return true
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Клонируем репозиторий и переключаемся на нужную ветку
|
|
229
|
+
git_manager.prepare_repository(merged_config[:branch])
|
|
230
|
+
|
|
231
|
+
# Шифруем и подготавливаем файлы
|
|
232
|
+
encrypted_files = local_files.map do |file_path|
|
|
233
|
+
content = file_manager.read_file(file_path)
|
|
234
|
+
encrypted_content = crypto.encrypt_file(content)
|
|
235
|
+
relative_path = file_manager.get_relative_path(file_path, merged_config[:local_path])
|
|
236
|
+
encrypted_filename = "#{relative_path}.enc"
|
|
237
|
+
|
|
238
|
+
{
|
|
239
|
+
path: encrypted_filename,
|
|
240
|
+
content: encrypted_content
|
|
241
|
+
}
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Записываем зашифрованные файлы в репозиторий
|
|
245
|
+
git_manager.write_encrypted_files(encrypted_files)
|
|
246
|
+
|
|
247
|
+
# Создаем коммит и отправляем
|
|
248
|
+
commit_msg = commit_message || "Update secrets: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
249
|
+
git_manager.commit_and_push(commit_msg, merged_config[:branch])
|
|
250
|
+
|
|
251
|
+
duration = Time.now - start_time
|
|
252
|
+
logger.info("Успешно зашифровано и отправлено #{encrypted_files.size} файлов")
|
|
253
|
+
|
|
254
|
+
# Аудит логирование успешного завершения
|
|
255
|
+
logger.security_log('push', :success, duration: duration, details: {
|
|
256
|
+
files_count: encrypted_files.size,
|
|
257
|
+
repo_url: repo_url,
|
|
258
|
+
branch: merged_config[:branch],
|
|
259
|
+
commit_message: commit_msg
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
true
|
|
263
|
+
rescue StandardError => e
|
|
264
|
+
duration = Time.now - start_time
|
|
265
|
+
logger.security_log('push', :failure, duration: duration, details: {
|
|
266
|
+
error: e.class.name,
|
|
267
|
+
repo_url: repo_url,
|
|
268
|
+
branch: branch
|
|
269
|
+
})
|
|
270
|
+
raise
|
|
271
|
+
ensure
|
|
272
|
+
git_manager&.cleanup
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
end
|
data/task/cr.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Change Request: Поддержка SSH ключей id_ed25519 и улучшение UX
|
|
2
|
+
|
|
3
|
+
## Контекст проблемы
|
|
4
|
+
При запуске команды (например, `keysloth pull`) возникла ошибка:
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
Ошибка выполнения операции: SSH ключ не найден: /Users/<user>/.ssh/id_rsa
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Причина: в `KeySloth::GitManager#create_ssh_credentials` по умолчанию ожидаются ключи формата RSA (`~/.ssh/id_rsa` и `~/.ssh/id_rsa.pub`). На машине пользователя используются ключи Ed25519 (`id_ed25519`, `id_ed25519.pub`).
|
|
11
|
+
|
|
12
|
+
## Временные решения (workarounds)
|
|
13
|
+
1. Использовать ключи из переменных окружения (подходит для локали и CI/CD):
|
|
14
|
+
```bash
|
|
15
|
+
export SSH_PRIVATE_KEY="$(cat ~/.ssh/id_ed25519)"
|
|
16
|
+
export SSH_PUBLIC_KEY="$(cat ~/.ssh/id_ed25519.pub)"
|
|
17
|
+
keysloth pull -r git@github.com:USER/REPO.git -p "PASSWORD"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
2. Создать симлинки под ожидаемые имена (быстро, локально):
|
|
21
|
+
```bash
|
|
22
|
+
ln -s ~/.ssh/id_ed25519 ~/.ssh/id_rsa
|
|
23
|
+
ln -s ~/.ssh/id_ed25519.pub ~/.ssh/id_rsa.pub
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
3. Сгенерировать отдельный RSA‑ключ (если принципиально):
|
|
27
|
+
```bash
|
|
28
|
+
ssh-keygen -t rsa -b 4096 -C "you@example.com" -f ~/.ssh/id_rsa
|
|
29
|
+
# добавить ~/.ssh/id_rsa.pub в GitHub/GitLab
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Предложения по доработке
|
|
33
|
+
1. Поддержать Ed25519 «из коробки» в `create_ssh_credentials`:
|
|
34
|
+
- Приоритет: переменные окружения `SSH_PRIVATE_KEY`/`SSH_PUBLIC_KEY` (как сейчас).
|
|
35
|
+
- Fallback: искать пары ключей по стандартным путям в порядке:
|
|
36
|
+
- `~/.ssh/id_rsa(.pub)`
|
|
37
|
+
- `~/.ssh/id_ed25519(.pub)`
|
|
38
|
+
- Если пара найдена — использовать; иначе выбрасывать `AuthenticationError` с подсказками.
|
|
39
|
+
|
|
40
|
+
2. Добавить конфигурируемый путь к ключу:
|
|
41
|
+
- Env: `KEYSLOTH_SSH_KEY_PATH` (если задан — использовать `<path>` и `<path>.pub`).
|
|
42
|
+
- Опция CLI: `--ssh-key PATH` (пробрасывать в `GitManager`).
|
|
43
|
+
|
|
44
|
+
3. Рассмотреть поддержку ssh-agent:
|
|
45
|
+
- Использовать `Rugged::Credentials::SshKeyFromAgent` при отсутствии файлов и env.
|
|
46
|
+
|
|
47
|
+
4. Улучшить сообщение об ошибке:
|
|
48
|
+
- Печатать проверённые пути, быстрые шаги: env, симлинк, `--ssh-key`.
|
|
49
|
+
|
|
50
|
+
## Тестирование
|
|
51
|
+
- Добавить тесты в `spec/keysloth/git_manager_spec.rb`:
|
|
52
|
+
- только `id_rsa`
|
|
53
|
+
- только `id_ed25519`
|
|
54
|
+
- ключи через ENV
|
|
55
|
+
- путь через `KEYSLOTH_SSH_KEY_PATH`
|
|
56
|
+
- отсутствие ключей → `AuthenticationError`
|
|
57
|
+
|
|
58
|
+
## Документация
|
|
59
|
+
- Обновить README (раздел «Настройка SSH ключей») и `task/test_plan.md` (раздел про SSH) с примерами для Ed25519 и `--ssh-key`/env.
|
data/task/plan.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# План реализации KeySloth - Ruby Gem для управления зашифрованными секретами
|
|
2
|
+
|
|
3
|
+
## 1. Подготовка проекта
|
|
4
|
+
|
|
5
|
+
### 1.1 Структура gem'а
|
|
6
|
+
- [x] Создать основную структуру ruby gem'а
|
|
7
|
+
- [x] Настроить gemspec файл с описанием и зависимостями
|
|
8
|
+
- [x] Создать базовую структуру директорий (lib/, bin/, spec/, etc.)
|
|
9
|
+
- [x] Настроить Gemfile с необходимыми зависимостями
|
|
10
|
+
|
|
11
|
+
### 1.2 Инфраструктура
|
|
12
|
+
- [x] Настроить RSpec для тестирования
|
|
13
|
+
- [x] Настроить RuboCop для линтинга кода
|
|
14
|
+
- [x] Создать базовый Rakefile
|
|
15
|
+
- [ ] Настроить CI/CD (GitHub Actions)
|
|
16
|
+
|
|
17
|
+
## 2. Основная функциональность
|
|
18
|
+
|
|
19
|
+
### 2.1 Криптографический модуль
|
|
20
|
+
- [x] Реализовать модуль шифрования/дешифрования файлов
|
|
21
|
+
- [x] Выбрать алгоритм шифрования (AES-256-GCM)
|
|
22
|
+
- [x] Реализовать генерацию ключей из паролей (PBKDF2)
|
|
23
|
+
- [x] Добавить обработку различных типов файлов (.cer, .p12, .mobileprovisioning, .json)
|
|
24
|
+
|
|
25
|
+
### 2.2 Работа с Git репозиториями
|
|
26
|
+
- [x] Реализовать клонирование удаленного репозитория (через системные команды)
|
|
27
|
+
- [x] Реализовать переключение на нужную ветку
|
|
28
|
+
- [x] Реализовать получение файлов из репозитория
|
|
29
|
+
- [x] Реализовать коммит и push изменений обратно в репозиторий
|
|
30
|
+
- [x] Добавить очистку временных файлов после работы
|
|
31
|
+
|
|
32
|
+
### 2.3 Управление файловой системой
|
|
33
|
+
- [x] Реализовать создание локальной директории для секретов
|
|
34
|
+
- [x] Реализовать извлечение зашифрованных файлов в локальную директорию
|
|
35
|
+
- [x] Реализовать упаковку измененных файлов для отправки в репозиторий
|
|
36
|
+
- [x] Добавить валидацию путей и проверку прав доступа
|
|
37
|
+
|
|
38
|
+
## 3. Интерфейс командной строки
|
|
39
|
+
|
|
40
|
+
### 3.1 Команда получения секретов
|
|
41
|
+
- [x] Реализовать команду `pull` для получения и дешифровки секретов
|
|
42
|
+
- [x] Параметры: URL репозитория, ветка, пароль, путь к локальной директории
|
|
43
|
+
- [x] Добавить валидацию входных параметров
|
|
44
|
+
- [x] Реализовать подробное логирование процесса
|
|
45
|
+
- [x] Добавить обработку ошибок (неверный пароль, недоступный репозиторий, etc.)
|
|
46
|
+
|
|
47
|
+
### 3.2 Команда отправки секретов
|
|
48
|
+
- [x] Реализовать команду `push` для шифровки и отправки секретов
|
|
49
|
+
- [x] Те же параметры что и для pull
|
|
50
|
+
- [x] Добавить проверку изменений перед отправкой
|
|
51
|
+
- [x] Реализовать создание коммита с описанием изменений
|
|
52
|
+
- [x] Добавить подтверждение действия перед отправкой
|
|
53
|
+
|
|
54
|
+
### 3.3 Help команда
|
|
55
|
+
- [x] Реализовать команду `help` с описанием всех команд
|
|
56
|
+
- [x] Добавить примеры использования
|
|
57
|
+
- [x] Добавить описание параметров и опций
|
|
58
|
+
- [x] Создать man-страницу или подробную документацию
|
|
59
|
+
|
|
60
|
+
### 3.4 Дополнительные команды
|
|
61
|
+
- [x] Реализовать команду `init` для первичной настройки
|
|
62
|
+
- [x] Добавить команду `status` для проверки состояния локальных секретов
|
|
63
|
+
- [x] Реализовать команду `validate` для проверки целостности файлов
|
|
64
|
+
- [x] Добавить команду `restore` для восстановления из backup'а
|
|
65
|
+
- [x] Добавить команду `version` для отображения версии
|
|
66
|
+
|
|
67
|
+
## 4. Безопасность и обработка ошибок
|
|
68
|
+
|
|
69
|
+
### 4.1 Безопасность
|
|
70
|
+
- [x] Добавить проверку целостности зашифрованных файлов
|
|
71
|
+
|
|
72
|
+
### 4.2 Обработка ошибок
|
|
73
|
+
- [x] Добавить логирование ошибок и отладочной информации
|
|
74
|
+
|
|
75
|
+
## 5. Тестирование
|
|
76
|
+
|
|
77
|
+
### 5.1 Unit тесты
|
|
78
|
+
- [x] Написать тесты для криптографического модуля
|
|
79
|
+
- [x] Написать тесты для работы с файловой системой
|
|
80
|
+
- [x] Написать тесты для Git операций
|
|
81
|
+
- [x] Написать тесты для CLI интерфейса
|
|
82
|
+
|
|
83
|
+
## 6. Документация и комментарии
|
|
84
|
+
|
|
85
|
+
### 6.1 Код документация
|
|
86
|
+
- [x] Добавить подробные комментарии ко всем публичным методам
|
|
87
|
+
- [x] Создать YARD документацию
|
|
88
|
+
- [x] Добавить примеры использования в комментариях
|
|
89
|
+
- [x] Документировать все исключения и edge cases
|
|
90
|
+
|
|
91
|
+
### 6.2 Пользовательская документация
|
|
92
|
+
- [x] Создать README.md с инструкциями по установке и использованию
|
|
93
|
+
- [x] Написать CHANGELOG.md
|
|
94
|
+
- [x] Создать примеры использования в Makefile
|
|
95
|
+
- [x] Добавить troubleshooting guide
|
|
96
|
+
|
|
97
|
+
## 7. Публикация и развертывание
|
|
98
|
+
|
|
99
|
+
### 7.1 Подготовка к публикации
|
|
100
|
+
- [x] Финальная проверка всех тестов
|
|
101
|
+
- [x] Код-ревью и рефакторинг
|
|
102
|
+
- [x] Проверка безопасности и соответствия best practices
|
|
103
|
+
- [x] Финализация версии и тегирование
|
|
104
|
+
|
|
105
|
+
### 7.2 Публикация
|
|
106
|
+
- [ ] Публикация gem'а в RubyGems.org
|
|
107
|
+
- [ ] Создание GitHub release с описанием изменений
|
|
108
|
+
- [ ] Обновление документации на публичных ресурсах
|
|
109
|
+
- [ ] Создание примеров интеграции с популярными CI/CD системами
|
|
110
|
+
|
|
111
|
+
## 8. Поддержка и развитие
|
|
112
|
+
|
|
113
|
+
### 8.1 Мониторинг
|
|
114
|
+
- [ ] Настроить мониторинг использования gem'а
|
|
115
|
+
- [ ] Создать систему сбора обратной связи
|
|
116
|
+
- [ ] Настроить автоматические уведомления о проблемах
|
|
117
|
+
|
|
118
|
+
### 8.2 Развитие
|
|
119
|
+
- [ ] Планирование будущих фич (GUI интерфейс, интеграции)
|
|
120
|
+
- [ ] Поддержка обратной совместимости
|
|
121
|
+
- [ ] Регулярные обновления зависимостей
|
|
122
|
+
- [ ] Поддержка сообщества пользователей
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
## Вопросы для уточнения
|
|
129
|
+
|
|
130
|
+
### 9.1 Технические детали
|
|
131
|
+
- [x] Какой алгоритм шифрования использовать? (AES-256-GCM, ChaCha20-Poly1305, другой?)
|
|
132
|
+
**Ответ:** AES-256-GCM - наиболее подходящий выбор, так как обеспечивает защиту целостности данных и аутентификацию, широко поддерживается, имеет хорошую производительность и является стандартом индустрии.
|
|
133
|
+
|
|
134
|
+
- [x] В каком формате хранить зашифрованные файлы в репозитории? (один архив или отдельные файлы?)
|
|
135
|
+
**Ответ:** Отдельные зашифрованные файлы с сохранением исходных имен + расширение .enc. Это обеспечивает гранулярность изменений в Git, позволяет отслеживать изменения конкретных файлов и упрощает парциальное обновление секретов.
|
|
136
|
+
|
|
137
|
+
- [x] Как именно будет называться gem? (KeySloth или другое имя?)
|
|
138
|
+
**Ответ:** KeySloth - подходящее название, отражает суть инструмента (ключи + медленная и осторожная работа с секретами). Альтернатива: SecretSloth.
|
|
139
|
+
|
|
140
|
+
### 9.2 Git и аутентификация
|
|
141
|
+
- [x] Поддерживать только HTTPS или также SSH ключи для доступа к репозиторию?
|
|
142
|
+
**Ответ:** Только SSH ключи. Это обеспечивает более высокий уровень безопасности и исключает необходимость передачи паролей/токенов. В CI/CD системах также можно настроить SSH ключи через deploy keys или service accounts.
|
|
143
|
+
|
|
144
|
+
- [x] Как передавать учетные данные Git? (параметры команды, переменные окружения, git credential helper?)
|
|
145
|
+
**Ответ:** Использование SSH ключей. Для локальной работы - стандартные SSH ключи из ~/.ssh/. Для CI/CD - настройка SSH ключей через переменные окружения (SSH_PRIVATE_KEY) или секреты системы CI/CD. Автоматическое добавление хостов в known_hosts при необходимости.
|
|
146
|
+
|
|
147
|
+
### 9.3 CLI интерфейс
|
|
148
|
+
- [x] Нужен ли namespace для команд? (например `keysloth pull` vs отдельные исполняемые файлы?)
|
|
149
|
+
**Ответ:** Да, использовать namespace `keysloth <command>`. Это современный подход, обеспечивает чистоту пространства имен системы и упрощает добавление новых команд. Основные команды: `keysloth pull`, `keysloth push`, `keysloth help`.
|
|
150
|
+
|
|
151
|
+
- [x] Нужна ли поддержка конфигурационных файлов для хранения URL репозитория и других параметров?
|
|
152
|
+
**Ответ:** Да, опциональная поддержка `.keyslothrc` (YAML формат) для хранения URL репозитория, ветки по умолчанию, путей. Параметры командной строки должны иметь приоритет над конфигом для гибкости в CI/CD.
|
|
153
|
+
|
|
154
|
+
### 9.4 Обработка конфликтов
|
|
155
|
+
- [x] Как обрабатывать ситуацию, когда несколько разработчиков одновременно изменяют секреты?
|
|
156
|
+
**Ответ:** Использовать стратегию "fail-fast": перед push выполнять pull и проверять изменения. При обнаружении конфликтов - прекращать операцию с подробным сообщением об ошибке и рекомендациями по разрешению. Добавить команду `keysloth status` для проверки состояния.
|
|
157
|
+
|
|
158
|
+
- [x] Как разрешать конфликты при слиянии изменений в Git?
|
|
159
|
+
**Ответ:** Не автоматически. Инструмент должен обнаруживать конфликты и требовать ручного разрешения администратором проекта. Предоставлять детальную информацию о конфликтующих файлах и временных метках изменений.
|
|
160
|
+
|
|
161
|
+
### 9.5 Дополнительные требования
|
|
162
|
+
- [x] Какие версии Ruby должны поддерживаться? (2.7+, 3.0+ и т.д.)
|
|
163
|
+
**Ответ:** Ruby 2.7+ для совместимости с современными CI/CD системами и не слишком устаревшими проектами. Активно тестировать на Ruby 2.7, 3.0, 3.1, 3.2. Указать minimum_ruby_version = "2.7.0" в gemspec.
|
|
164
|
+
|
|
165
|
+
- [x] Нужны ли подробные логи работы? В какой формат? (stdout, файлы?)
|
|
166
|
+
**Ответ:** Да, многоуровневое логирование. По умолчанию INFO в stdout. Параметр `--verbose` для DEBUG уровня. Параметр `--quiet` для только ERROR. Логи в простом текстовом формате с timestamp для удобства отладки в CI/CD.
|
|
167
|
+
|
|
168
|
+
- [x] Нужна ли возможность создания резервных копий перед изменениями?
|
|
169
|
+
**Ответ:** Да, автоматическое создание backup'ов локальной директории с секретами перед выполнением pull операций. Хранить последние 3 копии с временными метками. Команда `keysloth restore` для восстановления из backup'а.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# План отказа от Rugged/libgit2 и переход на системный git
|
|
2
|
+
|
|
3
|
+
## Текущая проблема
|
|
4
|
+
- Использование `Rugged` (libgit2) приводит к частым проблемам:
|
|
5
|
+
- Нужна сборка нативного гема: `cmake`, `libgit2`, `libssh2`, `openssl`, корректный `PKG_CONFIG_PATH`.
|
|
6
|
+
- Неполная сборка без SSH → ошибка `unsupported URL protocol` при `git@...`.
|
|
7
|
+
- Предзагрузка `RUBYOPT=-r rugged` ломает другие ruby-команды до установки гема (`gem install` и т.п.).
|
|
8
|
+
- Для пользователей и CI это поднимает порог входа и вызывает нестабильность.
|
|
9
|
+
|
|
10
|
+
Вывод: переходим на системный `git` (через `Open3`), убираем зависимость от `rugged`.
|
|
11
|
+
|
|
12
|
+
## Цели
|
|
13
|
+
- Исключить `rugged`/`libgit2` и любые шаги по их установке.
|
|
14
|
+
- Сохранить поведение `pull`/`push`/ветки/коммиты/cleanup.
|
|
15
|
+
- Улучшить DX: всё работает там, где есть стандартный `git` и SSH.
|
|
16
|
+
|
|
17
|
+
## Таск‑лист миграции
|
|
18
|
+
1) Подготовка
|
|
19
|
+
- Убедиться, что `keysloth.gemspec` не тянет `rugged` (оставляем как есть, rugged не в зависимостях).
|
|
20
|
+
- Создать ветку `feat/system-git`.
|
|
21
|
+
|
|
22
|
+
2) Реализация системного Git в `KeySloth::GitManager`
|
|
23
|
+
- Заменить логику на вызовы системного `git`:
|
|
24
|
+
- clone: `git clone --depth 1 <repo_url> <tmp>` (если ветка не `main`, затем checkout).
|
|
25
|
+
- checkout: `git checkout <branch>` или `git checkout -b <branch> --track origin/<branch>`.
|
|
26
|
+
- ensure up-to-date: `git fetch origin <branch>` + сравнить `git rev-parse HEAD` и `git rev-parse origin/<branch>`; при расхождении — `git pull --ff-only` или ошибка.
|
|
27
|
+
- list .enc: `Dir.glob("**/*.enc", base: tmp)` + `File.read`.
|
|
28
|
+
- clear .enc: `Dir.glob("**/*.enc", base: tmp)` + `File.delete`.
|
|
29
|
+
- write .enc: создать директории, `File.write`.
|
|
30
|
+
- add & commit: `git add -A`, `git commit -m "msg"` (автор из ENV: `GIT_AUTHOR_*`/`GIT_COMMITTER_*` или `-c user.name=... -c user.email=...`).
|
|
31
|
+
- push: `git push origin <branch>`.
|
|
32
|
+
- Инкапсулировать выполнение команд в метод: `run_git(cmd, env: {})` с возвратом `[stdout, stderr, status]`, логированием и конвертацией ошибок в `RepositoryError`.
|
|
33
|
+
- Сохранить `cleanup` (удаление временной директории).
|
|
34
|
+
|
|
35
|
+
3) Аутентификация SSH
|
|
36
|
+
- По умолчанию использовать стандартный SSH (ssh-agent, `~/.ssh/config`).
|
|
37
|
+
- Если заданы `SSH_PRIVATE_KEY`/`SSH_PUBLIC_KEY` (CI):
|
|
38
|
+
- создать временную директорию ключей, записать файлы, `chmod 600`.
|
|
39
|
+
- выставить `GIT_SSH_COMMAND="ssh -i <path> -o StrictHostKeyChecking=no"` для всех git‑команд.
|
|
40
|
+
- Опционально поддержать `KEYSLOTH_SSH_KEY_PATH` (если указан — использовать его в `GIT_SSH_COMMAND`).
|
|
41
|
+
- Позже: можно добавить поддержку `id_ed25519` в явном виде, но системный `ssh` уже это умеет.
|
|
42
|
+
|
|
43
|
+
4) Обработка ошибок и логирование
|
|
44
|
+
- Для каждой git‑команды логировать: краткое описание, команду, код выхода; при ошибке показывать stderr.
|
|
45
|
+
- Генерировать `RepositoryError` с понятным сообщением и советом (проверить доступ/ветку/SSH).
|
|
46
|
+
|
|
47
|
+
5) Тесты
|
|
48
|
+
- Переписать `spec/keysloth/git_manager_spec.rb`:
|
|
49
|
+
- мокать `Open3.capture3` и проверять последовательность вызовов.
|
|
50
|
+
- кейсы: clone/checkout/fetch/pull ok, нет ветки, нет изменений для коммита, запись файлов, push ok, ошибка SSH/доступа, ошибка git.
|
|
51
|
+
- кейсы с `GIT_SSH_COMMAND` и env‑ключами.
|
|
52
|
+
|
|
53
|
+
6) Документация и примеры
|
|
54
|
+
- Обновить `task/test_plan.md`:
|
|
55
|
+
- удалить блок «Важно про Rugged» и шаги `gem install rugged`, `RUBYOPT=-r rugged`.
|
|
56
|
+
- уточнить, что требуется установленный `git` и корректный SSH.
|
|
57
|
+
- поправить пример для push: использовать `git@github.com:...`, а не `ssh@github.com:...`.
|
|
58
|
+
- Обновить `README.md`:
|
|
59
|
+
- удалить упоминания rugged и предзагрузки.
|
|
60
|
+
- добавить раздел «Зависимости»: нужен только `git`.
|
|
61
|
+
- в CI‑примере убрать установку rugged и шаг с RUBYOPT, при необходимости показать `GIT_SSH_COMMAND`.
|
|
62
|
+
- Добавить запись в `CHANGELOG.md` (0.1.x → 0.1.(x+1)): «Переведён на системный git; удалены инструкции про rugged».
|
|
63
|
+
|
|
64
|
+
7) Релиз
|
|
65
|
+
- Прогнать тесты и линтинг.
|
|
66
|
+
- Обновить версию (`lib/keysloth/version.rb`).
|
|
67
|
+
- Сформировать gem, smoke‑тест: `pull/push` на чистой машине с только `git`.
|
|
68
|
+
- Создать GitHub release.
|
|
69
|
+
|
|
70
|
+
## Критерии готовности
|
|
71
|
+
- `keysloth pull/push` работают без установки rugged/libgit2.
|
|
72
|
+
- Все тесты зелёные; покрыт основной happy‑path и ошибки SSH.
|
|
73
|
+
- `README.md`/`task/test_plan.md` не содержат упоминаний rugged, примеры актуальны.
|
|
74
|
+
- Ошибки `git` отображаются с понятными причинами и советами.
|
|
75
|
+
|
|
76
|
+
## Согласованные решения
|
|
77
|
+
1) Поведение pull
|
|
78
|
+
- Выполняем `git fetch origin <branch>` и затем `git pull --ff-only`. При невозможности fast-forward — завершаем с ошибкой с понятным сообщением.
|
|
79
|
+
|
|
80
|
+
2) Автор коммита
|
|
81
|
+
- Требуются глобальные настройки Git: `user.name` и `user.email`. Перед коммитом проверяем их наличие, при отсутствии — ошибка с подсказкой настроить `git config --global`.
|
|
82
|
+
|
|
83
|
+
3) Приоритет и способ SSH-аутентификации
|
|
84
|
+
- Приоритет источников ключей: `KEYSLOTH_SSH_KEY_PATH` → `SSH_PRIVATE_KEY`/`SSH_PUBLIC_KEY` (создаём временные файлы, `chmod 600`) → системные ключи/агент (`~/.ssh`, `ssh-agent`).
|
|
85
|
+
- При использовании нестандартных ключей выставляем `GIT_SSH_COMMAND` с вызовом системного `ssh`.
|
|
86
|
+
|
|
87
|
+
4) Passphrase-ключи
|
|
88
|
+
- Поддержка passphrase-ключей не входит в объём. Возможна работа через `ssh-agent` или безфразные ключи.
|
|
89
|
+
|
|
90
|
+
5) Стратегия клонирования
|
|
91
|
+
- По умолчанию используем `git clone --depth 1` (shallow). Если для `pull --ff-only` потребуется история, выполняем однократно `git fetch --unshallow` и повторяем `pull`.
|
|
92
|
+
- Предусматриваем переключатель окружения `KEYSLOTH_FULL_CLONE=true` для полного клона при необходимости.
|
|
93
|
+
|
|
94
|
+
6) Очистка `.enc`
|
|
95
|
+
- Очищаем глобально все `**/*.enc` в рабочем дереве перед записью новых файлов. Риски (удаление «чужих» `.enc`, гонки) осознанно принимаются; перед очисткой выполняем `fetch` + `pull --ff-only` для минимизации расхождений.
|
|
96
|
+
|
|
97
|
+
7) Зависимости и SSH
|
|
98
|
+
- Используются стандартные системные инструменты: Git CLI и системный SSH (обычно OpenSSH). Дополнительные нативные библиотеки не требуются.
|
|
99
|
+
- В CI при использовании ключей из ENV формируем `GIT_SSH_COMMAND`, например: `ssh -i <path> -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null`.
|
|
100
|
+
- В локальной разработке проверку хостов не отключаем.
|
|
101
|
+
|
|
102
|
+
8) Версионирование
|
|
103
|
+
- Увеличиваем patch-версию.
|