Skip to content

Commit

Permalink
refactor(azure/get-or-create-secret-in-key-vault.md): encapsulate Key…
Browse files Browse the repository at this point in the history
… Vault operations in classes

The code in the documentation has been refactored to encapsulate the Azure Key Vault operations within two classes, `AzureSecretClient` and `KeyVaultManipulator`. This change improves the organization of the code, making it more modular and easier to maintain. The `AzureSecretClient` class is responsible for creating a `SecretClient` instance, while the `KeyVaultManipulator` class provides methods for manipulating environment variables within the Key Vault secrets. This refactor also includes the addition of methods for deleting environment variables and updating the usage examples to reflect the new class-based approach.
  • Loading branch information
mabdullahabid committed May 10, 2024
1 parent b5ce3c6 commit 491cf1b
Showing 1 changed file with 178 additions and 75 deletions.
253 changes: 178 additions & 75 deletions azure/get-or-create-secret-in-key-vault.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Get or Create Secrets in Azure Key Vault using Python

This needs to be updated to the latest version.

I was brought in to a project where a large Django monolith was being moved
from Heroku to Azure. It was decided that we'll be using Azure Kubernetes
Service (AKS) for hosting the Django application. The application had a lot of
Expand Down Expand Up @@ -34,96 +32,201 @@ import re
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential

key_vault_name = "kv-testapp-dev-eastus-001"
key_vault_uri = f"https://{key_vault_name}.vault.azure.net"

credential = DefaultAzureCredential()
client = SecretClient(vault_url=key_vault_uri, credential=credential)

def get_env_var_value(env_string, variable_name):
# Define the regex pattern to find the exact environment variable and its value
# It looks for the variable name followed by an equal sign and then captures everything between the quotes
pattern = rf'{variable_name}="([^"]*)"'
class AzureSecretClient:
def __new__(cls, key_vault_name):
key_vault_uri = f"https://{key_vault_name}.vault.azure.net"
credential = DefaultAzureCredential()
client = SecretClient(vault_url=key_vault_uri, credential=credential)
return client


class KeyVaultManipulator:
def __init__(self, client):
self.client = client

def get_env_var_value(self, env_string, variable_name):
# Define the regex pattern to find the exact environment variable and its value
# It looks for the variable name followed by an equal sign and then captures everything between the quotes
# \b around the variable_name ensures it matches as a whole word and not as part of another variable
pattern = rf'\b{variable_name}\b="([^"]*)"'

# Use the search function from the re module to find the first match
match = re.search(pattern, env_string)

# If a match is found, return the captured group (the value), otherwise return None
env_var_value = match.group(1) if match else None
if env_var_value:
print(f"{variable_name}: {env_var_value}")
return env_var_value

# Use the search function from the re module to find the first match
match = re.search(pattern, env_string)
def create_env_var(self, env_string, variable_name, new_value):
# Check if the variable already exists
current_value = self.get_env_var_value(env_string, variable_name)
if current_value is None:
print(f"Unable to find {variable_name} in envdata. Creating...")
# If it does not exist, append the new variable in the correct format
if env_string and not env_string.endswith('\n'):
env_string += '\n'
env_string += f'export {variable_name}="{new_value}"\n'
else:
print(f"Found {variable_name} in envdata. Updating from {current_value} to {new_value}")
env_string = self.update_env_var(env_string, variable_name, new_value)

return env_string

# If a match is found, return the captured group (the value), otherwise return None
return match.group(1) if match else None

def create_env_var(env_string, variable_name, new_value):
# Check if the variable already exists
current_value = get_env_var_value(env_string, variable_name)
if current_value is None:
print(f"Unable to find {variable_name} in envdata. Creating...")
# If it does not exist, append the new variable in the correct format
if env_string and not env_string.endswith('\n'):
env_string += '\n'
env_string += f'export {variable_name}="{new_value}"\n'
else:
print(f"Found {variable_name} in envdata. Updating from {current_value} to {new_value}")
env_string = update_env_var(env_string, variable_name, new_value)

return env_string


def update_env_var(env_string, variable_name, new_value):
# Define the regex pattern to find the exact environment variable with double quotes
pattern = rf'({variable_name}=)"[^"]*"'
def update_env_var(self, env_string, variable_name, new_value):
# Define the regex pattern to find the exact environment variable with double quotes
# \b around the variable_name ensures it matches as a whole word and not as part of another variable
pattern = rf'(\b{variable_name}\b=)"[^"]*"'

# Replace the old value with the new one using the sub function from the re module
# The replacement string will reuse the matched first group (variable_name=") and put the new value in double quotes
updated_env_string = re.sub(pattern, rf'\1"{new_value}"', env_string, count=1)

return updated_env_string


# Replace the old value with the new one using the sub function from the re module
# The replacement string will reuse the matched first group (variable_name=") and put the new value in double quotes
updated_env_string = re.sub(pattern, rf'\1"{new_value}"', env_string, count=1)
def delete_env_var(self, env_string, variable_name):
# Define the regex pattern to match the full line containing the environment variable
# \b around the variable_name ensures it matches as a whole word and not as part of another variable
pattern = rf'^export \b{variable_name}\b="[^"]*"\n?'

# Use the sub function from the re module to replace the matched line with an empty string
updated_env_string = re.sub(pattern, '', env_string, flags=re.MULTILINE)

