InFlask app update and fix
This commit is contained in:
parent
e4ca0ef8bf
commit
214841bdae
@ -0,0 +1,19 @@
|
|||||||
|
from flask import Flask, request, jsonify
|
||||||
|
import logging
|
||||||
|
from .handlers.webhook_handler import handle_payment_webhook
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route('/webhook/vpn', methods=['POST'])
|
||||||
|
def handle_payment():
|
||||||
|
return handle_payment_webhook(request)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0', port=5000)
|
@ -1,55 +1,56 @@
|
|||||||
# app/handlers/webhook_handler.py
|
import tempfile
|
||||||
|
from flask import jsonify
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
sys.path.append(str(Path(__file__).resolve().parent.parent.parent))
|
|
||||||
|
|
||||||
from flask import Flask, request, jsonify
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
import hmac
|
import hmac
|
||||||
import hashlib
|
import hashlib
|
||||||
import yaml
|
import yaml
|
||||||
import traceback
|
import traceback
|
||||||
import tempfile
|
from pathlib import Path
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from app.utils.vault_helper import decrypt_vault_file
|
|
||||||
|
|
||||||
# Load environment variables from .env file
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
# Configure logging
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
# Update paths for new structure
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||||
PLAYBOOK_PATH = BASE_DIR / 'ansible' / 'playbooks' / 'vpn_provision.yml'
|
PLAYBOOK_PATH = BASE_DIR / 'ansible' / 'playbooks' / 'vpn_provision.yml'
|
||||||
VAULT_PATH = BASE_DIR / 'ansible' / 'group_vars' / 'vpn_servers' / 'vault.yml'
|
|
||||||
INVENTORY_PATH = BASE_DIR / 'inventory.ini'
|
|
||||||
|
|
||||||
def get_vault_values():
|
def get_vault_values():
|
||||||
"""Get decrypted values from Ansible vault"""
|
"""Get decrypted values from Ansible vault"""
|
||||||
try:
|
try:
|
||||||
# Use vault helper to decrypt contents
|
vault_pass = os.getenv('ANSIBLE_VAULT_PASSWORD', '')
|
||||||
vault_contents = decrypt_vault_file(VAULT_PATH)
|
if not vault_pass:
|
||||||
vault_data = yaml.safe_load(vault_contents)
|
raise Exception("Vault password not found in environment variables")
|
||||||
|
|
||||||
# Construct full webhook URL
|
with tempfile.NamedTemporaryFile(mode='w', delete=False) as vault_pass_file:
|
||||||
vault_data['webhook_full_url'] = (
|
vault_pass_file.write(vault_pass)
|
||||||
f"{vault_data['btcpay_base_url']}"
|
vault_pass_file.flush()
|
||||||
f"{vault_data['btcpay_webhook_path']}"
|
|
||||||
|
result = subprocess.run(
|
||||||
|
['ansible-vault', 'view', str(BASE_DIR / 'ansible/group_vars/vpn_servers/vault.yml')],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
env={**os.environ, 'ANSIBLE_VAULT_PASSWORD_FILE': vault_pass_file.name}
|
||||||
|
)
|
||||||
|
|
||||||
|
os.unlink(vault_pass_file.name)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception(f"Failed to decrypt vault: {result.stderr}")
|
||||||
|
|
||||||
|
vault_contents = yaml.safe_load(result.stdout)
|
||||||
|
vault_contents['webhook_full_url'] = (
|
||||||
|
f"{vault_contents['btcpay_base_url']}"
|
||||||
|
f"{vault_contents['btcpay_webhook_path']}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return vault_data
|
return vault_contents
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error reading vault: {str(e)}")
|
logger.error(f"Error reading vault: {str(e)}")
|
||||||
@ -61,8 +62,6 @@ def verify_signature(payload_body, signature_header):
|
|||||||
vault_values = get_vault_values()
|
vault_values = get_vault_values()
|
||||||
secret = vault_values['webhook_secret']
|
secret = vault_values['webhook_secret']
|
||||||
|
|
||||||
logger.debug(f"Secret type in verify_signature: {type(secret)}")
|
|
||||||
|
|
||||||
expected_signature = hmac.new(
|
expected_signature = hmac.new(
|
||||||
secret.encode('utf-8'),
|
secret.encode('utf-8'),
|
||||||
payload_body,
|
payload_body,
|
||||||
@ -77,20 +76,17 @@ def verify_signature(payload_body, signature_header):
|
|||||||
logger.error(f"Signature verification failed: {str(e)}")
|
logger.error(f"Signature verification failed: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@app.route('/webhook/vpn', methods=['POST'])
|
def handle_payment_webhook(request):
|
||||||
def handle_payment():
|
"""Handle BTCPay Server webhook for VPN provisioning"""
|
||||||
try:
|
try:
|
||||||
# Get vault values first to ensure we can access them
|
|
||||||
vault_values = get_vault_values()
|
vault_values = get_vault_values()
|
||||||
logger.info(f"Processing webhook on endpoint: {vault_values['webhook_full_url']}")
|
logger.info(f"Processing webhook on endpoint: {vault_values['webhook_full_url']}")
|
||||||
|
|
||||||
# Get the signature from headers
|
|
||||||
signature = request.headers.get('BTCPay-Sig')
|
signature = request.headers.get('BTCPay-Sig')
|
||||||
if not signature:
|
if not signature:
|
||||||
logger.error("Missing BTCPay-Sig header")
|
logger.error("Missing BTCPay-Sig header")
|
||||||
return jsonify({"error": "Missing signature"}), 401
|
return jsonify({"error": "Missing signature"}), 401
|
||||||
|
|
||||||
# Verify signature
|
|
||||||
is_valid = verify_signature(request.get_data(), signature)
|
is_valid = verify_signature(request.get_data(), signature)
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
logger.error("Invalid signature")
|
logger.error("Invalid signature")
|
||||||
@ -104,17 +100,14 @@ def handle_payment():
|
|||||||
logger.error("Missing invoiceId in webhook data")
|
logger.error("Missing invoiceId in webhook data")
|
||||||
return jsonify({"error": "Missing invoiceId"}), 400
|
return jsonify({"error": "Missing invoiceId"}), 400
|
||||||
|
|
||||||
# Remove __test__ prefix/suffix for test webhooks
|
|
||||||
if invoice_id.startswith('__test__') and invoice_id.endswith('__test__'):
|
if invoice_id.startswith('__test__') and invoice_id.endswith('__test__'):
|
||||||
invoice_id = invoice_id[8:-8] # Strip the test markers
|
invoice_id = invoice_id[8:-8]
|
||||||
logger.info(f"Stripped test markers from invoice ID: {invoice_id}")
|
logger.info(f"Stripped test markers from invoice ID: {invoice_id}")
|
||||||
|
|
||||||
# Get vault password from environment
|
vault_pass = os.getenv('ANSIBLE_VAULT_PASSWORD', '')
|
||||||
vault_pass = os.getenv('ANSIBLE_VAULT_PASSWORD')
|
|
||||||
if not vault_pass:
|
if not vault_pass:
|
||||||
raise Exception("Vault password not found in environment variables")
|
raise Exception("Vault password not found in environment variables")
|
||||||
|
|
||||||
# Create temporary vault password file for ansible-playbook
|
|
||||||
with tempfile.NamedTemporaryFile(mode='w', delete=False) as vault_pass_file:
|
with tempfile.NamedTemporaryFile(mode='w', delete=False) as vault_pass_file:
|
||||||
vault_pass_file.write(vault_pass)
|
vault_pass_file.write(vault_pass)
|
||||||
vault_pass_file.flush()
|
vault_pass_file.flush()
|
||||||
@ -122,10 +115,10 @@ def handle_payment():
|
|||||||
cmd = [
|
cmd = [
|
||||||
'ansible-playbook',
|
'ansible-playbook',
|
||||||
str(PLAYBOOK_PATH),
|
str(PLAYBOOK_PATH),
|
||||||
'-i', str(INVENTORY_PATH),
|
'-i', str(BASE_DIR / 'inventory.ini'),
|
||||||
'-e', f'invoice_id={invoice_id}',
|
'-e', f'invoice_id={invoice_id}',
|
||||||
'--vault-password-file', vault_pass_file.name,
|
'--vault-password-file', vault_pass_file.name,
|
||||||
'-vvv' # Add verbose output
|
'-vvv'
|
||||||
]
|
]
|
||||||
|
|
||||||
logger.info(f"Running ansible-playbook command: {' '.join(cmd)}")
|
logger.info(f"Running ansible-playbook command: {' '.join(cmd)}")
|
||||||
@ -136,10 +129,8 @@ def handle_payment():
|
|||||||
text=True
|
text=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Clean up the temporary file
|
|
||||||
os.unlink(vault_pass_file.name)
|
os.unlink(vault_pass_file.name)
|
||||||
|
|
||||||
# Log the complete output regardless of success/failure
|
|
||||||
logger.info(f"Ansible playbook stdout: {result.stdout}")
|
logger.info(f"Ansible playbook stdout: {result.stdout}")
|
||||||
if result.stderr:
|
if result.stderr:
|
||||||
logger.error(f"Ansible playbook stderr: {result.stderr}")
|
logger.error(f"Ansible playbook stderr: {result.stderr}")
|
||||||
@ -167,16 +158,3 @@ def handle_payment():
|
|||||||
"error": str(e),
|
"error": str(e),
|
||||||
"traceback": traceback.format_exc()
|
"traceback": traceback.format_exc()
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Verify we can read the vault and construct URL before starting
|
|
||||||
try:
|
|
||||||
vault_values = get_vault_values()
|
|
||||||
logger.info(f"Successfully loaded vault values")
|
|
||||||
logger.info(f"Configured webhook URL: {vault_values['webhook_full_url']}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to load vault values: {str(e)}")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
logger.info(f"Starting webhook handler, watching for playbook at {PLAYBOOK_PATH}")
|
|
||||||
app.run(host='0.0.0.0', port=5000)
|
|
Loading…
Reference in New Issue
Block a user