os diag
This commit is contained in:
parent
8ae543ac69
commit
acdfd3c8ca
1 changed files with 206 additions and 0 deletions
|
|
@ -63,6 +63,7 @@ class DiagnosticsScreen(Screen):
|
||||||
yield Button("Refresh", variant="primary", id="refresh-btn")
|
yield Button("Refresh", variant="primary", id="refresh-btn")
|
||||||
yield Button("Check Podman", variant="default", id="check-podman-btn")
|
yield Button("Check Podman", variant="default", id="check-podman-btn")
|
||||||
yield Button("Check Docker", variant="default", id="check-docker-btn")
|
yield Button("Check Docker", variant="default", id="check-docker-btn")
|
||||||
|
yield Button("Check OpenSearch Security", variant="default", id="check-opensearch-security-btn")
|
||||||
yield Button("Copy to Clipboard", variant="default", id="copy-btn")
|
yield Button("Copy to Clipboard", variant="default", id="copy-btn")
|
||||||
yield Button("Save to File", variant="default", id="save-btn")
|
yield Button("Save to File", variant="default", id="save-btn")
|
||||||
yield Button("Back", variant="default", id="back-btn")
|
yield Button("Back", variant="default", id="back-btn")
|
||||||
|
|
@ -92,6 +93,8 @@ class DiagnosticsScreen(Screen):
|
||||||
asyncio.create_task(self.check_podman())
|
asyncio.create_task(self.check_podman())
|
||||||
elif event.button.id == "check-docker-btn":
|
elif event.button.id == "check-docker-btn":
|
||||||
asyncio.create_task(self.check_docker())
|
asyncio.create_task(self.check_docker())
|
||||||
|
elif event.button.id == "check-opensearch-security-btn":
|
||||||
|
asyncio.create_task(self.check_opensearch_security())
|
||||||
elif event.button.id == "copy-btn":
|
elif event.button.id == "copy-btn":
|
||||||
self.copy_to_clipboard()
|
self.copy_to_clipboard()
|
||||||
elif event.button.id == "save-btn":
|
elif event.button.id == "save-btn":
|
||||||
|
|
@ -415,5 +418,208 @@ class DiagnosticsScreen(Screen):
|
||||||
|
|
||||||
log.write("")
|
log.write("")
|
||||||
|
|
||||||
|
async def check_opensearch_security(self) -> None:
|
||||||
|
"""Run OpenSearch security configuration diagnostics."""
|
||||||
|
log = self.query_one("#diagnostics-log", Log)
|
||||||
|
log.write("[bold green]OpenSearch Security Diagnostics[/bold green]")
|
||||||
|
|
||||||
|
# Get OpenSearch password from environment or prompt user that it's needed
|
||||||
|
opensearch_password = os.getenv("OPENSEARCH_PASSWORD")
|
||||||
|
if not opensearch_password:
|
||||||
|
log.write("[red]OPENSEARCH_PASSWORD environment variable not set[/red]")
|
||||||
|
log.write("[yellow]Set OPENSEARCH_PASSWORD to test security configuration[/yellow]")
|
||||||
|
log.write("")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test basic authentication
|
||||||
|
log.write("Testing basic authentication...")
|
||||||
|
cmd = [
|
||||||
|
"curl", "-s", "-k", "-w", "%{http_code}",
|
||||||
|
"-u", f"admin:{opensearch_password}",
|
||||||
|
"https://localhost:9200"
|
||||||
|
]
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||||
|
)
|
||||||
|
stdout, stderr = await process.communicate()
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
response = stdout.decode().strip()
|
||||||
|
# Extract HTTP status code (last 3 characters)
|
||||||
|
if len(response) >= 3:
|
||||||
|
status_code = response[-3:]
|
||||||
|
response_body = response[:-3]
|
||||||
|
if status_code == "200":
|
||||||
|
log.write("[green]✓ Basic authentication successful[/green]")
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
info = json.loads(response_body)
|
||||||
|
if "version" in info and "distribution" in info["version"]:
|
||||||
|
log.write(f" OpenSearch version: {info['version']['number']}")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
log.write(f"[red]✗ Basic authentication failed with status {status_code}[/red]")
|
||||||
|
else:
|
||||||
|
log.write("[red]✗ Unexpected response from OpenSearch[/red]")
|
||||||
|
else:
|
||||||
|
log.write(f"[red]✗ Failed to connect to OpenSearch: {stderr.decode().strip()}[/red]")
|
||||||
|
|
||||||
|
# Test security plugin account info
|
||||||
|
log.write("Testing security plugin account info...")
|
||||||
|
cmd = [
|
||||||
|
"curl", "-s", "-k", "-w", "%{http_code}",
|
||||||
|
"-u", f"admin:{opensearch_password}",
|
||||||
|
"https://localhost:9200/_plugins/_security/api/account"
|
||||||
|
]
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||||
|
)
|
||||||
|
stdout, stderr = await process.communicate()
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
response = stdout.decode().strip()
|
||||||
|
if len(response) >= 3:
|
||||||
|
status_code = response[-3:]
|
||||||
|
response_body = response[:-3]
|
||||||
|
if status_code == "200":
|
||||||
|
log.write("[green]✓ Security plugin accessible[/green]")
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
user_info = json.loads(response_body)
|
||||||
|
if "user_name" in user_info:
|
||||||
|
log.write(f" Current user: {user_info['user_name']}")
|
||||||
|
if "roles" in user_info:
|
||||||
|
log.write(f" Roles: {', '.join(user_info['roles'])}")
|
||||||
|
if "tenants" in user_info:
|
||||||
|
tenants = list(user_info['tenants'].keys())
|
||||||
|
log.write(f" Tenants: {', '.join(tenants)}")
|
||||||
|
except:
|
||||||
|
log.write(" Account info retrieved but couldn't parse JSON")
|
||||||
|
else:
|
||||||
|
log.write(f"[red]✗ Security plugin returned status {status_code}[/red]")
|
||||||
|
else:
|
||||||
|
log.write(f"[red]✗ Failed to access security plugin: {stderr.decode().strip()}[/red]")
|
||||||
|
|
||||||
|
# Test internal users
|
||||||
|
log.write("Testing internal users configuration...")
|
||||||
|
cmd = [
|
||||||
|
"curl", "-s", "-k", "-w", "%{http_code}",
|
||||||
|
"-u", f"admin:{opensearch_password}",
|
||||||
|
"https://localhost:9200/_plugins/_security/api/internalusers"
|
||||||
|
]
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||||
|
)
|
||||||
|
stdout, stderr = await process.communicate()
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
response = stdout.decode().strip()
|
||||||
|
if len(response) >= 3:
|
||||||
|
status_code = response[-3:]
|
||||||
|
response_body = response[:-3]
|
||||||
|
if status_code == "200":
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
users = json.loads(response_body)
|
||||||
|
if "admin" in users:
|
||||||
|
log.write("[green]✓ Admin user configured[/green]")
|
||||||
|
admin_user = users["admin"]
|
||||||
|
if admin_user.get("reserved"):
|
||||||
|
log.write(" Admin user is reserved (protected)")
|
||||||
|
log.write(f" Total internal users: {len(users)}")
|
||||||
|
except:
|
||||||
|
log.write("[green]✓ Internal users endpoint accessible[/green]")
|
||||||
|
else:
|
||||||
|
log.write(f"[red]✗ Internal users returned status {status_code}[/red]")
|
||||||
|
else:
|
||||||
|
log.write(f"[red]✗ Failed to access internal users: {stderr.decode().strip()}[/red]")
|
||||||
|
|
||||||
|
# Test authentication domains configuration
|
||||||
|
log.write("Testing authentication configuration...")
|
||||||
|
cmd = [
|
||||||
|
"curl", "-s", "-k", "-w", "%{http_code}",
|
||||||
|
"-u", f"admin:{opensearch_password}",
|
||||||
|
"https://localhost:9200/_plugins/_security/api/securityconfig"
|
||||||
|
]
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||||
|
)
|
||||||
|
stdout, stderr = await process.communicate()
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
response = stdout.decode().strip()
|
||||||
|
if len(response) >= 3:
|
||||||
|
status_code = response[-3:]
|
||||||
|
response_body = response[:-3]
|
||||||
|
if status_code == "200":
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
config = json.loads(response_body)
|
||||||
|
if "config" in config and "dynamic" in config["config"] and "authc" in config["config"]["dynamic"]:
|
||||||
|
authc = config["config"]["dynamic"]["authc"]
|
||||||
|
if "openid_auth_domain" in authc:
|
||||||
|
log.write("[green]✓ OpenID Connect authentication domain configured[/green]")
|
||||||
|
oidc_config = authc["openid_auth_domain"].get("http_authenticator", {}).get("config", {})
|
||||||
|
if "openid_connect_url" in oidc_config:
|
||||||
|
log.write(f" OIDC URL: {oidc_config['openid_connect_url']}")
|
||||||
|
if "subject_key" in oidc_config:
|
||||||
|
log.write(f" Subject key: {oidc_config['subject_key']}")
|
||||||
|
if "basic_internal_auth_domain" in authc:
|
||||||
|
log.write("[green]✓ Basic internal authentication domain configured[/green]")
|
||||||
|
|
||||||
|
# Check for multi-tenancy
|
||||||
|
if "kibana" in config["config"]["dynamic"]:
|
||||||
|
kibana_config = config["config"]["dynamic"]["kibana"]
|
||||||
|
if kibana_config.get("multitenancy_enabled"):
|
||||||
|
log.write("[green]✓ Multi-tenancy enabled[/green]")
|
||||||
|
else:
|
||||||
|
log.write("[yellow]⚠ Authentication configuration not found in expected format[/yellow]")
|
||||||
|
except Exception as e:
|
||||||
|
log.write("[green]✓ Security config endpoint accessible[/green]")
|
||||||
|
log.write(f" (Could not parse JSON: {str(e)[:50]}...)")
|
||||||
|
else:
|
||||||
|
log.write(f"[red]✗ Security config returned status {status_code}[/red]")
|
||||||
|
else:
|
||||||
|
log.write(f"[red]✗ Failed to access security config: {stderr.decode().strip()}[/red]")
|
||||||
|
|
||||||
|
# Test indices with potential security filtering
|
||||||
|
log.write("Testing index access...")
|
||||||
|
cmd = [
|
||||||
|
"curl", "-s", "-k", "-w", "%{http_code}",
|
||||||
|
"-u", f"admin:{opensearch_password}",
|
||||||
|
"https://localhost:9200/_cat/indices?v"
|
||||||
|
]
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||||
|
)
|
||||||
|
stdout, stderr = await process.communicate()
|
||||||
|
|
||||||
|
if process.returncode == 0:
|
||||||
|
response = stdout.decode().strip()
|
||||||
|
if len(response) >= 3:
|
||||||
|
status_code = response[-3:]
|
||||||
|
response_body = response[:-3]
|
||||||
|
if status_code == "200":
|
||||||
|
log.write("[green]✓ Index listing accessible[/green]")
|
||||||
|
lines = response_body.strip().split('\n')
|
||||||
|
if len(lines) > 1: # Skip header
|
||||||
|
indices_found = []
|
||||||
|
for line in lines[1:]:
|
||||||
|
if 'documents' in line:
|
||||||
|
indices_found.append('documents')
|
||||||
|
elif 'knowledge_filters' in line:
|
||||||
|
indices_found.append('knowledge_filters')
|
||||||
|
elif '.opendistro_security' in line:
|
||||||
|
indices_found.append('.opendistro_security')
|
||||||
|
if indices_found:
|
||||||
|
log.write(f" Key indices found: {', '.join(indices_found)}")
|
||||||
|
else:
|
||||||
|
log.write(f"[red]✗ Index listing returned status {status_code}[/red]")
|
||||||
|
else:
|
||||||
|
log.write(f"[red]✗ Failed to list indices: {stderr.decode().strip()}[/red]")
|
||||||
|
|
||||||
|
log.write("")
|
||||||
|
|
||||||
|
|
||||||
# Made with Bob
|
# Made with Bob
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue