From acdfd3c8ca776945477a9314df89d7eec5ab80d1 Mon Sep 17 00:00:00 2001 From: phact Date: Mon, 8 Sep 2025 20:52:52 -0400 Subject: [PATCH 1/3] os diag --- src/tui/screens/diagnostics.py | 206 +++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) diff --git a/src/tui/screens/diagnostics.py b/src/tui/screens/diagnostics.py index 5091654a..3be628f2 100644 --- a/src/tui/screens/diagnostics.py +++ b/src/tui/screens/diagnostics.py @@ -63,6 +63,7 @@ class DiagnosticsScreen(Screen): yield Button("Refresh", variant="primary", id="refresh-btn") yield Button("Check Podman", variant="default", id="check-podman-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("Save to File", variant="default", id="save-btn") yield Button("Back", variant="default", id="back-btn") @@ -92,6 +93,8 @@ class DiagnosticsScreen(Screen): asyncio.create_task(self.check_podman()) elif event.button.id == "check-docker-btn": 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": self.copy_to_clipboard() elif event.button.id == "save-btn": @@ -415,5 +418,208 @@ class DiagnosticsScreen(Screen): 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 From 86709db9849ba2fb7215fe5564b2aa7157f2b477 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 8 Sep 2025 22:02:12 -0300 Subject: [PATCH 2/3] Update file path key in tweaks dictionary for LangflowFileService to enhance clarity This commit modifies the key for file paths in the tweaks dictionary from "file_path" to "path" within the LangflowFileService class. This change improves code clarity and consistency, aligning with best practices for robust async development while maintaining existing functionality. --- src/services/langflow_file_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/langflow_file_service.py b/src/services/langflow_file_service.py index b2e91fe2..9d582989 100644 --- a/src/services/langflow_file_service.py +++ b/src/services/langflow_file_service.py @@ -82,7 +82,7 @@ class LangflowFileService: # Pass files via tweaks to File component (File-PSU37 from the flow) if file_paths: - tweaks["File-PSU37"] = {"file_path": file_paths} + tweaks["File-PSU37"] = {"path": file_paths} # Pass JWT token via tweaks using the x-langflow-global-var- pattern if jwt_token: From df46e17b60d1ad481cfb6dfb752c49c2e06bb628 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 8 Sep 2025 22:02:44 -0300 Subject: [PATCH 3/3] Update uv.lock to reflect new package version and revision This commit updates the uv.lock file by incrementing the revision number to 3 and updating the version of the "openrag" package from 0.1.0 to 0.1.1. These changes ensure that the project dependencies are accurately tracked and align with best practices for maintaining robust async code. --- uv.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uv.lock b/uv.lock index 87734b48..bd7da744 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.13" resolution-markers = [ "sys_platform == 'darwin'", @@ -1405,7 +1405,7 @@ wheels = [ [[package]] name = "openrag" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } dependencies = [ { name = "agentd" },