return updated_env_string

return updated_env_string


def count_env_var(env_string):
# Define the regex pattern to match lines that start with 'export', followed by any characters, an equal sign, and quoted value
pattern = r'export \w+="[^"]*"'

# Use the findall function from the re module to find all matches in the string
matches = re.findall(pattern, env_string)
def count_env_var(self, env_string):
# Define the regex pattern to match lines that start with 'export', followed by any characters, an equal sign, and quoted value
pattern = r'export \w+="[^"]*"'

# Use the findall function from the re module to find all matches in the string
matches = re.findall(pattern, env_string)

# The length of the list of matches will be the count of environment variables
return len(matches)

# The length of the list of matches will be the count of environment variables
return len(matches)

def get_envdata(env_key):
testenvdata1 = client.get_secret("testenvdata1")
testenvdata2 = client.get_secret("testenvdata2")
testenvdata3 = client.get_secret("testenvdata3")
def get_envdata(self, env_key):
# Retrieve environment data from all key vault secrets
cbenvdata1 = self.client.get_secret("cbenvdata1")
cbenvdata2 = self.client.get_secret("cbenvdata2")
cbenvdata3 = self.client.get_secret("cbenvdata3")

# Print counts for debugging
print(f"len(cbenvdata1): {self.count_env_var(cbenvdata1.value)}")
print(f"len(cbenvdata2): {self.count_env_var(cbenvdata2.value)}")
print(f"len(cbenvdata3): {self.count_env_var(cbenvdata3.value)}")

# Initialize envdata to None
envdata = None

print(count_env_var(testenvdata1.value))
print(count_env_var(testenvdata2.value))
print(count_env_var(testenvdata2.value))
# Check each cbenvdata's value to find an exact match using regex with word boundaries
search_pattern = rf'\b{env_key}\b='

if env_key in testenvdata1.value:
return testenvdata1
elif env_key in testenvdata2.value:
return testenvdata2
elif env_key in testenvdata3.value:
return testenvdata3
if re.search(search_pattern, cbenvdata1.value):
envdata = cbenvdata1
elif re.search(search_pattern, cbenvdata2.value):
envdata = cbenvdata2
elif re.search(search_pattern, cbenvdata3.value):
envdata = cbenvdata3

# If envdata is still None, raise an error
if not envdata:
raise TypeError(f"Unable to find {env_key} in envdata.")

# Print out where the key was found
print(f"Key Vault: {envdata}")
print(f"Found {env_key} in {envdata.name}")

return envdata

def set_envdata(envdata, env_key, env_value):
# Use create_env_var to handle both creation and update of the variable
updated_value = create_env_var(envdata.value, env_key, env_value)
# Update the secret in Azure Key Vault
return client.set_secret(envdata.name, updated_value)

def set_envdata(self, envdata, env_key, env_value):
# Use create_env_var to handle both creation and update of the variable
updated_value = self.create_env_var(envdata.value, env_key, env_value)
# Update the secret in Azure Key Vault
return self.client.set_secret(envdata.name, updated_value)


def unset_envdata(self, envdata, env_key):
# Use delete_env_var to handle deletion of the variable
updated_value = self.delete_env_var(envdata.value, env_key)
# Update the secret in Azure Key Vault
return self.client.set_secret(envdata.name, updated_value)
```

Usage

Getting the environment data and value:
Get environment variable

```python
envvar_key = "DEBUG"
envdata = get_envdata(envvar_key)
print(envdata)
envvar_val = get_env_var_value(envdata.value, envvar_key)
print(envvar_val)
key_vault_list = [
"key-vault-1",
"key-vault-2",
]
for vault in key_vault_list:
client = AzureSecretClient(vault)
manipulator = KeyVaultManipulator(client=client)

envvar_key = "ENV_VAR_KEY"

print(vault)
print("--------------------------")
try:
envdata = manipulator.get_envdata(envvar_key)
envvar_val = manipulator.get_env_var_value(envdata.value, envvar_key)
except TypeError as e:
print(e)
finally:
print("--------------------------")
```

Setting the environment data and value:
Set environment variable

```python
set_envdata(envdata, envvar_key, "NEW_VALUE")
key_vault_list = [
"key-vault-1",
"key-vault-2",
]
for vault in key_vault_list:
client = AzureSecretClient(vault)
manipulator = KeyVaultManipulator(client=client)

envvar_key = "ENV_VAR_KEY"

print(vault)
print("--------------------------")
try:
envdata = manipulator.get_envdata(envvar_key)
manipulator.set_envdata(envdata, envvar_key, "ENV_VAR_VALUE")
except TypeError as e:
print(e)
finally:
print("--------------------------")
```

Unset environment variable

```python
key_vault_list = [
"key-vault-1",
"key-vault-2",
]
for vault in key_vault_list:
client = AzureSecretClient(vault)
manipulator = KeyVaultManipulator(client=client)

envvar_key = "ENV_VAR_KEY"

print(vault)
print("--------------------------")
try:
envdata = manipulator.get_envdata(envvar_key)
manipulator.unset_envdata(envdata, envvar_key)
except TypeError as e:
print(e)
finally:
print("--------------------------")
```

0 comments on commit 491cf1b

Please sign in to comment.