Class: Makit::Secrets::AzureKeyVault

Inherits:
Object
  • Object
show all
Defined in:
lib/makit/secrets/azure_key_vault.rb

Overview

Azure Key Vault operations

Constant Summary collapse

REQUIRED_VARS =

Required environment variables

[
  "AZURE_STORAGE_ACCOUNT",
  "AZURE_STORAGE_ACCOUNT_KEY",
].freeze
OPTIONAL_VARS =

Optional environment variables

[
  "AZURE_CDN_RESOURCE_GROUP",
  "AZURE_CDN_PROFILE_NAME",
  "AZURE_CDN_ENDPOINT_NAME",
  "GITLAB_NUGET_TOKEN",
  "GITLAB_USERNAME",
].freeze
ALL_VARS =

All environment variablesazure_key_vault.r

(REQUIRED_VARS + OPTIONAL_VARS).freeze

Class Method Summary collapse

Class Method Details

.azure_cli_authenticated?Boolean

Check if Azure CLI is authenticated

Returns:

  • (Boolean)


31
32
33
34
# File 'lib/makit/secrets/azure_key_vault.rb', line 31

def self.azure_cli_authenticated?
  return false unless system("which az > /dev/null 2>&1")
  system("az account show > /dev/null 2>&1")
end

.generate_secret_name_from_url(url) ⇒ Object

Generate a valid Azure Key Vault secret name from a Git remote URL Azure Key Vault secret names must be 1-127 characters, alphanumeric and hyphens only, cannot start or end with hyphen, and cannot have consecutive hyphens



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/makit/secrets/azure_key_vault.rb', line 78

