Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release version 0.15.0.0 #20

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,55 @@
# CURRENTLY-IN-DEVELOPMENT
# `v0.15.0.0`
### New Controllers
- ESXi Controllers
- 1114 - snmp_config
- VCSA Controllers
- 1225 - ip_based_storage_port_group_config
# `v0.14.7.0`
##### Released by balavigneshr on Sep 18, 2024 @ 09:45 PM UTC
### New Controllers
- ESXi Controllers
- 4 - ssh_host_based_authentication
- 7 - ssh_permit_user_environment
- 16 - ssh_permit_tunnel
- 13 - ssh_gateway_ports
- 147 - ntp_config
- 22 - password_quality_config
- 12 - ssh_compression
- 6 - ssh_permit_empty_passwords
- 11 - ssh_strict_mode
- 14 - ssh_x11_forwarding
### Controller enhancements
- ESXi Controllers
- Fix for alarm_esx_remote_syslog_failure by adding check for expression attribute
- Add version check for rhttpproxy fips 140 esxi control
# Adding schema documentation
# `v0.14.6.0`
##### Released by codydouglasBC on Sept 05, 2024 @ 11:07 PM UTC
# `v0.14.5.0`
##### Released by balavigneshr on Aug 30, 2024 @ 10:17 PM UTC
### Bug Fixes
- Delete vCenter REST session as part of the vc_context `__exit__()`.
# `v0.14.4.0`
##### Released by balavigneshr on Aug 28, 2024 @ 05:43 PM UTC
### Controllers
- VCSA Controllers
- VM migration fix for template vms
# `v0.14.3.0`
##### Released by hguangsong on Aug 26, 2024 @ 11:42 PM UTC
### Dependency Version Changes
- lxml version requirement changed to "lxml>=4.9.1,<=5.2.2"
# `v0.14.2.0`
##### Released by hguangsong on Aug 23, 2024 @ 07:19 PM UTC
### Dependency Version Changes
- requests version requirement changed to "requests>=2.31.0"
- pyOpenSSL version requirement changed to "pyOpenSSL>=23.2.0,<=24.0.0"
- urllib3 version requirement changed to "urllib3>=1.26.6,<2.0.0"
# `v0.14.1.0`
##### Released by balavigneshr on Aug 21, 2024 @ 08:24 PM UTC
### New Controllers
- ESXi Controllers
- 136 - log_location_config
# `v0.14.0.0`
##### Released by ravi-pratap-s on Aug 09, 2024 @ 06:33 PM UTC
### New Controllers
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ Config-modules is a library that can be utilized by services written in python t
| 2. [Configuration](docs/configuration.md) |
| 3. [Instructions to Create New Controllers](docs/instructions-to-create-new-controllers.md) |
| 4. [Testing Controllers](docs/testing-controllers.md) |
| 5. [Controller Documentation](docs/controllers/markdown/index.md) |
| 6. [Metadata](docs/metadata.md) |
| 7. [Functional Test](functional_tests/README.md) |
| 8. [API Service Documentation](docs/api-service.md) |
| 9. [Building and Running in Docker](docs/docker-instructions.md) |
| 5. [Compliance Schema Documentation](docs/compliance-schema-documentation.md) |
| 6. [Controller Documentation](docs/controllers/markdown/index.md) |
| 7. [Metadata](docs/metadata.md) |
| 8. [Functional Test](functional_tests/README.md) |
| 9. [API Service Documentation](docs/api-service.md) |
| 10. [Building and Running in Docker](docs/docker-instructions.md) |

## Directory Structure

Expand Down
2 changes: 1 addition & 1 deletion config_modules_vmware/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Copyright 2024 Broadcom. All Rights Reserved.

