长亭实习(四)-写基线检查的一些反思

长亭实习(四)-写基线检查的一些反思

最近这段时间一直在写基线检查的部分插件代码。首先说一下写基线检查的顺序,基线检查本身是照着cis的文档,先将每一个检查项的md文档放进去用lint-md检查格式,再写每一个检查项的lua代码,然后写Py代码测试单个检查项,测试按照 通过-不通过-通过 来进行。最后所有完成后再统一跑测试。

但是因为我一开始没有按照顺序写导致踩了很多本可以避免的坑,效率和进度也慢了很多。这也算是对自己做的一次检讨吧。

因为安排的任务是每个人写两章,总的检查项数量也不算少。我原本想尽可能写快点,感觉写一个检查项又写一个测试比较耽误时间。所以我打算把所有检查项的插件代码写完再统一写测试。

然而正是这个错误的决定导致了浪费大量不必要的时间。因为当我写完所有的插件代码后再写测试的时候,发现对前面的检查项已经开始有些遗忘,又开始看前面的代码。其次写了测试后才发现之前写的代码有存在错误的地方,这个时候再debug发现真的不如重新写一次。

另外还有用lint-md检查md格式的问题,当我一次性把所有的文档放进去检查的时候,报了两百多个错误,一个接一个地debug,心态都崩了Orz。倘若一开始按照顺序按部就班的写其实这些完全都是可以避免的。

反思一:有时候看似取巧的捷径,反而会把时间浪费在不必要的地方。项目开发中一切照顺序进行才是最合理的。

其次,来长亭之前我一直没有参与过企业项目的开发,亦或者是团队小项目的开发。之前一直都是自己挖挖洞,写点poc/exp什么的。对git很陌生,第一次听到李扬师傅给我说让我rebase master代码的时候,我感到很茫然。

反思二:熟练使用git真的很重要!!!

虽然git并不难,但是我还是在项目开发过程中犯了很多的错,有一次不小心切到了别的分支,以为还在自己分支,本想提交自己的代码,push -f 覆盖了另一位师傅合并之后的代码,还好及时解决了,自己也觉得很不好意思。要是平时自己多用github练练手,也不至于犯这些低级的错误。当然我觉得犯错也是件好事,说明自己又能进步了。

反思三:多思考,想清楚后再开始写。

我觉得这个也是很多人遇到的问题,一来就动手写代码,写到一半又发现自己的思路好像哪里不对。然后又把前面的代码推倒重来,这个我偶尔也会犯,其实浪费了很多时间写着无意义的代码。思考清楚后再写,自己思路也清晰写起来也快,这样才能事半功倍。

反思四:多看看别人的代码,其实写起来没这么复杂。

这个李扬师傅是在review我们代码的时候说的。因为在一个大型项目开发的过程中,你遇到的问题往往其他同事之前也遇到过,所以也会造很多轮子。我在写的过程中发现有点写起来很复杂,其实师傅早就把这些轮子造好了,直接拿来用就行。而我事先没有看他们的代码,自己写的时候又把轮子造了一遍,是觉得自己一直都在写效率还那么低,看来确实方法不对。

反思五:插件代码中尽可能减少使用命令执行的方式。

这个主要是针对插件部分,因为我们插件是以cloudwalker-plugin作为客户端agent跑在用户的机器上来运行的。以下面这个检查项为例。

根据该检查项的要求可知要求/var/log目录下所有的文件,group没有写和执行的权限,other没有任何权限。

而我就局限在了文档给的思路,直接用find命令来检查,下面是我之前的代码。

function registry.ensure_permissions_on_all_logfiles_are_configured(): result_t
    return subprocess.command_match_then_pass("/var/log","find","-type"
    ,"f","!","-perm","600","!","-perm","400","!","-perm","640","-ls")
end

很显然我的思路就是执行find命令找出目录下不是600,400和640的文件,除开这些如果命令执行的结果匹配到了,那么检查项就不通过。这样看似没什么问题。

那么为什么不要用这种方式呢?

因为根据检查项的要求,只要匹配到一个权限不符合的,那么检查项就不通过了。但是采用命令执行的方式,必须等待所有的匹配完后才能返回结果,而这是不必要的。设想如果这个目录下的文件特别多,每次执行都会占据大量时间和cpu,等待命令执行完成更是没有意义的。

其次执行命令是采用启用子进程的方式进行的,会存在权限管控以速率的问题。因为我们的agent本身是封装的golang的模块,会对执行的函数进行控制,而启用的子进程却无法控制到。

李扬师傅给我举了一个例子,同样读取一个G的文件,新起一个子进程可能占用大量内存和cpu瞬间完成读取,而用封装的函数如file.read(),实际读取地可能会慢很多,这就是在受控制的范围内。同样还有父进程的权限在非root的情况下,子进程可能连读取的权限都没有,自然执行命令不可能得到想要的结果。

下面是在review后我修改了的代码。

function registry.ensure_permissions_on_all_logfiles_are_configured(): result_t
    local file_info,err = filepath.glob("/var/log/*")
    if err ~= nil then
        return result.error(err)
    end
    for _,v in ipairs(file_info) do
        if not fileutil.path_is_dir(v) then
            local file_stat, err = file.stat(v)
            if err ~= nil then
                return result.error(err)
            end
            local file_perm = file_stat.perm()
            local p = fileutil.parse_perm(file_perm)
            if p.group.w or p.group.x or p.other.r or p.other.w or p.other.x then
                return result.failf("fail output: check %s permission is %s", v, file_perm)
            end
        end
    end
    return result.pass()
end

通过glob函数来遍历目录,只要有一个不符就返回fail。相比执行命令的方式科学多了。因为本身跑在客户的机器上,作为插件部分就要尽可能减少对cpu的占用,不然用户机器跑挂了,就要在项目组里发红包(惩罚)了,哈哈。

总结:虽然这段时间虽然踩了很多坑,但是感觉自己进步真的蛮快的。

zgao

如果有什么技术上的问题,可以加我的qq 1761321396 一起交流。