Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .metadata/omnia_version
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
omnia_version: 2.0.0.0
omnia_version: 2.2.0.1
omnia_installation_path: ""
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
<img src="docs/logos/omnia-logo-transparent.png" width="500px">

----

**Please note**: We take Omnia's security and our users' trust
very seriously. If you believe you have found a security issue
in Omnia, _please responsibly disclose_ by following the process at
[https://github.com/dell/omnia/security/advisories](https://github.com/dell/omnia/blob/main/SECURITY.md).

----

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<!-- DO NOT ADD A BADGE -->
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Only the latest released version of Omnia is supported with security updates. Us

If you discover a security vulnerability in Omnia, please do **not** create a public GitHub issue.

Please report it using GitHub's **Private Vulnerability Reporting** feature.
Please report it using GitHub's [**Private Vulnerability Reporting**](https://github.com/dell/omnia/security/advisories) feature.

Please include:

Expand All @@ -35,6 +35,6 @@ Please avoid public disclosure until the issue has been reviewed and a fix is av

## Contact

For security-related concerns, please use GitHub's **Private Vulnerability Reporting** feature.
For security-related concerns, please use GitHub's [**Private Vulnerability Reporting**](https://github.com/dell/omnia/security/advisories) feature.

Thank you for helping make Omnia more secure.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
the system auto-generates one K8s hop per intermediate version.

Example: Omnia 2.1.0.0 (K8s 1.34.1) -> Omnia 2.3.0.0 (K8s 1.37.1)
Omnia path : 2.1.0.0 -> 2.2.0.0 -> 2.3.0.0
Omnia path : 2.1.0.0 -> 2.2.0.1 -> 2.3.0.0
K8s hops : 1.34.1 -> 1.35.1 (Omnia 2.1->2.2, direct)
1.35.1 -> 1.36.1 (auto-generated, within 2.2->2.3)
1.36.1 -> 1.37.1 (Omnia 2.2->2.3, final)
Expand Down
139 changes: 74 additions & 65 deletions common/library/modules/delete_idracips_from_mysqldb.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@

#!/usr/bin/python
"""Module to delete iDRAC IPs from MySQL database.
This module connects to a Kubernetes pod running MySQL and deletes iDRAC IPs
that are not present in bmc_data.csv. It handles retries and delays for robustness."""
This module connects to a Kubernetes pod running MySQL via PyMySQL and deletes
iDRAC IPs that are not present in bmc_data.csv. It uses parameterized queries
to prevent SQL injection. It handles retries and delays for robustness."""

import time
import ipaddress
import pymysql
from ansible.module_utils.basic import AnsibleModule
from kubernetes import client, config
from kubernetes.stream import stream
from kubernetes.config.config_exception import ConfigException


Expand All @@ -32,83 +34,82 @@ def load_kube_context():
config.load_incluster_config()


def run_mysql_query_in_pod(namespace, pod, container, mysql_user, mysql_password, query):
"""Run a MySQL query in the specified pod.
def resolve_pod_ip(namespace, pod):
"""Resolve the IP address of a Kubernetes pod via the K8s API.

Args:
namespace: Kubernetes namespace
pod: Pod name
container: Container name
mysql_user: MySQL username
mysql_password: MySQL password
query: MySQL query to execute

Returns:
dict: Result containing return code and output
str: Pod IP address

Raises:
RuntimeError: If the pod IP cannot be resolved
"""
core_v1 = client.CoreV1Api()
mysql_command = [
"mysql",
"-u", mysql_user,
"-N", "-B",
f"-p{mysql_password}",
"-e", query
]

try:
ws = stream(
core_v1.connect_get_namespaced_pod_exec,
name=pod,
namespace=namespace,
container=container,
command=mysql_command,
stderr=True,
stdin=False,
stdout=True,
tty=False,
_preload_content=False
)
pod_obj = core_v1.read_namespaced_pod(name=pod, namespace=namespace)
pod_ip = pod_obj.status.pod_ip
if not pod_ip:
raise RuntimeError(f"Pod {pod} in namespace {namespace} has no IP assigned")
return pod_ip

stdout = ""
stderr = ""

while ws.is_open():
ws.update(timeout=1)
if ws.peek_stdout():
stdout += ws.read_stdout()
if ws.peek_stderr():
stderr += ws.read_stderr()
ws.close()
def run_mysql_delete_in_pod(
namespace, pod, mysqldb_container_port, mysqldb_name,
mysql_user, mysql_password, ip_to_delete
):
"""Delete an iDRAC IP from MySQL using a PyMySQL parameterized query.

rc = ws.returncode
Connects directly to the MySQL pod over TCP (resolved via the K8s API)
and executes a DELETE with a bound parameter, eliminating SQL injection.

if rc != 0:
return {
"rc": rc,
"result": stderr.strip() if stderr else "Unknown error"
}
Args:
namespace: Kubernetes namespace
pod: Pod name
mysqldb_container_port: MySQL container port (default 3306)
mysqldb_name: MySQL database name
mysql_user: MySQL username
mysql_password: MySQL password
ip_to_delete: IP address to delete

query_result = [
line.strip() for line in stdout.strip().splitlines()
if line.strip() and not line.strip().startswith("mysql:")
]
Returns:
dict: Result containing return code and output
"""
pod_ip = resolve_pod_ip(namespace, pod)

conn = None
try:
conn = pymysql.connect(
host=pod_ip,
port=mysqldb_container_port,
user=mysql_user,
password=mysql_password,
database=mysqldb_name,
connect_timeout=10
)
with conn.cursor() as cursor:
cursor.execute("DELETE FROM services WHERE ip = %s", (ip_to_delete,))
affected_rows = cursor.rowcount
conn.commit()
return {
"rc": rc,
"result": query_result
"rc": 0,
"result": f"Deleted {affected_rows} row(s)"
}

except (ConfigException, OSError) as e:
except (pymysql.err.OperationalError, pymysql.err.MySQLError) as e:
return {
"rc": 1,
"result": str(e)
}
finally:
if conn:
conn.close()


def delete_idrac_from_mysql(
namespace,
pod,
container,
mysqldb_container_port,
mysqldb_name,
mysql_user,
mysql_password,
Expand All @@ -121,7 +122,7 @@ def delete_idrac_from_mysql(
Args:
namespace: Kubernetes namespace
pod: Pod name
container: Container name
mysqldb_container_port: MySQL container port
mysqldb_name: MySQL database name
mysql_user: MySQL username
mysql_password: MySQL password
Expand All @@ -132,19 +133,26 @@ def delete_idrac_from_mysql(
Returns:
dict: Result containing success status and message
"""
query = (
f"DELETE FROM {mysqldb_name}.services "
f"WHERE ip = '{ip_to_delete}';"
)
# Defense-in-depth: validate IP before attempting DB operation
try:
ipaddress.ip_address(ip_to_delete)
except ValueError:
return {
"success": False,
"ip": ip_to_delete,
"msg": f"Invalid IP address format: {ip_to_delete}"
}

result = {}
for attempt in range(retries):
result = run_mysql_query_in_pod(
result = run_mysql_delete_in_pod(
namespace=namespace,
pod=pod,
container=container,
mysqldb_container_port=mysqldb_container_port,
mysqldb_name=mysqldb_name,
mysql_user=mysql_user,
mysql_password=mysql_password,
query=query
ip_to_delete=ip_to_delete
)

if result.get("rc") == 0:
Expand Down Expand Up @@ -177,20 +185,21 @@ def main():
"pod_to_db_idrac_ips": {"type": "dict", "required": True},
"db_retries": {"type": "int", "default": 3},
"db_delay": {"type": "int", "default": 3},
"mysqldb_container_port": {"type": "int", "default": 3306},
}

module = AnsibleModule(argument_spec=module_args, supports_check_mode=True)

telemetry_namespace = module.params["telemetry_namespace"]
idrac_podnames = module.params["idrac_podnames"]
mysqldb_k8s_name = module.params["mysqldb_k8s_name"]
mysqldb_name = module.params["mysqldb_name"]
mysqldb_user = module.params["mysqldb_user"]
mysqldb_password = module.params["mysqldb_password"]
ips_to_delete = module.params["ips_to_delete"]
pod_to_db_idrac_ips = module.params["pod_to_db_idrac_ips"]
db_retries = module.params["db_retries"]
db_delay = module.params["db_delay"]
mysqldb_container_port = module.params["mysqldb_container_port"]

load_kube_context()

Expand All @@ -213,7 +222,7 @@ def main():
result = delete_idrac_from_mysql(
namespace=telemetry_namespace,
pod=pod,
container=mysqldb_k8s_name,
mysqldb_container_port=mysqldb_container_port,
mysqldb_name=mysqldb_name,
mysql_user=mysqldb_user,
mysql_password=mysqldb_password,
Expand Down
Loading