Skip to content

Commit

Permalink
Merge branch 'develop' into firewall
Browse files Browse the repository at this point in the history
  • Loading branch information
narrieta authored Sep 25, 2024
2 parents 5641dab + 43cc835 commit 99fd28f
Show file tree
Hide file tree
Showing 14 changed files with 56 additions and 702 deletions.
25 changes: 13 additions & 12 deletions azurelinuxagent/ga/cgroupapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,9 +375,9 @@ def log_root_paths(self):
for controller in CgroupV1.get_supported_controller_names():
mount_point = self._cgroup_mountpoints.get(controller)
if mount_point is None:
log_cgroup_info("The {0} controller is not mounted".format(controller), send_event=False)
log_cgroup_info("The {0} controller is not mounted".format(controller))
else:
log_cgroup_info("The {0} controller is mounted at {1}".format(controller, mount_point), send_event=False)
log_cgroup_info("The {0} controller is mounted at {1}".format(controller, mount_point))

def start_extension_command(self, extension_name, command, cmd_name, timeout, shell, cwd, env, stdout, stderr,
error_code=ExtensionErrorCodes.PluginUnknownFailure):
Expand Down Expand Up @@ -546,12 +546,12 @@ def get_process_cgroup(self, process_id, cgroup_name):
return CgroupV2(cgroup_name=cgroup_name, root_cgroup_path=self._root_cgroup_path, cgroup_path=cgroup_path, enabled_controllers=self._controllers_enabled_at_root)

def log_root_paths(self):
log_cgroup_info("The root cgroup path is {0}".format(self._root_cgroup_path), send_event=False)
log_cgroup_info("The root cgroup path is {0}".format(self._root_cgroup_path))
for controller in CgroupV2.get_supported_controller_names():
if controller in self._controllers_enabled_at_root:
log_cgroup_info("The {0} controller is enabled at the root cgroup".format(controller), send_event=False)
log_cgroup_info("The {0} controller is enabled at the root cgroup".format(controller))
else:
log_cgroup_info("The {0} controller is not enabled at the root cgroup".format(controller), send_event=False)
log_cgroup_info("The {0} controller is not enabled at the root cgroup".format(controller))

def start_extension_command(self, extension_name, command, cmd_name, timeout, shell, cwd, env, stdout, stderr,
error_code=ExtensionErrorCodes.PluginUnknownFailure):
Expand Down Expand Up @@ -630,17 +630,18 @@ def get_controllers(self, expected_relative_path=None):
controller_mountpoint = self._controller_mountpoints.get(supported_controller_name)

if controller_mountpoint is None:
# Do not send telemetry here. We already have telemetry for unmounted controllers in cgroup init
log_cgroup_warning("{0} controller is not mounted; will not track".format(supported_controller_name), send_event=False)
continue

if controller_path is None:
log_cgroup_warning("{0} is not mounted for the {1} cgroup; will not track".format(supported_controller_name, self._cgroup_name), send_event=False)
log_cgroup_warning("{0} is not mounted for the {1} cgroup; will not track".format(supported_controller_name, self._cgroup_name))
continue

if expected_relative_path is not None:
expected_path = os.path.join(controller_mountpoint, expected_relative_path)
if controller_path != expected_path:
log_cgroup_warning("The {0} controller is not mounted at the expected path for the {1} cgroup; will not track. Actual cgroup path:[{2}] Expected:[{3}]".format(supported_controller_name, self._cgroup_name, controller_path, expected_path), send_event=False)
log_cgroup_warning("The {0} controller is not mounted at the expected path for the {1} cgroup; will not track. Actual cgroup path:[{2}] Expected:[{3}]".format(supported_controller_name, self._cgroup_name, controller_path, expected_path))
continue

if supported_controller_name == self.CPU_CONTROLLER:
Expand All @@ -650,7 +651,7 @@ def get_controllers(self, expected_relative_path=None):

if controller is not None:
msg = "{0} controller for cgroup: {1}".format(supported_controller_name, controller)
log_cgroup_info(msg, send_event=False)
log_cgroup_info(msg)
controllers.append(controller)

