Skip to content

Commit 30cfe17

Browse files
committed
set azuremonitoragent as primary group for azuremetricsext
1 parent 7572dd3 commit 30cfe17

3 files changed

Lines changed: 96 additions & 55 deletions

File tree

AzureMonitorAgent/agent.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,9 @@ def uninstall():
599599

600600
remove_localsyslog_configs()
601601

602+
if is_feature_enabled("enableAzureOTelCollector"):
603+
me_handler.remove_user(HUtilObj=HUtilObject)
604+
602605
uninstall_azureotelcollector()
603606

604607
# remove the logrotate config
@@ -1751,9 +1754,7 @@ def metrics_watcher(hutil_error, hutil_log):
17511754
is_lad=False,
17521755
managed_identity=managed_identity_str,
17531756
HUtilObj=HUtilObject,
1754-
is_local_control_channel=False,
1755-
user="azuremetricsext",
1756-
group="azuremonitoragent")
1757+
is_local_control_channel=False)
17571758
enabled_me_CMv2_mode, log_messages = me_handler.start_metrics_cmv2()
17581759
elif is_feature_enabled("enableCMV2"):
17591760
if os.path.exists(me_service_template_path):

AzureMonitorAgent/services/metrics-extension-cmv2.service

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ ExecReload=/bin/kill -HUP $MAINPID
1313
KillMode=control-group
1414
User=azuremetricsext
1515
Group=azuremonitoragent
16+
%SUPPLEMENTARY_GROUPS_LINE%
1617
RuntimeDirectory=azureotelcollector azuremetricsext
1718
RuntimeDirectoryMode=0755
1819

LAD-AMA-Common/metrics_ext_utils/metrics_ext_handler.py

Lines changed: 91 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@
6464
ArcACloudName: "armmanagement.autonomous.cloud.private"
6565
}
6666

67+
MeCMv2RuntimeDirectory = "/var/run/azuremetricsext"
68+
MeCMv2ServiceUser = "azuremetricsext"
69+
MeCMv2ServiceGroup = "azuremonitoragent"
70+
HimdsGroupName = "himds"
71+
6772

