Bugfix Docker Rule overwrite in UI

This commit is contained in:
ChrispyBacon-dev 2025-09-23 09:36:35 +02:00
parent 4f35fde0c6
commit 74aec141d8
7 changed files with 85 additions and 5 deletions

View file

@ -105,6 +105,16 @@ def create_app():
login_manager.login_view = 'auth.login'
login_manager.login_message_category = "info"
@login_manager.unauthorized_handler
def unauthorized():
"""Handle unauthorized access - return JSON for API requests, redirect for web requests."""
from flask import request, jsonify, redirect, url_for
# Check if this is an API request
if request.path.startswith('/api/'):
return jsonify({"status": "error", "message": "authentication_required"}), 401
# For web requests, redirect to login page
return redirect(url_for('auth.login'))
@login_manager.user_loader
def load_user(user_id):
"""Load user from the config for session management."""

View file

@ -268,6 +268,10 @@ def process_container_start(container_obj):
logging.info(f"DOCKER_HANDLER: Rule {rule_key} is manual, skipping for {container_name_val}.")
continue
if existing_rule and existing_rule.get("rule_ui_override", False):
logging.info(f"DOCKER_HANDLER: Rule {rule_key} is UI-overridden, skipping Docker updates for {container_name_val}.")
continue
original_existing_rule_for_comparison = copy.deepcopy(existing_rule) if existing_rule else None
if existing_rule:
@ -337,6 +341,7 @@ def process_container_start(container_obj):
"access_policy_type": None,
"access_app_config_hash": None,
"access_policy_ui_override": False,
"rule_ui_override": False,
"source": "docker",
"access_group_id": None,
"tunnel_id": master_tunnel_id,

View file

@ -264,6 +264,7 @@ def reconcile_agent_report(agent_id, reported_containers):
"access_policy_type": None,
"access_app_config_hash": None,
"access_policy_ui_override": False,
"rule_ui_override": False,
"source": "agent",
"agent_id": agent_id,
"access_group_id": None,
@ -389,7 +390,7 @@ def _run_reconciliation_logic():
"origin_server_name": desired_details.get("origin_server_name"),
"http_host_header": desired_details.get("http_host_header"),
"access_app_id": None, "access_policy_type": None, "access_app_config_hash": None,
"access_policy_ui_override": False, "source": "docker",
"access_policy_ui_override": False, "rule_ui_override": False, "source": "docker",
"access_group_id": None,
"tunnel_name": master_tunnel_name
}

View file

@ -128,6 +128,7 @@ def load_state():
rule_copy.setdefault("access_policy_type", None)
rule_copy.setdefault("access_app_config_hash", None)
rule_copy.setdefault("access_policy_ui_override", False)
rule_copy.setdefault("rule_ui_override", False)
rule_copy.setdefault("source", "docker")
rule_copy.setdefault("path", None)
rule_copy.setdefault("http_host_header", None)
@ -197,6 +198,7 @@ def save_state():
"access_policy_type": rule.get("access_policy_type"),
"access_app_config_hash": rule.get("access_app_config_hash"),
"access_policy_ui_override": rule.get("access_policy_ui_override", False),
"rule_ui_override": rule.get("rule_ui_override", False),
"source": rule.get("source", "docker"),
"access_group_id": rule.get("access_group_id"),
"tunnel_id": rule.get("tunnel_id"),
@ -355,7 +357,8 @@ def serialize_managed_rule(rule_key: str, rule: Dict[str, Any]) -> Dict[str, Any
"tunnel_id": rule.get("tunnel_id"),
"tunnel_name": rule.get("tunnel_name"),
"access_policy_type": rule.get("access_policy_type"),
"access_policy_ui_override": rule.get("access_policy_ui_override", False)
"access_policy_ui_override": rule.get("access_policy_ui_override", False),
"rule_ui_override": rule.get("rule_ui_override", False)
}

View file

@ -189,6 +189,9 @@
{% if details.access_policy_ui_override and details.source != 'manual' %}
<span class="badge badge-warning badge-xs ml-2 animate-pulse" title="This policy is managed by the UI and overrides container labels.">UI Override</span>
{% endif %}
{% if details.rule_ui_override and details.source == 'docker' %}
<span class="badge badge-error badge-xs ml-2 animate-pulse" title="This Docker rule has been overridden via UI and no longer reflects container labels.">Rule UI Override</span>
{% endif %}
</td>
<td class="p-3 whitespace-nowrap text-sm policy-actions-cell" style="min-width: 180px;">
<div class="flex items-center space-x-1 sm:space-x-2">
@ -203,6 +206,13 @@
<button type="submit" class="btn btn-xs btn-warning btn-outline" title="Revert to Label/Default Policy">Revert</button>
</form>
{% endif %}
{% if details.rule_ui_override and details.source == 'docker' %}
<form action="{{ url_for('web.ui_revert_docker_rule_route') }}" method="POST" onsubmit="return confirm('Revert rule for {{ display_hostname }} to be managed by Docker labels?');" class="protocol-aware-form inline-block">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="rule_key" value="{{ hostname }}">
<button type="submit" class="btn btn-xs btn-error btn-outline" title="Revert to Docker Label Control">Revert to Labels</button>
</form>
{% endif %}
</div>
</td>
<td class="p-3 whitespace-nowrap text-sm opacity-70" data-role="expires-cell">