return controllers
Expand Down Expand Up @@ -705,21 +706,21 @@ def get_controllers(self, expected_relative_path=None):
controller = None

if supported_controller_name not in self._enabled_controllers:
# Do not send telemetry here. We already have telemetry for disabled controllers in cgroup init
log_cgroup_warning("{0} controller is not enabled; will not track".format(supported_controller_name),
send_event=False)
continue

if self._cgroup_path == "":
log_cgroup_warning("Cgroup path for {0} cannot be determined; will not track".format(self._cgroup_name),
send_event=False)
log_cgroup_warning("Cgroup path for {0} cannot be determined; will not track".format(self._cgroup_name))
continue

if expected_relative_path is not None:
expected_path = os.path.join(self._root_cgroup_path, expected_relative_path)
if self._cgroup_path != expected_path:
log_cgroup_warning(
"The {0} cgroup is not mounted at the expected path; will not track. Actual cgroup path:[{1}] Expected:[{2}]".format(
self._cgroup_name, self._cgroup_path, expected_path), send_event=False)
self._cgroup_name, self._cgroup_path, expected_path))
continue

if supported_controller_name == self.CPU_CONTROLLER:
Expand All @@ -729,7 +730,7 @@ def get_controllers(self, expected_relative_path=None):

if controller is not None:
msg = "{0} controller for cgroup: {1}".format(supported_controller_name, controller)
log_cgroup_info(msg, send_event=False)
log_cgroup_info(msg)
controllers.append(controller)

return controllers
Expand Down
130 changes: 8 additions & 122 deletions azurelinuxagent/ga/policy/policy_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,66 +16,21 @@
#

from azurelinuxagent.common import logger
from azurelinuxagent.common.version import DISTRO_VERSION, DISTRO_NAME
from azurelinuxagent.common.utils.distro_version import DistroVersion
from azurelinuxagent.common.event import WALAEventOperation, add_event
from azurelinuxagent.common import conf
from azurelinuxagent.common.osutil import get_osutil
import azurelinuxagent.ga.policy.regorus as regorus
from azurelinuxagent.ga.policy.regorus import PolicyError
from azurelinuxagent.common.exception import AgentError

# Define support matrix for Regorus and policy engine feature.
# Dict in the format: { distro:min_supported_version }
POLICY_SUPPORTED_DISTROS_MIN_VERSIONS = {
'ubuntu': DistroVersion('16.04'),
'mariner': DistroVersion('2'),
'azurelinux': DistroVersion('3')
}
# TODO: add 'arm64', 'aarch64' here once support is enabled for ARM64
POLICY_SUPPORTED_ARCHITECTURE = ['x86_64']

class PolicyError(AgentError):
"""
Error raised during agent policy enforcement.
"""


class PolicyEngine(object):
"""
Implements base policy engine API.
If any errors are thrown in regorus.py, they will be caught and re-raised here.
The caller will be responsible for handling errors.
"""
def __init__(self, rule_file, policy_file):
"""
Constructor checks that policy enforcement should be enabled, and then sets up the
Regorus policy engine (add rule and policy file).
rule_file: Path to a Rego file that specifies rules for policy behavior.
policy_file: Path to a JSON file that specifies parameters for policy behavior - for example,
whether allowlist or extension signing should be enforced.
The expected file format is:
{
"azureGuestAgentPolicy": {
"policyVersion": "0.1.0",
"signingRules": {
"extensionSigned": <true, false>
},
"allowListOnly": <true, false>
},
"azureGuestExtensionsPolicy": {
"allowed_ext_1": {
"signingRules": {
"extensionSigned": <true, false>
}
}
}
"""
self._engine = None
if not self.is_policy_enforcement_enabled():
self._log_policy(msg="Policy enforcement is not enabled.")
return

# If unsupported, this call will raise an error
self._check_policy_enforcement_supported()
self._engine = regorus.Engine(policy_file=policy_file, rule_file=rule_file)