def self.generate_secret_name_from_url(url)
  # Remove protocol (http://, https://, git@)
  name = url.gsub(/^https?:\/\//, "").gsub(/^git@/, "")
  
  # Remove .git suffix if present
  name = name.gsub(/\.git$/, "")
  
  # Replace invalid characters with hyphens
  name = name.gsub(/[^a-zA-Z0-9\-]/, "-")
  
  # Remove consecutive hyphens
  name = name.gsub(/-+/, "-")
  
  # Remove leading/trailing hyphens
  name = name.gsub(/^-+|-+$/, "")
  
  # Ensure it starts with a letter or number (Azure requirement)
  name = "secret-#{name}" if name.empty? || name.match(/^[^a-zA-Z0-9]/)
  
  # Truncate to 127 characters (Azure Key Vault limit)
  name = name[0, 127]
  
  # Remove trailing hyphen if truncation created one
  name = name.gsub(/-+$/, "")
  
  # Ensure it's not empty
  name = "git-secrets" if name.empty?
  
  name
end

.get_git_remote_urlObject

Get GIT_REMOTE_URL from various sources



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/makit/secrets/azure_key_vault.rb', line 55

def self.get_git_remote_url
  # Try constant first
  return GIT_REMOTE_URL if defined?(GIT_REMOTE_URL) && !GIT_REMOTE_URL.nil? && !GIT_REMOTE_URL.empty?
  
  # Try Git module
  if defined?(Makit::Git) && Makit::Git.git_repo?
    url = Makit::Git.get_remote_url
    return url if url && !url.empty?
  end
  
  # Try project configuration
  if defined?(Makit::Configuration::Project)
    project = Makit::Configuration::Project.default
    url = project.git_remote_url
    return url if url && !url.nil? && !url.empty?
  end
  
  nil
end

.keyvault_nameObject

Get Azure Key Vault name from environment or use default



37
38
39
# File 'lib/makit/secrets/azure_key_vault.rb', line 37

def self.keyvault_name
  ENV["AZURE_KEYVAULT_NAME"] || "louparslow-secrets"
end

.kvenv_available?Boolean

Check if kvenv is available

Returns:

  • (Boolean)


26
27
28
# File 'lib/makit/secrets/azure_key_vault.rb', line 26

def self.kvenv_available?
  system("which kvenv > /dev/null 2>&1")
end

.load(keyvault_name: nil, secret_name: nil) ⇒ Boolean

Load secrets from Azure Key Vault using kvenv

Parameters:

  • keyvault_name (String, nil) (defaults to: nil)

    The Azure Key Vault name (defaults to ENV or “louparslow-secrets”)

  • secret_name (String, nil) (defaults to: nil)

    The secret name in Key Vault (defaults to ENV or “portal-secrets”)

Returns:

  • (Boolean)

    true if secrets were loaded successfully, false otherwise



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/makit/secrets/azure_key_vault.rb', line 215

def self.load(keyvault_name: nil, secret_name: nil)
  # Use provided parameters or fall back to defaults
  kv_name = keyvault_name || self.keyvault_name
  sec_name = secret_name || self.secret_name
  
  unless kvenv_available?
    puts "  Warning: kvenv not installed, secrets will not be loaded".colorize(:yellow)
    return false
  end
  
  # Check if Azure CLI is authenticated (kvenv can use this)
  unless azure_cli_authenticated?
    puts "  Warning: Azure CLI not authenticated, run 'az login' first".colorize(:yellow)
    return false
  end
  
  cache_file = "/tmp/kvenv-#{sec_name}.json"
  
  # Build kvenv command
  cmd_parts = [
    "kvenv cache",
    "--azure",
    "--azure-keyvault-name #{kv_name}",
  ]
  
  if secret_prefix
    cmd_parts << "--secret-prefix #{secret_prefix}"
  else
    cmd_parts << "--secret-name #{sec_name}"
  end
  
  cmd_parts << "--output #{cache_file}"
  
  # Run kvenv to cache secrets
  puts "  Loading secrets from Key Vault: #{kv_name}, Secret: #{sec_name}".colorize(:cyan)
  success = system(cmd_parts.join(" "))
  return false unless success
  return false unless File.exist?(cache_file)
  
  # Load secrets from cache file
  begin
    require "json"
    secrets = JSON.parse(File.read(cache_file))
  
    # Set environment variables (don't override existing ones)
    loaded_count = 0
    secrets.each do |key, value|
      unless ENV[key]
        ENV[key] = value
        loaded_count += 1
      end
    end
  
    puts "  Loaded #{loaded_count} secrets from Azure Key Vault (#{secrets.keys.count} total in secret)".colorize(:green)
    true
  rescue => e
    puts "  Warning: Could not parse secrets from cache: #{e.message}".colorize(:yellow)
    false
  end
end

.secret_nameObject

Get secret name from environment or auto-generate from GIT_REMOTE_URL



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/makit/secrets/azure_key_vault.rb', line 42

def self.secret_name
  return ENV["AZURE_SECRET_NAME"] if ENV["AZURE_SECRET_NAME"] && !ENV["AZURE_SECRET_NAME"].empty?
  
  # Auto-generate from GIT_REMOTE_URL
  git_remote_url = get_git_remote_url
  if git_remote_url && !git_remote_url.empty?
    generate_secret_name_from_url(git_remote_url)
  else
    "portal-secrets"  # Fallback default
  end
end

.secret_prefixObject

Get secret prefix from environment



110
111
112
# File 'lib/makit/secrets/azure_key_vault.rb', line 110

def self.secret_prefix
  ENV["AZURE_SECRET_PREFIX"]
end

.set(secret_name, secret_value, keyvault_name: nil) ⇒ Boolean

Set a secret in Azure Key Vault

Parameters:

  • secret_name (String)

    The name of the secret to set

  • secret_value (String)

    The value of the secret

  • keyvault_name (String, nil) (defaults to: nil)

    The Azure Key Vault name (defaults to ENV or “louparslow-secrets”)

Returns:

  • (Boolean)

    true if secret was set successfully, false otherwise



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/makit/secrets/azure_key_vault.rb', line 281

def self.set(secret_name, secret_value, keyvault_name: nil)
  # Use provided parameter or fall back to default
  kv_name = keyvault_name || self.keyvault_name
  
  if secret_name.nil? || secret_name.empty?
    raise "Secret name is required"
  end
  
  if secret_value.nil? || secret_value.empty?
    raise "Secret value is required"
  end
  
  unless azure_cli_authenticated?
    raise "Azure CLI is not authenticated. Run 'az login' first"
  end
  
  puts "  Setting secret '#{secret_name}' in Key Vault: #{kv_name}".colorize(:cyan)
  
  # Build Azure CLI command
  cmd = [
    "az keyvault secret set",
    "--vault-name '#{kv_name}'",
    "--name '#{secret_name}'",
    "--value '#{secret_value}'",
    "2>&1",
  ].join(" ")
  
  output = `#{cmd}`
  exit_code = $?.exitstatus
  
  if exit_code != 0
    puts "  Error: Failed to set secret".colorize(:red)
    puts "  #{output}".colorize(:red)
    return false
  end
  
  puts "  Successfully set secret '#{secret_name}' in Key Vault".colorize(:green)
  true
end

.setupObject

Setup method: Attempt to initialize environment variables



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/makit/secrets/azure_key_vault.rb', line 115

def self.setup
  puts "Setting up secrets...".colorize(:blue)
  
  # Check if required variables are already set
  missing_required = REQUIRED_VARS.select { |var| ENV[var].nil? || ENV[var].empty? }
  
  if missing_required.empty?
    puts "  All required environment variables are already set".colorize(:green)
    return true
  end
  
  puts "  Missing required variables: #{missing_required.join(", ")}".colorize(:yellow)
  puts "  Attempting to load from Azure Key Vault...".colorize(:yellow)
  
  # Try to load from Azure Key Vault
  if load(keyvault_name: nil, secret_name: nil)
    # Verify again after loading
    still_missing = REQUIRED_VARS.select { |var| ENV[var].nil? || ENV[var].empty? }
    if still_missing.empty?
      puts "  Successfully loaded all required secrets".colorize(:green)
      return true
    else
      puts "  Warning: Still missing required variables: #{still_missing.join(", ")}".colorize(:yellow)
      return false
    end
  else
    puts "  Could not load secrets from Azure Key Vault".colorize(:yellow)
    puts "  Please set environment variables manually or configure Azure Key Vault access".colorize(:yellow)
    return false
  end
end

.statusObject

Get status of all environment variables



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/makit/secrets/azure_key_vault.rb', line 191

def self.status
  puts "Environment Variables Status:".colorize(:blue)
  puts ""
  
  puts "Required Variables:".colorize(:cyan)
  REQUIRED_VARS.each do |var|
    status = ENV[var].nil? || ENV[var].empty? ? "❌ Not set" : "✅ Set"
    color = ENV[var].nil? || ENV[var].empty? ? :red : :green
    puts "  #{var}: #{status}".colorize(color)
  end
  
  puts ""
  puts "Optional Variables:".colorize(:cyan)
  OPTIONAL_VARS.each do |var|
    status = ENV[var].nil? || ENV[var].empty? ? "⚪ Not set" : "✅ Set"
    color = ENV[var].nil? || ENV[var].empty? ? :yellow : :green
    puts "  #{var}: #{status}".colorize(color)
  end
end

.verify(warn_only: true) ⇒ Object

Verify that expected environment variables are set



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/makit/secrets/azure_key_vault.rb', line 148

def self.verify(warn_only: true)
  issues = []
  warnings = []
  
  # Check required variables
  REQUIRED_VARS.each do |var|
    if ENV[var].nil? || ENV[var].empty?
      issues << "Required variable '#{var}' is not set"
    end
  end
  
  # Check optional variables (only warn)
  OPTIONAL_VARS.each do |var|
    if ENV[var].nil? || ENV[var].empty?
      warnings << "Optional variable '#{var}' is not set"
    end
  end
  
  # Report issues
  if issues.any?
    if warn_only
      issues.each { |issue| puts "  ⚠️  #{issue}".colorize(:yellow) }
    else
      issues.each { |issue| puts "#{issue}".colorize(:red) }
    end
  end
  
  if warnings.any?
    warnings.each { |warning| puts "  ℹ️  #{warning}".colorize(:cyan) }
  end
  
  if issues.empty? && warnings.empty?
    puts "  ✅ All environment variables are set".colorize(:green)
    return true
  elsif issues.empty?
    puts "  ✅ All required environment variables are set".colorize(:green)
    return true
  else
    return false
  end
end