6873
def is_running(is_lad):
6974
"""
@@ -354,7 +359,7 @@ def get_ArcA_MSI_token(resource = "https://monitoring.azs"):
354359
return True, token_string, log_messages
355360

356361

357-
def setup_me_service(is_lad, configFolder, monitoringAccount, metrics_ext_bin, me_influx_port, managed_identity="sai", HUtilObj=None):
362+
def setup_me_service(is_lad, configFolder, monitoringAccount, metrics_ext_bin, me_influx_port, managed_identity="sai", HUtilObj=None, supplementary_grps=None):
358363
"""
359364
Setup the metrics service if VM is using systemd
360365
:param configFolder: Path for the config folder for metrics extension
@@ -382,6 +387,10 @@ def setup_me_service(is_lad, configFolder, monitoringAccount, metrics_ext_bin, m
382387
os.system(r"sed -i 's+%ME_MONITORING_ACCOUNT%+{1}+' {0}".format(me_service_path, monitoringAccount))
383388
os.system(r"sed -i 's+%ME_MANAGED_IDENTITY%+{1}+' {0}".format(me_service_path, managed_identity))
384389
os.system(r"sed -i 's+%ME_INFLUX_SOCKET_FILE_PATH%+{1}+' {0}".format(me_service_path, me_influx_socket_path))
390+
supplementary_groups_line = ""
391+
if supplementary_grps:
392+
supplementary_groups_line = "SupplementaryGroups={0}".format(supplementary_grps)
393+
os.system(r"sed -i 's+%SUPPLEMENTARY_GROUPS_LINE%+{1}+' {0}".format(me_service_path, supplementary_groups_line))
385394
daemon_reload_status = os.system("systemctl daemon-reload")
386395
if daemon_reload_status != 0:
387396
message = "Unable to reload systemd after ME service file change. Failed to set up ME service. Check system for hardening. Exit code:" + str(daemon_reload_status)
@@ -833,31 +842,21 @@ def get_metrics_extension_service_name(is_lad):
833842
return metrics_constants.metrics_extension_service_name
834843

835844

836-
def setup_me(is_lad, managed_identity="sai", HUtilObj=None, is_local_control_channel=True, user=None, group=None):
845+
def setup_me(is_lad, managed_identity="sai", HUtilObj=None, is_local_control_channel=True):
837846
"""
838847
The main method for creating and writing MetricsExtension configuration as well as service setup
839848
:param is_lad: Boolean value for whether the extension is Lad or not (AMA)
840849
:param is_local_control_channel: Boolean value for whether MetricsExtension needs to be run in `-LocalControlChannel` mode (CMv1 only)
841-
:param user: User that would own MetricsExtension process. If not specified, would default to the caller, in this case being root
842-
:param group: Group that would own MetricsExtension process. If not specified, would default to the caller, in this case being root
843850
"""
844851
_, config_folder = get_handler_vars()
845852
me_config_dir = config_folder + "/metrics_configs/"
846-
create_empty_data_directory(me_config_dir)
853+
setup_data_directory(me_config_dir)
854+
supplementary_grps = ""
847855

848856
if not is_local_control_channel:
849857
# CMv2 and related modes
850858
me_monitoring_account = ""
851-
if user and group:
852-
# Remove any previous user setup for MetricsExtension if it exists
853-
remove_user(user, HUtilObj=HUtilObj)
854-
# Create user/group for metrics-extension.service if it is requested
855-
ensure_user_and_group(user, group, create_if_missing=True, HUtilObj=HUtilObj)
856-
# For ARC, add user to himds group if it exists
857-
ensure_user_and_group(user, "himds", create_if_missing=False, HUtilObj=HUtilObj)
858-
# In CMv2 with user and group specified, create directory for MetricsExtension config caching
859-
me_config_dir = "/var/run/azuremetricsext"
860-
create_empty_data_directory(me_config_dir, user, group, HUtilObj=HUtilObj)
859+
me_config_dir, supplementary_grps = prepare_cmv2_service_runtime(HUtilObj=HUtilObj)
861860
else:
862861
# query imds to get the required information
863862
az_resource_id, subscription_id, location, az_environment, data = get_imds_values(is_lad)
@@ -953,16 +952,14 @@ def setup_me(is_lad, managed_identity="sai", HUtilObj=None, is_local_control_cha
953952
# setup metrics extension service
954953
# If the VM has systemd, then we use that to start/stop
955954
if metrics_utils.is_systemd():
956-
setup_me_service(is_lad, me_config_dir, me_monitoring_account, metrics_ext_bin, me_influx_port, managed_identity, HUtilObj)
955+
setup_me_service(is_lad, me_config_dir, me_monitoring_account, metrics_ext_bin, me_influx_port, managed_identity, HUtilObj, supplementary_grps=supplementary_grps)
957956

958957
return True
959958

960959

961-
def remove_user(user, HUtilObj=None):
960+
def remove_user(user=MeCMv2ServiceUser, HUtilObj=None):
962961
"""
963962
Removes existing user.
964-
Note: This is important as the older MetricsExtension might have created the user which needs to be removed.
965-
This mechanism can be removed in the future, if the user and group are maintained from MetricsExtension package.
966963
:param user: linux user
967964
:param HUtilObj: utility object for logging
968965
"""
@@ -978,7 +975,7 @@ def remove_user(user, HUtilObj=None):
978975
return
979976

980977
try:
981-
process = subprocess.Popen(['userdel', "-r", user], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
978+
process = subprocess.Popen(['userdel', '-r', user], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
982979
out, err = process.communicate()
983980
if process.returncode != 0:
984981
if HUtilObj:
@@ -988,21 +985,17 @@ def remove_user(user, HUtilObj=None):
988985
HUtilObj.log('Error while deleting user {0}: {1}'.format(user, e))
989986

990987

991-
def ensure_user_and_group(user, group, create_if_missing=False, HUtilObj=None):
988+
def ensure_group(group, create_if_missing=False, HUtilObj=None):
992989
"""
993-
Ensures if the user and group exists, optionally creating them if it does not exist.
994-
Group is checked, user is checked and then user is added to the group.
995-
Returns True if all of them are available (or created), else returns False.
996-
:param user: linux user
990+
Ensure the group exists, optionally creating it if it does not exist.
997991
:param group: linux group
998-
:param create_if_missing: boolean if true, create the requested user and group, where user belongs to the group
999-
:param HUtilObj: utility object for logging
992+
:param create_if_missing: boolean if true, create the requested group if it does not exist
1000993
"""
1001-
# Check/Create group if missing
1002994
try:
1003995
grp.getgrnam(group)
1004996
if HUtilObj:
1005997
HUtilObj.log('Group {0} exists.'.format(group))
998+
return True
1006999
except KeyError:
10071000
if create_if_missing:
10081001
try:
@@ -1014,6 +1007,7 @@ def ensure_user_and_group(user, group, create_if_missing=False, HUtilObj=None):
10141007
return False
10151008
if HUtilObj:
10161009
HUtilObj.log('Group {0} created.'.format(group))
1010+
return True
10171011
except Exception as e:
10181012
if HUtilObj:
10191013
HUtilObj.log('Error while creating group {0}: {1}'.format(group, e))
@@ -1027,16 +1021,24 @@ def ensure_user_and_group(user, group, create_if_missing=False, HUtilObj=None):
10271021
HUtilObj.log('Error while checking group {0}: {1}'.format(group, e))
10281022
return False
10291023

1030-
# Check/Create user if missing
1024+
1025+
def ensure_user_primary_group(user, group, create_if_missing=False, HUtilObj=None):
1026+
"""
1027+
Ensure the user exists and uses the provided group as its primary group.
1028+
If the user is missing and creation is allowed, create it with that primary group.
1029+
:param user: linux user
1030+
:param group: linux group
1031+
:param create_if_missing: boolean if true, create the requested user if it does not exist
1032+
"""
10311033
try:
1032-
pwd.getpwnam(user)
1034+
user_info = pwd.getpwnam(user)
10331035
if HUtilObj:
10341036
HUtilObj.log('User {0} exists.'.format(user))
10351037
except KeyError:
10361038
if create_if_missing:
10371039
try:
10381040
process = subprocess.Popen([
1039-
'useradd', '--no-create-home', '--system', '--shell', '/usr/sbin/nologin', user
1041+
'useradd', '--no-create-home', '--system', '--shell', '/usr/sbin/nologin', '-g', group, user
10401042
], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
10411043
out, err = process.communicate()
10421044
if process.returncode != 0:
@@ -1045,57 +1047,94 @@ def ensure_user_and_group(user, group, create_if_missing=False, HUtilObj=None):
10451047
return False
10461048
if HUtilObj:
10471049
HUtilObj.log('User {0} created.'.format(user))
1050+
HUtilObj.log('User {0} created with primary group {1}.'.format(user, group))
1051+
return True
10481052
except Exception as e:
10491053
if HUtilObj:
10501054
HUtilObj.log('Error while creating user {0}: {1}'.format(user, e))
10511055
return False
1052-
else:
1053-
if HUtilObj:
1054-
HUtilObj.log('User {0} does not exist.'.format(user))
1055-
return False
1056+
if HUtilObj:
1057+
HUtilObj.log('User {0} does not exist.'.format(user))
1058+
return False
10561059
except Exception as e:
10571060
if HUtilObj:
10581061
HUtilObj.log('Error while checking user {0}: {1}'.format(user, e))
10591062
return False
10601063

1061-
# Add user to group
10621064
try:
1063-
process = subprocess.Popen(['usermod', '-aG', group, user], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1065+
target_group = grp.getgrnam(group)
1066+
except Exception as e:
1067+
if HUtilObj:
1068+
HUtilObj.log('Error while checking group {0}: {1}'.format(group, e))
1069+
return False
1070+
1071+
if user_info.pw_gid == target_group.gr_gid:
1072+
if HUtilObj:
1073+
HUtilObj.log('User {0} already has primary group {1}.'.format(user, group))
1074+
return True
1075+
1076+
try:
1077+
process = subprocess.Popen(['usermod', '-g', group, user], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
10641078
out, err = process.communicate()
10651079
if process.returncode != 0:
10661080
if HUtilObj:
1067-
HUtilObj.log('Failed to add user {0} to group {1}. stderr: {2}'.format(user, group, err))
1081+
HUtilObj.log('Failed to set primary group {0} for user {1}. stderr: {2}'.format(group, user, err))
10681082
return False
10691083
if HUtilObj:
1070-
HUtilObj.log('User {0} added to group {1}.'.format(user, group))
1084+
HUtilObj.log('User {0} primary group set to {1}.'.format(user, group))
1085+
return True
10711086
except Exception as e:
10721087
if HUtilObj:
1073-
HUtilObj.log('Error while adding user {0} to group {1}: {2}'.format(user, group, e))
1088+
HUtilObj.log('Error while setting primary group {0} for user {1}: {2}'.format(group, user, e))
10741089
return False
10751090

1076-
if HUtilObj:
1077-
HUtilObj.log('User {0} added to group {1} (or already a member).'.format(user, group))
1078-
return True
10791091

1092+
def ensure_service_user_and_group(user, group, create_if_missing=False, HUtilObj=None):
1093+
"""
1094+
Ensure the service group exists and the service user uses it as the primary group.
1095+
:param user: linux user
1096+
:param group: linux group
1097+
:param create_if_missing: boolean if true, create the requested user and group if they do not exist
1098+
"""
1099+
if not ensure_group(group, create_if_missing=create_if_missing, HUtilObj=HUtilObj):
1100+
return False
1101+
1102+
return ensure_user_primary_group(user, group, create_if_missing=create_if_missing, HUtilObj=HUtilObj)
1103+
1104+
1105+
def prepare_cmv2_service_runtime(user=MeCMv2ServiceUser, group=MeCMv2ServiceGroup, HUtilObj=None):
1106+
"""
1107+
Prepare local runtime prerequisites for the CMv2 MetricsExtension service.
1108+
Returns the service data directory and any supplementary groups the unit should use at runtime.
1109+
"""
1110+
if not ensure_service_user_and_group(user, group, create_if_missing=True, HUtilObj=HUtilObj):
1111+
raise Exception("Failed to ensure user {0} with primary group {1}.".format(user, group))
1112+
1113+
supplementary_grps = []
1114+
if ensure_group(HimdsGroupName, HUtilObj=HUtilObj):
1115+
supplementary_grps.append(HimdsGroupName)
10801116

1081-
def create_empty_data_directory(me_config_dir, user=None, group=None, mode=0o755, HUtilObj=None):
1117+
setup_data_directory(MeCMv2RuntimeDirectory, user, group, clear_existing=False, HUtilObj=HUtilObj)
1118+
return MeCMv2RuntimeDirectory, " ".join(supplementary_grps)
1119+
1120+
1121+
def setup_data_directory(me_config_dir, user=None, group=None, mode=0o755, clear_existing=True, HUtilObj=None):
10821122
'''
1083-
Creates an empty data directory where MetricsExtension can store cached configurations.
1123+
Creates a data directory where MetricsExtension can store cached configurations.
10841124
For CMv1, MetricsExtension requires mdsd to provide all configurations on disk.
1085-
For CMv2, MetricsExtension requires an empty data directory where it can cache its configurations.
1125+
For CMv2, MetricsExtension requires a runtime data directory where it can cache its configurations.
10861126
'''
10871127
try:
1088-
# Clear older config directory if exists.
1089-
if os.path.exists(me_config_dir):
1128+
if clear_existing and os.path.exists(me_config_dir):
10901129
rmtree(me_config_dir)
1091-
os.makedirs(me_config_dir, mode=mode)
1130+
if not os.path.exists(me_config_dir):
1131+
os.makedirs(me_config_dir, mode=mode)
1132+
else:
1133+
os.chmod(me_config_dir, mode)
10921134

10931135
if user and group:
1094-
# Get UID and GID from user and group names
10951136
uid = pwd.getpwnam(user).pw_uid
10961137
gid = grp.getgrnam(group).gr_gid
1097-
1098-
# Set the ownership
10991138
os.chown(me_config_dir, uid, gid)
11001139

11011140
if HUtilObj:

0 commit comments

Comments
 (0)