Skip to content

Commit

Permalink
feat: Unix系统下支持无免密sudo权限账号安装agent (closed TencentBlueKing#1675)
Browse files Browse the repository at this point in the history
  • Loading branch information
chalice-1831 committed Nov 28, 2024
1 parent 847a5d7 commit 0f10866
Show file tree
Hide file tree
Showing 19 changed files with 2,050 additions and 126 deletions.
38 changes: 29 additions & 9 deletions apps/backend/agent/solution_maker.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,20 @@ def choose_script_file(cls, host: models.Host, is_execute_on_target: bool) -> st
return script_file_name

@staticmethod
def get_gse_extra_config_dir(os_type: str):
def get_gse_extra_config_dir(host: models.Host):
extra_config_sub_dir: str = "user_conf"
os_type: str = host.os_type
extra_dir: str = settings.GSE_ENVIRON_DIR
if os_type.upper() == constants.OsType.WINDOWS:
return json.dumps(PathHandler(os_type).join(settings.GSE_ENVIRON_WIN_DIR, extra_config_sub_dir))[1:-1]
extra_dir = settings.GSE_ENVIRON_WIN_DIR

if not host.ap.is_use_sudo:
extra_dir: str = host.ap.get_agent_config(os_type)["setup_path"]

if os_type.upper() == constants.OsType.WINDOWS:
return json.dumps(PathHandler(os_type).join(extra_dir, extra_config_sub_dir))[1:-1]
else:
return PathHandler(os_type).join(settings.GSE_ENVIRON_DIR, extra_config_sub_dir)
return PathHandler(os_type).join(extra_dir, extra_config_sub_dir)


class BaseExecutionSolutionMaker(metaclass=abc.ABCMeta):
Expand Down Expand Up @@ -271,6 +279,12 @@ def get_run_cmd_base_params(self) -> typing.List[str]:
f"-s {self.pipeline_id}",
]

if self.host_ap.is_use_sudo:
run_dir = f'GSE_AGENT_RUN_DIR={self.agent_config["run_path"]}'
data_dir = f'GSE_AGENT_DATA_DIR={self.agent_config["data_path"]}'
log_dir = f'GSE_AGENT_LOG_DIR={self.agent_config["log_path"]}'
run_cmd_params.append(f"-v {run_dir} {data_dir} {log_dir}")

# 系统开启使用密码注册 Windows 服务时,需额外传入 -U -P 参数,用于注册 Windows 服务,详见 setup_agent.bat 脚本
if self.need_encrypted_password():
# GSE 密码注册场景暂不启用国密,使用固定 RSA 的方式
Expand Down Expand Up @@ -304,7 +318,7 @@ def get_run_cmd_base_params(self) -> typing.List[str]:

return list(filter(None, run_cmd_params))

def add_sudo_to_cmds(self, execution_solution: ExecutionSolution):
def add_sudo_to_cmds(self, execution_solution: ExecutionSolution, is_use_sudo: bool = True):
# 非 Windows 机器使用 sudo 权限执行命令
# PAgent 依赖 setup_pagent.py 添加 sudo
# Windows Cygwin sudo command not found:Cygwin 本身通过 administrator 启动,无需 sudo
Expand All @@ -317,15 +331,16 @@ def add_sudo_to_cmds(self, execution_solution: ExecutionSolution):
):
return

sudo_cmd: str = "sudo " if is_use_sudo else ""
for execution_solution_step in execution_solution.steps:
if execution_solution_step.type != constants.CommonExecutionSolutionStepType.COMMANDS.value:
continue
for execution_solution_content in execution_solution_step.contents:
if execution_solution_content.name == "run_cmd":
shell_pkg: str = ("bash", "ksh")[self.host.os_type == constants.OsType.AIX]
execution_solution_content.text = f'sudo {shell_pkg} -c "{execution_solution_content.text}"'
execution_solution_content.text = f'{sudo_cmd}{shell_pkg} -c "{execution_solution_content.text}"'
else:
execution_solution_content.text = f"sudo {execution_solution_content.text}"
execution_solution_content.text = f"{sudo_cmd}{execution_solution_content.text}"