@classmethod
def _log_policy(cls, msg, is_success=True, op=WALAEventOperation.Policy, send_event=True):
"""
Expand All @@ -94,76 +49,7 @@ def is_policy_enforcement_enabled():
Check whether user has opted into policy enforcement feature.
Caller function should check this before performing any operations.
"""
# TODO: The conf flag will be removed post private preview. Before public preview, add checks
# according to the planned user experience (TBD).
# TODO: Add check for policy file present at /etc/waagent_policy.json.
# Policy should only be enabled if conf flag is true AND policy file is present.
return conf.get_extension_policy_enabled()

@staticmethod
def _check_policy_enforcement_supported():
"""
Check that both platform architecture and distro/version are supported.
If supported, do nothing.
If not supported, raise PolicyError with user-friendly error message.
"""
osutil = get_osutil()
arch = osutil.get_vm_arch()
# TODO: surface as a user error with clear instructions for fixing
msg = "Attempted to enable policy enforcement, but feature is not supported on "
if arch not in POLICY_SUPPORTED_ARCHITECTURE:
msg += " architecture " + str(arch)
elif DISTRO_NAME not in POLICY_SUPPORTED_DISTROS_MIN_VERSIONS:
msg += " distro " + str(DISTRO_NAME)
else:
min_version = POLICY_SUPPORTED_DISTROS_MIN_VERSIONS.get(DISTRO_NAME)
if DISTRO_VERSION < min_version:
msg += " distro " + DISTRO_NAME + " " + DISTRO_VERSION + ". Policy is only supported on version " + \
str(min_version) + " and above."
else:
return # do nothing if platform is supported
raise PolicyError(msg)

def evaluate_query(self, input_to_check, query):
"""
Input_to_check is the input we want to check against the policy engine (ex: extensions we want to install).
Input_to_check should be a dict. Expected format:
{
"extensions": {
"<extension_name_1>": {
"signingInfo": {
"extensionSigned": <true, false>
}
}, ...
}
The query parameter specifies the value we want to retrieve from the policy engine.
Example format for query: "data.agent_extension_policy.extensions_to_download"
"""
# This method should never be called if policy is not enabled, this would be a developer error.
if not self.is_policy_enforcement_enabled():
raise PolicyError("Policy enforcement is disabled, cannot evaluate query.")

try:
full_result = self._engine.eval_query(input_to_check, query)
debug_info = "Rule file is located at '{0}'. \nFull query output: {1}".format(self._engine.rule_file, full_result)
if full_result is None or full_result == {}:
raise PolicyError("query returned empty output. Please validate rule file. {0}".format(debug_info))
result = full_result.get('result')
if result is None or not isinstance(result, list) or len(result) == 0:
raise PolicyError("query returned unexpected output with no 'result' list. Please validate rule file. {0}".format(debug_info))
expressions = result[0].get('expressions')
if expressions is None or not isinstance(expressions, list) or len(expressions) == 0:
raise PolicyError("query returned unexpected output with no 'expressions' list. {0}".format(debug_info))
value = expressions[0].get('value')
if value is None:
raise PolicyError("query returned unexpected output, 'value' not found in 'expressions' list. {0}".format(debug_info))
if value == {}:
raise PolicyError("query returned expected output format, but value is empty. Please validate policy file '{0}'. '{1}"
.format(self._engine.policy_file, debug_info))
# TODO: surface as a user error with clear instructions for fixing
return value
except Exception as ex:
msg = "Failed to evaluate query for Regorus policy engine: '{0}'".format(ex)
self._log_policy(msg=msg, is_success=False)
raise PolicyError(msg)

# TODO: Implement class ExtensionPolicyEngine with API is_extension_download_allowed(ext_name) that calls evaluate_query.
146 changes: 0 additions & 146 deletions azurelinuxagent/ga/policy/regorus.py

This file was deleted.

4 changes: 0 additions & 4 deletions tests/data/policy/agent-extension-data-invalid.json

This file was deleted.

Loading

0 comments on commit 99fd28f

Please sign in to comment.