version: str = "0.14.6.0"
version: str = "0.15.0.0"
name: str = "config_modules_vmware"
author: str = "Broadcom"
description: str = "VMware Unified Config Modules"
33 changes: 32 additions & 1 deletion config_modules_vmware/controllers/base_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from abc import ABC
from abc import abstractmethod
from typing import Any
from typing import ClassVar
from typing import Dict
from typing import List
from typing import Tuple
Expand All @@ -12,14 +13,20 @@
from config_modules_vmware.framework.logging.logger_adapter import LoggerAdapter
from config_modules_vmware.framework.models.controller_models.metadata import ControllerMetadata
from config_modules_vmware.framework.models.output_models.compliance_response import ComplianceStatus
from config_modules_vmware.framework.models.output_models.get_schema_response import GetSchemaStatus
from config_modules_vmware.framework.models.output_models.remediate_response import RemediateStatus
from config_modules_vmware.framework.models.output_models.validate_configuration_response import (
ValidateConfigurationStatus,
)
from config_modules_vmware.framework.utils.comparator import Comparator
from config_modules_vmware.framework.utils.comparator import ComparatorOptionForList

logger = LoggerAdapter(logging.getLogger(__name__))


class BaseController(ABC):
metadata: ClassVar[ControllerMetadata]

def __init__(self):
self.comparator_option = ComparatorOptionForList.COMPARE_AFTER_SORT
self.instance_key = "name"
Expand Down Expand Up @@ -105,7 +112,7 @@ def remediate(self, context: BaseContext, desired_values: Any) -> Dict:

elif compliance_response.get(consts.STATUS) == ComplianceStatus.COMPLIANT:
# For compliant_status as "COMPLIANT", return remediation as skipped.
return {consts.STATUS: RemediateStatus.SKIPPED, consts.ERRORS: ["Control already compliant"]}
return {consts.STATUS: RemediateStatus.SKIPPED, consts.ERRORS: [consts.CONTROL_ALREADY_COMPLIANT]}

elif compliance_response.get(consts.STATUS) == ComplianceStatus.SKIPPED:
# For compliance_status as "SKIPPED", return remediation as SKIPPED since no remediation was performed.
Expand Down Expand Up @@ -138,3 +145,27 @@ def remediate(self, context: BaseContext, desired_values: Any) -> Dict:
else:
result = {consts.STATUS: RemediateStatus.FAILED, consts.ERRORS: errors}
return result

def get_schema(self, context: BaseContext) -> Dict: # pylint: disable=W0613
"""Get configuration schema.
Note: This is not yet implemented for compliance.

:param context: Product context instance.
:type context: BaseContext
:return: Dict of status and schema result.
:rtype: dict
"""
return {consts.RESULT: self.metadata.spec, consts.STATUS: GetSchemaStatus.SUCCESS}

def validate(self, context: BaseContext, desired_values: Any) -> Dict: # pylint: disable=W0613
"""Validate configuration.
Note: This is not yet implemented for compliance.

:param context: Product context instance
:type context: BaseContext
:param desired_values: Desired configuration values.
:type desired_values: Any
:return: Dict of status and schema result.
:rtype: dict
"""
return {consts.STATUS: ValidateConfigurationStatus.SKIPPED}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
ADDRESS_KEY = "address"
NETWORK_KEY = "network"
NETWORK_CIDR_FORMAT = "{}/{}"
USER_CONTROLLABLE = "userControllable"
IP_LIST_CONFIGURABLE = "ipListUserConfigurable"

logger = LoggerAdapter(logging.getLogger(__name__))

Expand Down Expand Up @@ -136,7 +138,7 @@ def remediate(self, context: HostContext, desired_values: Any) -> Dict:

