makit 0.0.147 → 0.0.153
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 +4 -4
- data/lib/generated/makit/v1/configuration/project_pb.rb +22 -0
- data/lib/generated/makit/v1/configuration/project_service_pb.rb +34 -0
- data/lib/generated/makit/v1/configuration/project_service_services_pb.rb +51 -0
- data/lib/generated/makit/v1/git/git_repository_model_pb.rb +22 -0
- data/lib/generated/makit/v1/git/git_repository_service_pb.rb +29 -0
- data/lib/generated/makit/v1/git/git_repository_service_services_pb.rb +39 -0
- data/lib/generated/makit/v1/gitlab/pipeline_pb.rb +26 -0
- data/lib/generated/makit/v1/gitlab/pipeline_result_pb.rb +29 -0
- data/lib/generated/makit/v1/gitlab/pipeline_service_pb.rb +36 -0
- data/lib/generated/makit/v1/gitlab/pipeline_service_services_pb.rb +41 -0
- data/lib/generated/makit/v1/grpc/service_specification_pb.rb +27 -0
- data/lib/generated/makit/v1/grpc/test_specification_pb.rb +29 -0
- data/lib/generated/makit/v1/io/filesystem_pb.rb +27 -0
- data/lib/generated/makit/v1/io/filesystem_services_pb.rb +47 -0
- data/lib/generated/makit/v1/makit.v1_pb.rb +35 -0
- data/lib/generated/makit/v1/makit.v1_services_pb.rb +26 -0
- data/lib/generated/makit/v1/podman/podman_service_pb.rb +64 -0
- data/lib/generated/makit/v1/podman/podman_service_services_pb.rb +52 -0
- data/lib/generated/makit/v1/services/repository_manager_model_pb.rb +23 -0
- data/lib/generated/makit/v1/services/repository_manager_service_pb.rb +32 -0
- data/lib/generated/makit/v1/services/repository_manager_service_services_pb.rb +35 -0
- data/lib/generated/makit/v1/spec/message_proto_generator_pb.rb +33 -0
- data/lib/generated/makit/v1/spec/message_proto_generator_services_pb.rb +38 -0
- data/lib/generated/makit/v1/spec/message_spec_pb.rb +31 -0
- data/lib/generated/makit/v1/spec/message_spec_suite_pb.rb +30 -0
- data/lib/generated/makit/v1/spec/message_spec_test_pb.rb +34 -0
- data/lib/generated/makit/v1/spec/proto_service_pb.rb +53 -0
- data/lib/generated/makit/v1/spec/proto_service_services_pb.rb +42 -0
- data/lib/generated/makit/v1/spec/spec_manifest_pb.rb +44 -0
- data/lib/generated/makit/v1/web/link_pb.rb +20 -0
- data/lib/makit/azure/blob_storage.rb +257 -0
- data/lib/makit/azure/cli.rb +285 -0
- data/lib/makit/configuration/project.rb +137 -291
- data/lib/makit/git/repository.rb +24 -190
- data/lib/makit/gitlab/pipeline.rb +16 -16
- data/lib/makit/gitlab/pipeline_service_impl.rb +43 -43
- data/lib/makit/io/filesystem_service_impl.rb +6 -6
- data/lib/makit/lint.rb +212 -0
- data/lib/makit/logging/configuration.rb +2 -1
- data/lib/makit/logging.rb +15 -2
- data/lib/makit/podman/podman.rb +20 -20
- data/lib/makit/podman/podman_service_impl.rb +41 -41
- data/lib/makit/secrets/azure_key_vault.rb +323 -0
- data/lib/makit/secrets/azure_secrets.rb +183 -0
- data/lib/makit/secrets/local_secrets.rb +72 -0
- data/lib/makit/secrets/secrets_manager.rb +105 -0
- data/lib/makit/secrets.rb +10 -45
- data/lib/makit/tasks/bump.rb +7 -0
- data/lib/makit/tasks/info.rb +204 -0
- data/lib/makit/tasks/integrate.rb +28 -1
- data/lib/makit/tasks/secrets.rb +7 -0
- data/lib/makit/tasks/version.rb +6 -0
- data/lib/makit/tasks.rb +4 -0
- data/lib/makit/v1/configuration/project_service_impl.rb +1 -1
- data/lib/makit/version.rb +382 -1
- data/lib/makit.rb +21 -18
- metadata +46 -5
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
module Makit
|
|
4
|
+
module Secrets
|
|
5
|
+
# Azure Key Vault operations
|
|
6
|
+
class AzureKeyVault
|
|
7
|
+
# Required environment variables
|
|
8
|
+
REQUIRED_VARS = [
|
|
9
|
+
"AZURE_STORAGE_ACCOUNT",
|
|
10
|
+
"AZURE_STORAGE_ACCOUNT_KEY",
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
# Optional environment variables
|
|
14
|
+
OPTIONAL_VARS = [
|
|
15
|
+
"AZURE_CDN_RESOURCE_GROUP",
|
|
16
|
+
"AZURE_CDN_PROFILE_NAME",
|
|
17
|
+
"AZURE_CDN_ENDPOINT_NAME",
|
|
18
|
+
"GITLAB_NUGET_TOKEN",
|
|
19
|
+
"GITLAB_USERNAME",
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
# All environment variablesazure_key_vault.r
|
|
23
|
+
ALL_VARS = (REQUIRED_VARS + OPTIONAL_VARS).freeze
|
|
24
|
+
|
|
25
|
+
# Check if kvenv is available
|
|
26
|
+
def self.kvenv_available?
|
|
27
|
+
system("which kvenv > /dev/null 2>&1")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Check if Azure CLI is authenticated
|
|
31
|
+
def self.azure_cli_authenticated?
|
|
32
|
+
return false unless system("which az > /dev/null 2>&1")
|
|
33
|
+
system("az account show > /dev/null 2>&1")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Get Azure Key Vault name from environment or use default
|
|
37
|
+
def self.keyvault_name
|
|
38
|
+
ENV["AZURE_KEYVAULT_NAME"] || "louparslow-secrets"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Get secret name from environment or auto-generate from GIT_REMOTE_URL
|
|
42
|
+
def self.secret_name
|
|
43
|
+
return ENV["AZURE_SECRET_NAME"] if ENV["AZURE_SECRET_NAME"] && !ENV["AZURE_SECRET_NAME"].empty?
|
|
44
|
+
|
|
45
|
+
# Auto-generate from GIT_REMOTE_URL
|
|
46
|
+
git_remote_url = get_git_remote_url
|
|
47
|
+
if git_remote_url && !git_remote_url.empty?
|
|
48
|
+
generate_secret_name_from_url(git_remote_url)
|
|
49
|
+
else
|
|
50
|
+
"portal-secrets" # Fallback default
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get GIT_REMOTE_URL from various sources
|
|
55
|
+
def self.get_git_remote_url
|
|
56
|
+
# Try constant first
|
|
57
|
+
return GIT_REMOTE_URL if defined?(GIT_REMOTE_URL) && !GIT_REMOTE_URL.nil? && !GIT_REMOTE_URL.empty?
|
|
58
|
+
|
|
59
|
+
# Try Git module
|
|
60
|
+
if defined?(Makit::Git) && Makit::Git.git_repo?
|
|
61
|
+
url = Makit::Git.get_remote_url
|
|
62
|
+
return url if url && !url.empty?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Try project configuration
|
|
66
|
+
if defined?(Makit::Configuration::Project)
|
|
67
|
+
project = Makit::Configuration::Project.default
|
|
68
|
+
url = project.git_remote_url
|
|
69
|
+
return url if url && !url.nil? && !url.empty?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Generate a valid Azure Key Vault secret name from a Git remote URL
|
|
76
|
+
# Azure Key Vault secret names must be 1-127 characters, alphanumeric and hyphens only,
|
|
77
|
+
# cannot start or end with hyphen, and cannot have consecutive hyphens
|
|
78
|
+
def self.generate_secret_name_from_url(url)
|
|
79
|
+
# Remove protocol (http://, https://, git@)
|
|
80
|
+
name = url.gsub(/^https?:\/\//, "").gsub(/^git@/, "")
|
|
81
|
+
|
|
82
|
+
# Remove .git suffix if present
|
|
83
|
+
name = name.gsub(/\.git$/, "")
|
|
84
|
+
|
|
85
|
+
# Replace invalid characters with hyphens
|
|
86
|
+
name = name.gsub(/[^a-zA-Z0-9\-]/, "-")
|
|
87
|
+
|
|
88
|
+
# Remove consecutive hyphens
|
|
89
|
+
name = name.gsub(/-+/, "-")
|
|
90
|
+
|
|
91
|
+
# Remove leading/trailing hyphens
|
|
92
|
+
name = name.gsub(/^-+|-+$/, "")
|
|
93
|
+
|
|
94
|
+
# Ensure it starts with a letter or number (Azure requirement)
|
|
95
|
+
name = "secret-#{name}" if name.empty? || name.match(/^[^a-zA-Z0-9]/)
|
|
96
|
+
|
|
97
|
+
# Truncate to 127 characters (Azure Key Vault limit)
|
|
98
|
+
name = name[0, 127]
|
|
99
|
+
|
|
100
|
+
# Remove trailing hyphen if truncation created one
|
|
101
|
+
name = name.gsub(/-+$/, "")
|
|
102
|
+
|
|
103
|
+
# Ensure it's not empty
|
|
104
|
+
name = "git-secrets" if name.empty?
|
|
105
|
+
|
|
106
|
+
name
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Get secret prefix from environment
|
|
110
|
+
def self.secret_prefix
|
|
111
|
+
ENV["AZURE_SECRET_PREFIX"]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Setup method: Attempt to initialize environment variables
|
|
115
|
+
def self.setup
|
|
116
|
+
puts "Setting up secrets...".colorize(:blue)
|
|
117
|
+
|
|
118
|
+
# Check if required variables are already set
|
|
119
|
+
missing_required = REQUIRED_VARS.select { |var| ENV[var].nil? || ENV[var].empty? }
|
|
120
|
+
|
|
121
|
+
if missing_required.empty?
|
|
122
|
+
puts " All required environment variables are already set".colorize(:green)
|
|
123
|
+
return true
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
puts " Missing required variables: #{missing_required.join(", ")}".colorize(:yellow)
|
|
127
|
+
puts " Attempting to load from Azure Key Vault...".colorize(:yellow)
|
|
128
|
+
|
|
129
|
+
# Try to load from Azure Key Vault
|
|
130
|
+
if load(keyvault_name: nil, secret_name: nil)
|
|
131
|
+
# Verify again after loading
|
|
132
|
+
still_missing = REQUIRED_VARS.select { |var| ENV[var].nil? || ENV[var].empty? }
|
|
133
|
+
if still_missing.empty?
|
|
134
|
+
puts " Successfully loaded all required secrets".colorize(:green)
|
|
135
|
+
return true
|
|
136
|
+
else
|
|
137
|
+
puts " Warning: Still missing required variables: #{still_missing.join(", ")}".colorize(:yellow)
|
|
138
|
+
return false
|
|
139
|
+
end
|
|
140
|
+
else
|
|
141
|
+
puts " Could not load secrets from Azure Key Vault".colorize(:yellow)
|
|
142
|
+
puts " Please set environment variables manually or configure Azure Key Vault access".colorize(:yellow)
|
|
143
|
+
return false
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Verify that expected environment variables are set
|
|
148
|
+
def self.verify(warn_only: true)
|
|
149
|
+
issues = []
|
|
150
|
+
warnings = []
|
|
151
|
+
|
|
152
|
+
# Check required variables
|
|
153
|
+
REQUIRED_VARS.each do |var|
|
|
154
|
+
if ENV[var].nil? || ENV[var].empty?
|
|
155
|
+
issues << "Required variable '#{var}' is not set"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Check optional variables (only warn)
|
|
160
|
+
OPTIONAL_VARS.each do |var|
|
|
161
|
+
if ENV[var].nil? || ENV[var].empty?
|
|
162
|
+
warnings << "Optional variable '#{var}' is not set"
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Report issues
|
|
167
|
+
if issues.any?
|
|
168
|
+
if warn_only
|
|
169
|
+
issues.each { |issue| puts " ⚠️ #{issue}".colorize(:yellow) }
|
|
170
|
+
else
|
|
171
|
+
issues.each { |issue| puts " ❌ #{issue}".colorize(:red) }
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
if warnings.any?
|
|
176
|
+
warnings.each { |warning| puts " ℹ️ #{warning}".colorize(:cyan) }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
if issues.empty? && warnings.empty?
|
|
180
|
+
puts " ✅ All environment variables are set".colorize(:green)
|
|
181
|
+
return true
|
|
182
|
+
elsif issues.empty?
|
|
183
|
+
puts " ✅ All required environment variables are set".colorize(:green)
|
|
184
|
+
return true
|
|
185
|
+
else
|
|
186
|
+
return false
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Get status of all environment variables
|
|
191
|
+
def self.status
|
|
192
|
+
puts "Environment Variables Status:".colorize(:blue)
|
|
193
|
+
puts ""
|
|
194
|
+
|
|
195
|
+
puts "Required Variables:".colorize(:cyan)
|
|
196
|
+
REQUIRED_VARS.each do |var|
|
|
197
|
+
status = ENV[var].nil? || ENV[var].empty? ? "❌ Not set" : "✅ Set"
|
|
198
|
+
color = ENV[var].nil? || ENV[var].empty? ? :red : :green
|
|
199
|
+
puts " #{var}: #{status}".colorize(color)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
puts ""
|
|
203
|
+
puts "Optional Variables:".colorize(:cyan)
|
|
204
|
+
OPTIONAL_VARS.each do |var|
|
|
205
|
+
status = ENV[var].nil? || ENV[var].empty? ? "⚪ Not set" : "✅ Set"
|
|
206
|
+
color = ENV[var].nil? || ENV[var].empty? ? :yellow : :green
|
|
207
|
+
puts " #{var}: #{status}".colorize(color)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Load secrets from Azure Key Vault using kvenv
|
|
212
|
+
# @param keyvault_name [String, nil] The Azure Key Vault name (defaults to ENV["AZURE_KEYVAULT_NAME"] or "louparslow-secrets")
|
|
213
|
+
# @param secret_name [String, nil] The secret name in Key Vault (defaults to ENV["AZURE_SECRET_NAME"] or "portal-secrets")
|
|
214
|
+
# @return [Boolean] true if secrets were loaded successfully, false otherwise
|
|
215
|
+
def self.load(keyvault_name: nil, secret_name: nil)
|
|
216
|
+
# Use provided parameters or fall back to defaults
|
|
217
|
+
kv_name = keyvault_name || self.keyvault_name
|
|
218
|
+
sec_name = secret_name || self.secret_name
|
|
219
|
+
|
|
220
|
+
unless kvenv_available?
|
|
221
|
+
puts " Warning: kvenv not installed, secrets will not be loaded".colorize(:yellow)
|
|
222
|
+
return false
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Check if Azure CLI is authenticated (kvenv can use this)
|
|
226
|
+
unless azure_cli_authenticated?
|
|
227
|
+
puts " Warning: Azure CLI not authenticated, run 'az login' first".colorize(:yellow)
|
|
228
|
+
return false
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
cache_file = "/tmp/kvenv-#{sec_name}.json"
|
|
232
|
+
|
|
233
|
+
# Build kvenv command
|
|
234
|
+
cmd_parts = [
|
|
235
|
+
"kvenv cache",
|
|
236
|
+
"--azure",
|
|
237
|
+
"--azure-keyvault-name #{kv_name}",
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
if secret_prefix
|
|
241
|
+
cmd_parts << "--secret-prefix #{secret_prefix}"
|
|
242
|
+
else
|
|
243
|
+
cmd_parts << "--secret-name #{sec_name}"
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
cmd_parts << "--output #{cache_file}"
|
|
247
|
+
|
|
248
|
+
# Run kvenv to cache secrets
|
|
249
|
+
puts " Loading secrets from Key Vault: #{kv_name}, Secret: #{sec_name}".colorize(:cyan)
|
|
250
|
+
success = system(cmd_parts.join(" "))
|
|
251
|
+
return false unless success
|
|
252
|
+
return false unless File.exist?(cache_file)
|
|
253
|
+
|
|
254
|
+
# Load secrets from cache file
|
|
255
|
+
begin
|
|
256
|
+
require "json"
|
|
257
|
+
secrets = JSON.parse(File.read(cache_file))
|
|
258
|
+
|
|
259
|
+
# Set environment variables (don't override existing ones)
|
|
260
|
+
loaded_count = 0
|
|
261
|
+
secrets.each do |key, value|
|
|
262
|
+
unless ENV[key]
|
|
263
|
+
ENV[key] = value
|
|
264
|
+
loaded_count += 1
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
puts " Loaded #{loaded_count} secrets from Azure Key Vault (#{secrets.keys.count} total in secret)".colorize(:green)
|
|
269
|
+
true
|
|
270
|
+
rescue => e
|
|
271
|
+
puts " Warning: Could not parse secrets from cache: #{e.message}".colorize(:yellow)
|
|
272
|
+
false
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Set a secret in Azure Key Vault
|
|
277
|
+
# @param secret_name [String] The name of the secret to set
|
|
278
|
+
# @param secret_value [String] The value of the secret
|
|
279
|
+
# @param keyvault_name [String, nil] The Azure Key Vault name (defaults to ENV["AZURE_KEYVAULT_NAME"] or "louparslow-secrets")
|
|
280
|
+
# @return [Boolean] true if secret was set successfully, false otherwise
|
|
281
|
+
def self.set(secret_name, secret_value, keyvault_name: nil)
|
|
282
|
+
# Use provided parameter or fall back to default
|
|
283
|
+
kv_name = keyvault_name || self.keyvault_name
|
|
284
|
+
|
|
285
|
+
if secret_name.nil? || secret_name.empty?
|
|
286
|
+
raise "Secret name is required"
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
if secret_value.nil? || secret_value.empty?
|
|
290
|
+
raise "Secret value is required"
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
unless azure_cli_authenticated?
|
|
294
|
+
raise "Azure CLI is not authenticated. Run 'az login' first"
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
puts " Setting secret '#{secret_name}' in Key Vault: #{kv_name}".colorize(:cyan)
|
|
298
|
+
|
|
299
|
+
# Build Azure CLI command
|
|
300
|
+
cmd = [
|
|
301
|
+
"az keyvault secret set",
|
|
302
|
+
"--vault-name '#{kv_name}'",
|
|
303
|
+
"--name '#{secret_name}'",
|
|
304
|
+
"--value '#{secret_value}'",
|
|
305
|
+
"2>&1",
|
|
306
|
+
].join(" ")
|
|
307
|
+
|
|
308
|
+
output = `#{cmd}`
|
|
309
|
+
exit_code = $?.exitstatus
|
|
310
|
+
|
|
311
|
+
if exit_code != 0
|
|
312
|
+
puts " Error: Failed to set secret".colorize(:red)
|
|
313
|
+
puts " #{output}".colorize(:red)
|
|
314
|
+
return false
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
puts " Successfully set secret '#{secret_name}' in Key Vault".colorize(:green)
|
|
318
|
+
true
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "azure_key_vault"
|
|
4
|
+
|
|
5
|
+
module Makit
|
|
6
|
+
module Secrets
|
|
7
|
+
# Azure Key Vault adapter that implements the LocalSecrets interface
|
|
8
|
+
# Uses Azure CLI to store and retrieve individual secrets
|
|
9
|
+
class AzureSecrets
|
|
10
|
+
def initialize(keyvault_name: nil, secret_prefix: nil)
|
|
11
|
+
@keyvault_name = keyvault_name || AzureKeyVault.keyvault_name
|
|
12
|
+
@secret_prefix = secret_prefix || AzureKeyVault.secret_prefix
|
|
13
|
+
raise "Azure Key Vault name is required" if @keyvault_name.nil? || @keyvault_name.empty?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def add(key, value)
|
|
17
|
+
set(key, value)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def remove(key)
|
|
21
|
+
secret_name = build_secret_name(key)
|
|
22
|
+
return true unless secret_exists?(secret_name)
|
|
23
|
+
|
|
24
|
+
unless AzureKeyVault.azure_cli_authenticated?
|
|
25
|
+
raise "Azure CLI is not authenticated. Run 'az login' first"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
cmd = [
|
|
29
|
+
"az keyvault secret delete",
|
|
30
|
+
"--vault-name '#{@keyvault_name}'",
|
|
31
|
+
"--name '#{secret_name}'",
|
|
32
|
+
"2>&1"
|
|
33
|
+
].join(" ")
|
|
34
|
+
|
|
35
|
+
output = `#{cmd}`
|
|
36
|
+
exit_code = $?.exitstatus
|
|
37
|
+
|
|
38
|
+
if exit_code != 0
|
|
39
|
+
raise "Failed to delete secret '#{secret_name}': #{output}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def has_key?(key)
|
|
46
|
+
secret_name = build_secret_name(key)
|
|
47
|
+
secret_exists?(secret_name)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def get(key)
|
|
51
|
+
secret_name = build_secret_name(key)
|
|
52
|
+
return nil unless secret_exists?(secret_name)
|
|
53
|
+
|
|
54
|
+
unless AzureKeyVault.azure_cli_authenticated?
|
|
55
|
+
raise "Azure CLI is not authenticated. Run 'az login' first"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
cmd = [
|
|
59
|
+
"az keyvault secret show",
|
|
60
|
+
"--vault-name '#{@keyvault_name}'",
|
|
61
|
+
"--name '#{secret_name}'",
|
|
62
|
+
"--query value",
|
|
63
|
+
"-o tsv",
|
|
64
|
+
"2>&1"
|
|
65
|
+
].join(" ")
|
|
66
|
+
|
|
67
|
+
output = `#{cmd}`.strip
|
|
68
|
+
exit_code = $?.exitstatus
|
|
69
|
+
|
|
70
|
+
if exit_code != 0
|
|
71
|
+
nil
|
|
72
|
+
else
|
|
73
|
+
output
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def set(key, value)
|
|
78
|
+
secret_name = build_secret_name(key)
|
|
79
|
+
AzureKeyVault.set(secret_name, value.to_s, keyvault_name: @keyvault_name)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def get_secrets_filename
|
|
83
|
+
"#{@keyvault_name}/secrets"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def get_secrets_hash
|
|
87
|
+
# Load all secrets from Azure Key Vault
|
|
88
|
+
# This is a simplified version - in practice, you might want to list all secrets
|
|
89
|
+
# For now, we'll return an empty hash and let individual get() calls retrieve secrets
|
|
90
|
+
{}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def save_secrets_hash(hash)
|
|
94
|
+
# Save all secrets from hash to Azure Key Vault
|
|
95
|
+
hash.each do |key, value|
|
|
96
|
+
set(key, value)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def info
|
|
101
|
+
unless AzureKeyVault.azure_cli_authenticated?
|
|
102
|
+
puts " Azure CLI is not authenticated. Run 'az login' first"
|
|
103
|
+
return
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
cmd = [
|
|
107
|
+
"az keyvault secret list",
|
|
108
|
+
"--vault-name '#{@keyvault_name}'",
|
|
109
|
+
"--query '[].name'",
|
|
110
|
+
"-o tsv",
|
|
111
|
+
"2>&1"
|
|
112
|
+
].join(" ")
|
|
113
|
+
|
|
114
|
+
output = `#{cmd}`.strip
|
|
115
|
+
exit_code = $?.exitstatus
|
|
116
|
+
|
|
117
|
+
if exit_code != 0
|
|
118
|
+
# Check if it's a permission error
|
|
119
|
+
if output.include?("Forbidden") || output.include?("ForbiddenByRbac")
|
|
120
|
+
puts " Error: Insufficient permissions to list secrets from Azure Key Vault"
|
|
121
|
+
puts " The authenticated identity does not have the required RBAC permissions."
|
|
122
|
+
puts " Required permission: 'Microsoft.KeyVault/vaults/secrets/readMetadata/action'"
|
|
123
|
+
puts " Required role: 'Key Vault Secrets User' or 'Key Vault Secrets Officer'"
|
|
124
|
+
puts " Vault: #{@keyvault_name}"
|
|
125
|
+
puts ""
|
|
126
|
+
puts " To fix this, ask your Azure administrator to grant you one of these roles:"
|
|
127
|
+
puts " - Key Vault Secrets User (read-only access to secret names and values)"
|
|
128
|
+
puts " - Key Vault Secrets Officer (full access to secrets)"
|
|
129
|
+
else
|
|
130
|
+
puts " Error: Failed to list secrets from Azure Key Vault"
|
|
131
|
+
puts " #{output}"
|
|
132
|
+
end
|
|
133
|
+
return
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
secret_names = output.split("\n").map(&:strip).reject(&:empty?)
|
|
137
|
+
|
|
138
|
+
if @secret_prefix
|
|
139
|
+
# Filter secrets that match the prefix and remove the prefix
|
|
140
|
+
filtered_secrets = secret_names.select { |name| name.start_with?("#{@secret_prefix}-") }
|
|
141
|
+
.map { |name| name.sub(/^#{Regexp.escape(@secret_prefix)}-/, "") }
|
|
142
|
+
else
|
|
143
|
+
filtered_secrets = secret_names
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if filtered_secrets.empty?
|
|
147
|
+
puts " No secrets found"
|
|
148
|
+
else
|
|
149
|
+
puts " Available secrets (#{filtered_secrets.count}):"
|
|
150
|
+
filtered_secrets.sort.each do |key|
|
|
151
|
+
puts " - #{key}"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
def build_secret_name(key)
|
|
159
|
+
if @secret_prefix
|
|
160
|
+
"#{@secret_prefix}-#{key}"
|
|
161
|
+
else
|
|
162
|
+
key.to_s
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def secret_exists?(secret_name)
|
|
167
|
+
unless AzureKeyVault.azure_cli_authenticated?
|
|
168
|
+
return false
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
cmd = [
|
|
172
|
+
"az keyvault secret show",
|
|
173
|
+
"--vault-name '#{@keyvault_name}'",
|
|
174
|
+
"--name '#{secret_name}'",
|
|
175
|
+
"2>&1"
|
|
176
|
+
].join(" ")
|
|
177
|
+
|
|
178
|
+
system("#{cmd} > /dev/null 2>&1")
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "../directories" unless defined?(Makit::Directories)
|
|
5
|
+
|
|
6
|
+
module Makit
|
|
7
|
+
module Secrets
|
|
8
|
+
class LocalSecrets
|
|
9
|
+
def add(key, value)
|
|
10
|
+
secrets_hash = get_secrets_hash
|
|
11
|
+
secrets_hash[key] = value
|
|
12
|
+
save_secrets_hash(secrets_hash)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def remove(key)
|
|
16
|
+
secrets_hash = get_secrets_hash
|
|
17
|
+
secrets_hash.delete(key)
|
|
18
|
+
save_secrets_hash(secrets_hash)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def has_key?(key)
|
|
22
|
+
secrets_hash = get_secrets_hash
|
|
23
|
+
secrets_hash.key?(key)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def get(key)
|
|
27
|
+
secrets_hash = get_secrets_hash
|
|
28
|
+
secrets_hash[key]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def set(key, value)
|
|
32
|
+
secrets_hash = get_secrets_hash
|
|
33
|
+
secrets_hash[key] = value
|
|
34
|
+
save_secrets_hash(secrets_hash)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def get_secrets_filename
|
|
38
|
+
"#{Makit::Directories::ROOT}/secrets.json"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def get_secrets_hash
|
|
42
|
+
secrets_file = get_secrets_filename
|
|
43
|
+
return {} unless File.exist?(secrets_file)
|
|
44
|
+
|
|
45
|
+
text = File.read(secrets_file).strip
|
|
46
|
+
return {} if text.empty?
|
|
47
|
+
|
|
48
|
+
JSON.parse(text)
|
|
49
|
+
rescue JSON::ParserError
|
|
50
|
+
{}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def save_secrets_hash(hash)
|
|
54
|
+
secrets_file = get_secrets_filename
|
|
55
|
+
# pretty print the hash
|
|
56
|
+
File.open(secrets_file, "w") { |f| f.puts JSON.pretty_generate(hash) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def info
|
|
60
|
+
secrets_hash = get_secrets_hash
|
|
61
|
+
if secrets_hash.empty?
|
|
62
|
+
puts " No secrets found"
|
|
63
|
+
else
|
|
64
|
+
puts " Available secrets (#{secrets_hash.keys.count}):"
|
|
65
|
+
secrets_hash.keys.sort.each do |key|
|
|
66
|
+
puts " - #{key}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "local_secrets"
|
|
4
|
+
|
|
5
|
+
module Makit
|
|
6
|
+
module Secrets
|
|
7
|
+
# Manager class for secrets management
|
|
8
|
+
# Uses AzureSecrets if Azure Key Vault environment variables are set,
|
|
9
|
+
# otherwise falls back to LocalSecrets
|
|
10
|
+
class SecretsManager
|
|
11
|
+
def initialize
|
|
12
|
+
@secrets_backend = choose_backend
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def choose_backend
|
|
18
|
+
# Check if Azure Key Vault environment variables are set
|
|
19
|
+
if azure_keyvault_configured?
|
|
20
|
+
require_relative "azure_secrets"
|
|
21
|
+
AzureSecrets.new
|
|
22
|
+
else
|
|
23
|
+
LocalSecrets.new
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def azure_keyvault_configured?
|
|
28
|
+
# Check for Azure Key Vault configuration
|
|
29
|
+
# Requires both AZURE_SERVICE_PRINCIPAL_APP_ID and AZURE_KEYVAULT_NAME
|
|
30
|
+
ENV["AZURE_SERVICE_PRINCIPAL_APP_ID"] && !ENV["AZURE_SERVICE_PRINCIPAL_APP_ID"].empty? &&
|
|
31
|
+
ENV["AZURE_KEYVAULT_NAME"] && !ENV["AZURE_KEYVAULT_NAME"].empty?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
public
|
|
35
|
+
|
|
36
|
+
def add(key, value)
|
|
37
|
+
@secrets_backend.add(key, value)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def remove(key)
|
|
41
|
+
@secrets_backend.remove(key)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def has_key?(key)
|
|
45
|
+
@secrets_backend.has_key?(key)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def key?(key)
|
|
49
|
+
@secrets_backend.has_key?(key)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def get(key)
|
|
53
|
+
@secrets_backend.get(key)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def set(key, value)
|
|
57
|
+
@secrets_backend.set(key, value)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def get_secrets_filename
|
|
61
|
+
@secrets_backend.get_secrets_filename
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def get_secrets_hash
|
|
65
|
+
@secrets_backend.get_secrets_hash
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def save_secrets_hash(hash)
|
|
69
|
+
@secrets_backend.save_secrets_hash(hash)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def info
|
|
73
|
+
backend_name = backend_type
|
|
74
|
+
puts " Backend: #{backend_name}"
|
|
75
|
+
|
|
76
|
+
backend_class_name = @secrets_backend.class.name
|
|
77
|
+
if backend_class_name == "Makit::Secrets::AzureSecrets"
|
|
78
|
+
puts " Key Vault: #{@secrets_backend.instance_variable_get(:@keyvault_name)}"
|
|
79
|
+
secret_name = Makit::Secrets::AzureKeyVault.secret_name
|
|
80
|
+
puts " Secret Name: #{secret_name}"
|
|
81
|
+
prefix = @secrets_backend.instance_variable_get(:@secret_prefix)
|
|
82
|
+
puts " Secret Prefix: #{prefix}" if prefix
|
|
83
|
+
elsif backend_class_name == "Makit::Secrets::LocalSecrets"
|
|
84
|
+
puts " Secrets File: #{@secrets_backend.get_secrets_filename}"
|
|
85
|
+
end
|
|
86
|
+
puts ""
|
|
87
|
+
@secrets_backend.info
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def backend_type
|
|
93
|
+
backend_class_name = @secrets_backend.class.name
|
|
94
|
+
case backend_class_name
|
|
95
|
+
when "Makit::Secrets::AzureSecrets"
|
|
96
|
+
"Azure Key Vault"
|
|
97
|
+
when "Makit::Secrets::LocalSecrets"
|
|
98
|
+
"LocalSecrets"
|
|
99
|
+
else
|
|
100
|
+
backend_class_name.split("::").last
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|