First commit
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
"""
|
||||
Azure Key Vault Secret Manager
|
||||
|
||||
Main application entry point.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import customtkinter as ctk
|
||||
from tkinter import messagebox
|
||||
import sys
|
||||
|
||||
# Import modules
|
||||
import config
|
||||
from auth.graph_authenticator import GraphAuthenticator
|
||||
from auth.azure_authenticator import AzureAuthenticator
|
||||
from services.app_registration_service import AppRegistrationService
|
||||
from services.secret_service import SecretService
|
||||
from services.keyvault_service import KeyVaultService
|
||||
from ui.main_window import MainWindow
|
||||
from utils.logger import setup_logger
|
||||
|
||||
# Set appearance
|
||||
ctk.set_appearance_mode("system")
|
||||
ctk.set_default_color_theme("blue")
|
||||
|
||||
|
||||
class Application:
|
||||
"""Main application class."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the application."""
|
||||
self.logger = setup_logger()
|
||||
|
||||
# Initialize async worker FIRST (before any async operations)
|
||||
from utils.async_worker import AsyncWorker
|
||||
self.async_worker = AsyncWorker()
|
||||
self.async_worker.start()
|
||||
|
||||
# Authentication
|
||||
self.graph_auth = GraphAuthenticator()
|
||||
self.azure_auth = AzureAuthenticator()
|
||||
|
||||
# Services (will be initialized after authentication)
|
||||
self.app_service = None
|
||||
self.secret_service = None
|
||||
self.vault_service = None
|
||||
|
||||
# Data
|
||||
self.apps = []
|
||||
self.vaults = []
|
||||
self.subscriptions = []
|
||||
self.selected_app = None
|
||||
self.selected_subscription = None
|
||||
|
||||
# Create main window
|
||||
self.window = MainWindow(
|
||||
on_connect=self.handle_connect,
|
||||
on_subscription_selected=self.handle_subscription_selected,
|
||||
on_app_selected=self.handle_app_selected,
|
||||
on_generate_secret=self.handle_generate_secret,
|
||||
on_generate_another=self.handle_generate_another
|
||||
)
|
||||
|
||||
def handle_connect(self):
|
||||
"""Handle authentication button click."""
|
||||
self.window.set_connecting()
|
||||
|
||||
# Submit to async worker instead of creating new loop
|
||||
def on_complete(future):
|
||||
"""Handle authentication completion in main thread."""
|
||||
try:
|
||||
future.result() # Raises if authentication failed
|
||||
except Exception as e:
|
||||
self.logger.error(f"Authentication failed: {str(e)}")
|
||||
self.window.after(0, lambda: messagebox.showerror(
|
||||
"Authentication Error",
|
||||
f"Failed to authenticate:\n\n{str(e)}\n\nPlease try again."
|
||||
))
|
||||
self.window.after(0, self.window.enable_connect_button)
|
||||
|
||||
future = self.async_worker.submit(self._authenticate())
|
||||
future.add_done_callback(on_complete)
|
||||
|
||||
async def _authenticate(self):
|
||||
"""Perform authentication."""
|
||||
try:
|
||||
self.logger.info("Starting authentication...")
|
||||
|
||||
# Authenticate to Microsoft Graph
|
||||
await self.graph_auth.authenticate()
|
||||
|
||||
# Authenticate to Azure (reuse credential)
|
||||
credential = self.graph_auth.get_credential()
|
||||
self.azure_auth.authenticate(credential)
|
||||
|
||||
# Initialize Graph services
|
||||
graph_client = self.graph_auth.get_client()
|
||||
self.app_service = AppRegistrationService(graph_client)
|
||||
self.secret_service = SecretService(graph_client)
|
||||
|
||||
# Update UI
|
||||
self.window.set_authenticated(True)
|
||||
|
||||
self.logger.info("Authentication successful")
|
||||
|
||||
# Load subscriptions (should use SSO from Graph auth above)
|
||||
await self._load_subscriptions()
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Authentication failed: {str(e)}")
|
||||
messagebox.showerror(
|
||||
"Authentication Error",
|
||||
f"Failed to authenticate:\n\n{str(e)}\n\nPlease try again."
|
||||
)
|
||||
self.window.enable_connect_button()
|
||||
|
||||
async def _load_subscriptions(self):
|
||||
"""Load Azure subscriptions."""
|
||||
try:
|
||||
self.window.set_loading_subscriptions(True)
|
||||
self.logger.info("Loading subscriptions...")
|
||||
|
||||
# Get subscriptions from authenticator
|
||||
self.subscriptions = self.azure_auth.get_subscriptions()
|
||||
self.window.set_subscriptions(self.subscriptions)
|
||||
self.window.set_loading_subscriptions(False)
|
||||
|
||||
self.logger.info(f"Loaded {len(self.subscriptions)} subscription(s)")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to load subscriptions: {str(e)}")
|
||||
messagebox.showerror(
|
||||
"Load Error",
|
||||
f"Failed to load subscriptions:\n\n{str(e)}"
|
||||
)
|
||||
|
||||
def handle_subscription_selected(self, subscription):
|
||||
"""Handle subscription selection."""
|
||||
self.selected_subscription = subscription
|
||||
self.logger.info(f"Selected subscription: {subscription['name']} ({subscription['id']})")
|
||||
|
||||
# Set the subscription in the azure authenticator
|
||||
self.azure_auth.set_subscription(subscription['id'])
|
||||
|
||||
# Initialize KeyVault service with the selected subscription
|
||||
self.vault_service = KeyVaultService(
|
||||
self.azure_auth.get_credential(),
|
||||
self.azure_auth.get_subscription_id()
|
||||
)
|
||||
|
||||
# Enable UI elements
|
||||
self.window.set_subscription_selected()
|
||||
|
||||
# Submit data loading to async worker (no threading.Thread needed)
|
||||
def on_complete(future):
|
||||
"""Handle data loading completion."""
|
||||
try:
|
||||
future.result()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to load data: {str(e)}")
|
||||
self.window.after(0, lambda: messagebox.showerror(
|
||||
"Load Error",
|
||||
f"Failed to load data:\n\n{str(e)}"
|
||||
))
|
||||
|
||||
future = self.async_worker.submit(self._load_data())
|
||||
future.add_done_callback(on_complete)
|
||||
|
||||
async def _load_data(self):
|
||||
"""Load app registrations and Key Vaults."""
|
||||
try:
|
||||
# Load apps
|
||||
self.window.set_loading_apps(True)
|
||||
self.logger.info("Loading app registrations...")
|
||||
self.apps = await self.app_service.list_applications()
|
||||
self.window.set_apps(self.apps)
|
||||
self.window.set_loading_apps(False)
|
||||
self.logger.info(f"Loaded {len(self.apps)} app registrations")
|
||||
|
||||
# Load Key Vaults
|
||||
self.window.set_loading_vaults(True)
|
||||
self.logger.info("Loading Key Vaults...")
|
||||
self.vaults = self.vault_service.list_keyvaults()
|
||||
self.window.set_vaults(self.vaults)
|
||||
self.window.set_loading_vaults(False)
|
||||
self.logger.info(f"Loaded {len(self.vaults)} Key Vaults")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to load data: {str(e)}")
|
||||
messagebox.showerror(
|
||||
"Load Error",
|
||||
f"Failed to load data:\n\n{str(e)}"
|
||||
)
|
||||
|
||||
def handle_app_selected(self, app):
|
||||
"""Handle app selection."""
|
||||
self.selected_app = app
|
||||
self.logger.info(f"Selected app: {app['display_name']}")
|
||||
|
||||
def handle_generate_secret(self, description: str, vault: dict, remove_old: bool):
|
||||
"""Handle secret generation."""
|
||||
# Submit to async worker instead of creating new loop
|
||||
def on_complete(future):
|
||||
"""Handle generation completion."""
|
||||
try:
|
||||
future.result()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to generate secret: {str(e)}")
|
||||
self.window.after(0, lambda: self.window.set_generating(False))
|
||||
self.window.after(0, lambda: messagebox.showerror(
|
||||
"Generation Error",
|
||||
f"Failed to generate secret:\n\n{str(e)}"
|
||||
))
|
||||
|
||||
future = self.async_worker.submit(self._generate_secret(description, vault, remove_old))
|
||||
future.add_done_callback(on_complete)
|
||||
|
||||
async def _generate_secret(self, description: str, vault: dict, remove_old: bool):
|
||||
"""Generate secret asynchronously."""
|
||||
try:
|
||||
# Validate inputs
|
||||
app = self.window.get_selected_app()
|
||||
if not app:
|
||||
messagebox.showwarning("Validation Error", "Please select an app registration.")
|
||||
return
|
||||
|
||||
if not description:
|
||||
messagebox.showwarning("Validation Error", "Please enter a secret description.")
|
||||
return
|
||||
|
||||
if not vault:
|
||||
messagebox.showwarning("Validation Error", "Please select a Key Vault.")
|
||||
return
|
||||
|
||||
self.window.set_generating(True)
|
||||
self.logger.info(f"Generating secret for app: {app['display_name']}")
|
||||
|
||||
# Create secret
|
||||
secret_result = await self.secret_service.create_secret(
|
||||
app_object_id=app['id'],
|
||||
description=description
|
||||
)
|
||||
|
||||
self.logger.info(f"Secret created successfully. Key ID: {secret_result['key_id']}")
|
||||
|
||||
# Remove old secrets if requested
|
||||
removed_count = 0
|
||||
if remove_old:
|
||||
self.logger.info("Removing old secrets...")
|
||||
removed_count = await self.secret_service.remove_old_secrets(
|
||||
app_object_id=app['id'],
|
||||
keep_key_id=secret_result['key_id']
|
||||
)
|
||||
self.logger.info(f"Removed {removed_count} old secret(s)")
|
||||
|
||||
# Store in Key Vault
|
||||
self.logger.info(f"Storing secret in Key Vault: {vault['name']}")
|
||||
sanitized_name = self.vault_service.store_secret(
|
||||
vault_name=vault['name'],
|
||||
secret_name=app['display_name'],
|
||||
secret_value=secret_result['secret_text'],
|
||||
description=description,
|
||||
secret_id=secret_result['key_id'],
|
||||
expires=secret_result['end_datetime']
|
||||
)
|
||||
|
||||
self.logger.info(f"Secret stored successfully: {sanitized_name}")
|
||||
|
||||
# Show result in the main window (no popup needed - result frame shows all info)
|
||||
self.window.show_result(
|
||||
secret_name=sanitized_name,
|
||||
vault_name=vault['name'],
|
||||
secret_value=secret_result['secret_text'],
|
||||
removed_count=removed_count
|
||||
)
|
||||
|
||||
# Reset generating state
|
||||
self.window.set_generating(False)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to generate secret: {str(e)}")
|
||||
self.window.set_generating(False)
|
||||
messagebox.showerror(
|
||||
"Generation Error",
|
||||
f"Failed to generate secret:\n\n{str(e)}"
|
||||
)
|
||||
|
||||
def handle_generate_another(self):
|
||||
"""Handle generate another secret."""
|
||||
self.window.reset_form()
|
||||
self.logger.info("Form reset for another secret generation")
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup resources on application exit."""
|
||||
self.logger.info("Shutting down application...")
|
||||
if hasattr(self, 'async_worker'):
|
||||
self.async_worker.stop()
|
||||
|
||||
def _on_closing(self):
|
||||
"""Handle window close event."""
|
||||
self.cleanup()
|
||||
self.window.destroy()
|
||||
|
||||
def run(self):
|
||||
"""Run the application."""
|
||||
self.logger.info("Starting Azure Key Vault Secret Manager")
|
||||
|
||||
# Register cleanup on window close
|
||||
self.window.protocol("WM_DELETE_WINDOW", self._on_closing)
|
||||
|
||||
self.window.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = Application()
|
||||
app.run()
|
||||
Reference in New Issue
Block a user