elif compliance_response.get(consts.STATUS) == ComplianceStatus.COMPLIANT:
# For compliant case, return SKIPPED.
return {consts.STATUS: RemediateStatus.SKIPPED, consts.ERRORS: ["Control already compliant"]}
return {consts.STATUS: RemediateStatus.SKIPPED, consts.ERRORS: [consts.CONTROL_ALREADY_COMPLIANT]}
else:
# Check for non-compliant items and iterate through each of drifts and invoke remediation.
# Scenario 1. Handle addition/removal of ruleset in desired config
Expand Down Expand Up @@ -220,6 +222,14 @@ def _set_ruleset_config(
old = {}
firewall_config = context.host_ref.configManager.firewallSystem
ruleset_name = non_compliant_desired_config.get(NAME_KEY)
rulesets_metadata = {
ruleset.key: {
USER_CONTROLLABLE: getattr(ruleset, USER_CONTROLLABLE, True),
IP_LIST_CONFIGURABLE: getattr(ruleset, IP_LIST_CONFIGURABLE, True),
}
for ruleset in firewall_config.firewallInfo.ruleset
}

allow_all_ip, allowed_ips = None, None
# desired_config dict contains only non-compliant config. This operation requires other keys that are
# compliant as well. So get full desired config from input desired_values.
Expand All @@ -235,13 +245,15 @@ def _set_ruleset_config(
for config_key in non_compliant_desired_config.keys():
if config_key == NAME_KEY:
continue
if config_key == ENABLED_KEY:
if config_key == ENABLED_KEY and rulesets_metadata[ruleset_name].get(USER_CONTROLLABLE):
# Handle Enable/Disable Ruleset configuration.
config = non_compliant_desired_config.get(ENABLED_KEY)
self._toggle_ruleset(
firewall_config, ruleset_name, config, new, old, errors, non_compliant_current_config
)
elif config_key == ALLOW_ALL_IP_KEY or config_key == ALLOWED_IPS_KEY:
elif (config_key == ALLOW_ALL_IP_KEY or config_key == ALLOWED_IPS_KEY) and rulesets_metadata[
ruleset_name
].get(IP_LIST_CONFIGURABLE):
# create tuples with drift and full desired configs.
allow_all_ip = (
desired_config_full.get(ALLOW_ALL_IP_KEY),
Expand Down
176 changes: 176 additions & 0 deletions config_modules_vmware/controllers/esxi/log_location_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Copyright 2024 Broadcom. All Rights Reserved.
import logging
import re
from typing import List
from typing import Tuple

from config_modules_vmware.controllers.base_controller import BaseController
from config_modules_vmware.framework.auth.contexts.base_context import BaseContext
from config_modules_vmware.framework.auth.contexts.esxi_context import HostContext
from config_modules_vmware.framework.logging.logger_adapter import LoggerAdapter
from config_modules_vmware.framework.models.controller_models.metadata import ControllerMetadata
from config_modules_vmware.framework.models.output_models.remediate_response import RemediateStatus

logger = LoggerAdapter(logging.getLogger(__name__))

LOG_LOCATION = "log_location"
IS_PERSISTENT = "is_persistent"


class LogLocationConfig(BaseController):
"""ESXi controller to get/set/check_compliance/remediate persistent log location config.

| Config Id - 136
| Config Title - Configure a persistent log location for all locally stored logs.

"""

metadata = ControllerMetadata(
name="log_location_config", # controller name
path_in_schema="compliance_config.esxi.log_location_config",
# path in the schema to this controller's definition.
configuration_id="136", # configuration id as defined in compliance kit.
title="Configure a persistent log location for all locally stored logs",
# controller title as defined in compliance kit.
tags=[], # controller tags for future querying and filtering
version="1.0.0", # version of the controller implementation.
since="", # version when the controller was first introduced in the compliance kit.
products=[BaseContext.ProductEnum.ESXI], # product from enum in BaseContext.
components=[], # subcomponent within the product if applicable.
status=ControllerMetadata.ControllerStatus.ENABLED, # used to enable/disable a controller
impact=None, # from enum in ControllerMetadata.RemediationImpact.
scope="", # any information or limitations about how the controller operates. i.e. runs as a CLI on VCSA.
)

def _is_log_location_persistent(self, context: HostContext) -> bool:
"""Check if the log location is persistent.

:param context: ESXi context instance.
:type context: HostContext
:return: True if log location is persistent otherwise False.
:rtype: bool
:raises: Exception if fetching the config fails.
"""
persistent_log_config_get_command = "system syslog config get"
cli_output, _, _ = context.esx_cli_client().run_esx_cli_cmd(context.hostname, persistent_log_config_get_command)
logger.debug(f"{persistent_log_config_get_command} output is {cli_output}")

# Fetch persistent flag.
match = re.search(r"Local Log Output Is Persistent:\s*(\w+)", cli_output)
if not match:
err_msg = f"Unable to fetch persistent flag using command esxcli {persistent_log_config_get_command}"
raise Exception(err_msg)

return match.group(1).lower() == "true"

def _get_log_location(self, context: HostContext) -> str:
"""Get log location for the esxi host.

:param context: ESXi context instance.
:type context: HostContext
:return: Log location.
:rtype: str
:raises: Exception if fetching the config fails.
"""
persistent_log_config_get_command = "system syslog config get"
out, err, ret_code = context.esx_cli_client().run_esx_cli_cmd(
hostname=context.hostname, command=persistent_log_config_get_command, raise_on_non_zero=False
)
logger.debug(f"{persistent_log_config_get_command} output is {out}")

if ret_code:
err_msg = f"Command esxcli {persistent_log_config_get_command} failed."
if out:
err_msg += f" {out}"
if err:
err_msg += f" {err}"
raise Exception(err_msg)

# Fetch persistent flag.
match = re.search(r"Local Log Output:\s*(/.+)", out)
if not match:
err_msg = f"Unable to fetch log location using command esxcli {persistent_log_config_get_command}"
raise Exception(err_msg)

return match.group(1)

def _set_log_location(self, context: HostContext, log_location: str):
"""Set log location for esxi host.

:param context: ESXi context instance.
:type context: HostContext
:param log_location: Log location
:type log_location: str
:raises: Exception if there is any error during set operation
"""
persistent_log_config_set_command = f"system syslog config set --logdir={log_location}"
out, err, ret_code = context.esx_cli_client().run_esx_cli_cmd(
hostname=context.hostname, command=persistent_log_config_set_command, raise_on_non_zero=False
)
if ret_code:
err_msg = f"Command esxcli {persistent_log_config_set_command} failed."
if out:
err_msg += f" {out}"
if err:
err_msg += f" {err}"
raise Exception(err_msg)

def get(self, context: HostContext) -> Tuple[dict, List[str]]:
"""Get persistent log location config for esxi host.

:param context: ESXi context instance.
:type context: HostContext
:return: Tuple of dictionary with keys "log_location" and "is_persistent" and a list of errors.
:rtype: Tuple
"""
logger.info("Getting persistent log location config for syslog for esxi.")
errors = []
persistent_log_config = {}
try:
persistent_log_config = {
IS_PERSISTENT: self._is_log_location_persistent(context),
LOG_LOCATION: self._get_log_location(context),
}

except Exception as e:
logger.exception(f"An error occurred: {e}")
errors.append(str(e))
return persistent_log_config, errors

def set(self, context: HostContext, desired_values: dict) -> Tuple[RemediateStatus, List[str]]:
"""Set persistent log location config for esxi host.
It sets the log location and verifies if the log location persistent criteria matches with desired or not.
If it does not, then reverts to the original log location and report error

:param context: Esxi context instance.
:type context: HostContext
:param desired_values: dictionary with keys "log_location" and "is_persistent"
:type desired_values: dict
:return: Tuple of "status" and list of error messages.
:rtype: Tuple
"""
logger.info("Setting persistent log location config for syslog for esxi.")
errors = []
status = RemediateStatus.SUCCESS
try:
current_log_location = self._get_log_location(context)

is_persistent_required = desired_values.get(IS_PERSISTENT)

desired_log_location = desired_values.get(LOG_LOCATION)
logger.debug(f"Set the desired logdir to {desired_log_location}")
self._set_log_location(context=context, log_location=desired_log_location)
if is_persistent_required != self._is_log_location_persistent(context):
# Revert the location path and report error message
self._set_log_location(context=context, log_location=current_log_location)
err_msg = (
f"'log_location: {desired_log_location}' is not matching the "
f"desired criteria 'is_persistent: {is_persistent_required}'"
)
raise Exception(err_msg)

except Exception as e:
logger.exception(f"An error occurred: {e}")
errors.append(str(e))
status = RemediateStatus.FAILED
return status, errors
Loading