View file

@ -75,8 +75,12 @@ def _enforce_master_api_key():
return
if request.method == 'OPTIONS':
return
# For agent endpoints in allowlist, skip all authentication (including Flask-Login)
if endpoint in _AGENT_ENDPOINT_ALLOWLIST:
return
# For all other API endpoints, ensure proper API authentication
expected_key = current_app.config.get('MASTER_API_KEY') or config.MASTER_API_KEY
if not expected_key:
logging.warning("MASTER_AUTH: Master API key not configured; rejecting %s", endpoint)
@ -495,6 +499,7 @@ def create_manual_rule_api():
"access_policy_type": None,
"access_app_config_hash": None,
"access_policy_ui_override": False,
"rule_ui_override": False,
"source": "manual",
"access_group_id": access_groups or None,
"tunnel_id": tunnel_id,
@ -723,6 +728,7 @@ def process_agent_container_start(payload, agent_id):
"access_policy_type": None,
"access_app_config_hash": None,
"access_policy_ui_override": False,
"rule_ui_override": False,
"source": "agent",
"agent_id": agent_id,
"tunnel_name": assigned_tunnel_name,

View file

@ -1262,6 +1262,7 @@ def ui_add_manual_rule_route():
"access_app_config_hash": access_app_config_hash,
"access_group_id": access_group_id,
"access_policy_ui_override": True,
"rule_ui_override": False,
"tunnel_id": target_tunnel_id,
"tunnel_name": target_tunnel_name
}
@ -1280,6 +1281,49 @@ def ui_add_manual_rule_route():
return redirect(url_for('web.status_page'))
@bp.route('/ui/docker-rules/revert', methods=['POST'])
def ui_revert_docker_rule_route():
"""
Reverts a UI-overridden Docker rule back to label-driven configuration.
"""
if not docker_client:
cloudflared_agent_state["last_action_status"] = "Error: Docker client unavailable."
return redirect(url_for('web.status_page'))
rule_key = request.form.get('rule_key')
if not rule_key:
cloudflared_agent_state["last_action_status"] = "Error: Missing rule key for revert."
return redirect(url_for('web.status_page'))
with state_lock:
existing = managed_rules.get(rule_key)
if not existing:
cloudflared_agent_state["last_action_status"] = f"Error: Rule '{rule_key}' not found."
return redirect(url_for('web.status_page'))
if existing.get("source") != "docker":
cloudflared_agent_state["last_action_status"] = f"Error: Rule '{rule_key}' is not a Docker rule."
return redirect(url_for('web.status_page'))
if not existing.get("rule_ui_override", False):
cloudflared_agent_state["last_action_status"] = f"Info: Rule '{rule_key}' is not UI-overridden."
return redirect(url_for('web.status_page'))
# Revert the rule back to Docker label control
existing["rule_ui_override"] = False
save_state()
cloudflared_agent_state["last_action_status"] = f"Success: Rule '{rule_key}' reverted to Docker label control. Reconciliation will update it based on container labels."
# Trigger reconciliation to pick up Docker labels
try:
from app.core.reconciler import reconcile_state_threaded
reconcile_state_threaded()
except Exception as e:
logging.error(f"Failed to trigger reconciliation after Docker rule revert: {e}")
return redirect(url_for('web.status_page'))
@bp.route('/ui/manual-rules/edit', methods=['POST'])
def ui_edit_manual_rule_route():
"""
@ -1301,9 +1345,9 @@ def ui_edit_manual_rule_route():
if not existing:
cloudflared_agent_state["last_action_status"] = f"Error: Rule '{rule_key}' not found."
return redirect(url_for('web.status_page'))
if existing.get("source") == "docker":
cloudflared_agent_state["last_action_status"] = f"Error: Rule '{rule_key}' is Docker-managed and cannot be edited via UI."
return redirect(url_for('web.status_page'))
# Allow editing Docker rules but mark them as UI-overridden
is_docker_rule = existing.get("source") == "docker"
subdomain_input = request.form.get('edit_subdomain', '').strip()
domain_name_input = request.form.get('edit_domain_name', '').strip()
@ -1442,6 +1486,7 @@ def ui_edit_manual_rule_route():
"access_app_config_hash": access_app_config_hash,
"access_group_id": access_group_id,
"access_policy_ui_override": True,
"rule_ui_override": is_docker_rule,
"source": existing.get("source", "manual")
}