def combine_cmd_step(self, execution_solution: ExecutionSolution):
for execution_solution_step in execution_solution.steps:
Expand Down Expand Up @@ -366,7 +381,7 @@ def get_create_pre_dirs_step(self, is_shell_adapter: bool = False) -> ExecutionS

if not self.agent_setup_info.is_legacy:
# GSE 1.0 不需要创建额外配置目录
filepath_necessary_names.append(ExecutionSolutionTools.get_gse_extra_config_dir(self.host.os_type))
filepath_necessary_names.append(ExecutionSolutionTools.get_gse_extra_config_dir(self.host))

dirs_to_be_created: typing.Set[str] = {self.dest_dir}
for filepath_necessary_name in filepath_necessary_names:
Expand Down Expand Up @@ -533,7 +548,8 @@ def make(self) -> ExecutionSolution:
execution_solution: ExecutionSolution = self._make()
if self.is_combine_cmd_step:
self.combine_cmd_step(execution_solution)
self.add_sudo_to_cmds(execution_solution)

self.add_sudo_to_cmds(execution_solution, self.host_ap.is_use_sudo)
return execution_solution


Expand Down Expand Up @@ -587,7 +603,11 @@ def shell_cmd_adapter(
shell: str = "bash"
else:
shell: str = suffix
run_cmd = f"nohup {shell} {run_cmd} &> {self.dest_dir}nm.nohup.out &"

if self.host.os_type.lower() == backend_api_constants.OS.AIX:
run_cmd = f"nohup {shell} {run_cmd} > {self.dest_dir}nm.nohup.out 2>&1 &"
else:
run_cmd = f"nohup {shell} {run_cmd} &> {self.dest_dir}nm.nohup.out &"

curl_cmd: str = ("curl", f"{dest_dir}curl.exe")[self.host.os_type == constants.OsType.WINDOWS]
download_cmd = (
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/components/collections/agent_new/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,8 @@ def handle_report_data(self, host: models.Host, sub_inst_id: int, success_callba
untreated_healthz_result = base64.b64decode(untreated_healthz_result.encode()).decode()
except binascii.Error:
pass
except UnicodeDecodeError:
pass

# 去除可能存在的前缀
if untreated_healthz_result.startswith("healthz:"):
Expand Down
16 changes: 16 additions & 0 deletions apps/backend/components/collections/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ def request_single_job_and_create_map(
account_alias = (settings.BACKEND_UNIX_ACCOUNT, settings.BACKEND_WINDOWS_ACCOUNT)[
os_type == constants.OsType.WINDOWS
]

account_set: set = set()
for host in job_params["target_server"][host_interaction_from]:
if host_interaction_from == "host_id_list":
account = models.Host.objects.get(bk_host_id=host).identity.account
account_set.add(account)

if host_interaction_from == "ip_list":
account = models.Host.objects.get(inner_ip=host["ip"]).identity.account
account_set.add(account)

if len(account_set) > 1:
raise AppBaseException(_("目标机器账户不一致,请检查"))

account_alias = account_set.pop()

script_language = (constants.ScriptLanguageType.SHELL.value, constants.ScriptLanguageType.BAT.value)[
os_type == constants.OsType.WINDOWS
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def __post_init__(self):
]
)
],
extra_config_directory=ExecutionSolutionTools.get_gse_extra_config_dir(self.host.os_type),
extra_config_directory=ExecutionSolutionTools.get_gse_extra_config_dir(self.host),
),
context_dataclass.AccessConfigContext(
cluster_endpoints=",".join(
Expand Down
18 changes: 18 additions & 0 deletions apps/node_man/migrations/0084_accesspoint_is_use_sudo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.4 on 2024-08-26 08:39

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("node_man", "0083_subscription_operate_info"),
]

operations = [
migrations.AddField(
model_name="accesspoint",
name="is_use_sudo",
field=models.BooleanField(default=True, verbose_name="是否使用sudo"),
),
]
1 change: 1 addition & 0 deletions apps/node_man/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ class AccessPoint(models.Model):
proxy_package = JSONField(_("Proxy上的安装包"), default=list)
outer_callback_url = models.CharField(_("节点管理外网回调地址"), max_length=128, blank=True, null=True, default="")
callback_url = models.CharField(_("节点管理内网回调地址"), max_length=128, blank=True, null=True, default="")
is_use_sudo = models.BooleanField(_("是否使用sudo"), default=True)

@property
def file_endpoint_info(self) -> EndpointInfo:
Expand Down
2 changes: 2 additions & 0 deletions apps/node_man/serializers/ap.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class ListSerializer(serializers.ModelSerializer):
is_default = serializers.BooleanField(label=_("是否默认接入点,不可删除"))
proxy_package = serializers.JSONField(label=_("Proxy上的安装包"))
file_cache_dirs = serializers.SerializerMethodField(label=_("文件缓存目录"))
is_use_sudo = serializers.BooleanField(label=_("是否使用sudo"))

def to_representation(self, instance):
ret = super(ListSerializer, self).to_representation(instance)
Expand Down Expand Up @@ -117,6 +118,7 @@ class ZKSerializer(serializers.Serializer):
bscp_config = serializers.DictField(_("BSCP配置"), required=False)
outer_callback_url = serializers.CharField(label=_("节点管理外网回调地址"), required=False, allow_blank=True)
callback_url = serializers.CharField(label=_("节点管理内网回调地址"), required=False, allow_blank=True)
is_use_sudo = serializers.BooleanField(label=_("是否使用sudo"), required=False, default=True)

def validate(self, data):
gse_version_list: List[str] = list(set(AccessPoint.objects.values_list("gse_version", flat=True)))
Expand Down
86 changes: 86 additions & 0 deletions docs/solution/how_use_no_sudo_user_install_agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# 使用无免密sudo账户安装agent注意事项

*请确保使用无免密sudo权限账户安装agent的机器未曾使用过非该账户安装过agent*

*或在使用无免密sudo权限账户安装agent前将原agent卸载并清理痕迹*

```shell
# 基于默认接入点包括但不限于
/tmp/nm*
/tmp/xuoasefasd.err
/tmp/bkjob
/usr/local/gse
/var/run/ipc.stat
/var/log/gse
/var/run/gse
/var/run/ipc.state.report
/var/lib/gse
```

*请在配置接入点时确认agent的安装账户拥有hostid文件路径的读写操作权限*

hostid文件的路径默认为`Linux: /var/lib/gse/host Windows: c:/gse/data/host`

> 二进制部署版本中默认位于cmdb的/data/bkee/cmdb/server/conf/common.yaml文件eventServer-hostIdentifier中配置
>
> 容器化版本中默认位于cmdb的/data/bkhelmfile/blueking/environments/default/bkcmdb-values.yaml.gotmpl文件中的common-eventServer-hostIdentifier中配置
*请确保所用账户拥有一下系统程序操作权限*

```shell
/usr/bin/curl,
/usr/bin/mkdir,
/usr/bin/ls,
/usr/bin/cat,
/usr/bin/which,
/usr/bin/ping,
/usr/bin/echo,
/usr/bin/chmod,
/usr/bin/nohup,
/usr/bin/tail,
/usr/bin/ps,
/usr/bin/date,
/usr/bin/tee,
/usr/bin/uname,
/usr/bin/rm,
/usr/bin/awk,
/usr/bin/lsof,
/usr/bin/stat,
/usr/bin/readlink,
/usr/bin/grep,
/usr/bin/read,
/usr/bin/hash,
/usr/bin/timeout,
/usr/bin/bash,
/usr/bin/sed,
/usr/bin/chattr,
/usr/bin/cd,
/usr/bin/cp,
/usr/bin/wait,
/usr/bin/tr,
/usr/bin/wc,
/usr/bin/mktemp,
/usr/bin/seq,
/usr/bin/sleep,
/usr/bin/df,
/usr/bin/pidof,
/usr/bin/tar,
/usr/bin/gzip,
/usr/bin/pgrep,
/usr/bin/xargs,
```

## 接入点配置

接入点配置中 `hostid路径` 必须与上述 cmdb 的配置文件中指定的路径一致

创建接入点后根据接入点页面中显示的接入点id到节点管理数据库中执行下述语句

```sql
use bk_nodeman;
update node_man_accesspoint set is_use_sudo=0 where id={接入点页面中显示的id};
```

## 作业平台

作业平台需要新建该无免密sudo权限的账户的执行账户
Loading

0 comments on commit 0f10866

Please sign in to comment.