freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

sonarqube使用指北(三)-编写代码进行自动化扫描
2024-05-28 14:03:19

一、引言

上一篇文章之后 我们应该已经成功完成的配置了扫描环境并执行了一次基本的本地扫描,但是之前的手动扫描需要我们每一次都手动切换到代码目标并手动执行扫描命令,效率很低。在代码库较大的情况下会占用大量的时间。这一章我们会通过编写python代码的形式来实现自动化的本地扫描。
本篇需要使用的基本工具

  • python3
  • linux下的shell脚本
  • crontab定时任务

二、代码创建扫描项目

sonarqube官方提供了大量的api接口供我们使用,我们可调用这些接口来实现自动化创建。
图片.png
通过这个地方就能查看当前安装版本支持的各种api接口,这里我们需要调用/api/projects/create
图片.png

  • mainBranch为项目分支,非必填,默认为main分支
  • name为项目的名字,必填,可重复
  • project为项目的唯一标识,必填,不可重复
  • visibility为项目的公开属性,用于项目管理,非必填,默认为公开,即所有sonarqube用户都能查看该项目

代码示范,一次成功的项目创建实现如下:

user ='admin'
password = 'admin'
sonarqube_url ='url'
#创建sonarqube项目
def create_sonarqube_project(sonarqube_url, project_name, username, password):
    api_endpoint = sonarqube_url + "/api/projects/create"
    data = {
        "project": project_name,
        "name": project_name
    }
    try:
        response = requests.post(api_endpoint, data=data, auth=HTTPBasicAuth(username, password))
        response.raise_for_status()  # 抛出异常如果响应不是 200 OK
        print(f"项目 '{project_name}' 创建成功!")
        return True
    except requests.exceptions.RequestException as e:
        print("创建项目失败:", e)
        return False
  • 创建项目同样需要授权,这里我们使用了auth=HTTPBasicAuth(username, password) 来实现,这里的账号信息就是对应sonarqube的账号密码,当然你也可以通过生成令牌的方式来实现授权,这里没有列举。
  • 这里的代码中为了方便查看,将项目的name和key设置为同样的值
  • 如果项目创建成功,会返回200
  • 如果认证错误,会返回401
  • 如果项目存在,会返回400

三、代码执行本地扫描

#调用sonarqube扫描mvn代码
def exec_mvn_scan(path,project):
    command =f'''mvn clean verify sonar:sonar \
  -Dsonar.projectKey={project} \
  -Dsonar.host.url=url \
  -Dsonar.login=sqa_2cb36aeb59566dbc633bfc10b27371c732******
    '''
    working_dir = path
    result = subprocess.run(command, cwd=working_dir, shell=True)
    # 检查执行结果
    if result.returncode == 0:
        print("命令执行成功!")
    else:
        print("命令执行失败!")
  • path的值为项目所在的根目录
  • subprocess执行系统命令会输出命令的执行结果,如果失败了 可以分析一下具体的错误原因,多半是由于配置错误导致的
  • Dsonar.host.url 的值为sonarqube的访问地址
  • Dsonar.login的值为文章二中创建的项目令牌(不是用户令牌)
  • Dsonar.projectKey的值为上一步创建项目时输入的唯一key
  • 如果命令成功的话,打开sonarqube就会获得执行结果。
  • 这里是不需要执行登录操作,因为命令中的项目令牌已经包含了代码扫描权限。
    图片.png
    上面是以maven配置的代码来举例的,sonarqube支持的其他几种代码配置环境扫描代码也放在下方 供大家按需拿取。
#调用sonarqube扫描gradle代码
def exec_gradle_scan(path,project):
    command =f'''./gradlew sonar \
  -Dsonar.projectKey={project} \
  -Dsonar.host.url=url \
  -Dsonar.login=sqa_2cb36aeb59566dbc633bfc10b27371c732******
    '''
    working_dir = path
    result = subprocess.run(command, cwd=working_dir, shell=True)
    # 检查执行结果
    if result.returncode == 0:
        print("命令执行成功!")
    else:
        print("命令执行失败!")

#调用sonarqube扫描.net代码
def exec_net_scan(path,project):
    command =f'''./gradlew sonar \
  -Dsonar.projectKey={project} \
  -Dsonar.host.url=url \
  -Dsonar.login=sqa_2cb36aeb59566dbc633bfc10b27371c732******
    '''
    working_dir = path
    result = subprocess.run(command, cwd=working_dir, shell=True)
    # 检查执行结果
    if result.returncode == 0:
        print("命令执行成功!")
    else:
        print("命令执行失败!")

#调用sonarqube扫描非maven、gradle、net类型的代码
def exec_other_scan(path,project):
    command =f'''sonar-scanner \
  -Dsonar.projectKey={project} \
  -Dsonar.sources=. \
  -Dsonar.host.url=url \
  -Dsonar.login=sqa_2cb36aeb59566dbc633bfc10b27371c732******
    '''
    working_dir = path
    result = subprocess.run(command, cwd=working_dir, shell=True)
    # 检查执行结果
    if result.returncode == 0:
        print("命令执行成功!")
    else:
        print("命令执行失败!")

四、代码执行批量扫描

前面2步我们已经通过代码实现了项目自动创建和自动扫描的操作,现在我们需要将2者结合起来。
假设这样一个场景:

  • 项目备份代码存在本地。
  • 项目按照不同的业务线进行分类,不同的业务线分别有各自的文件夹。
    图片.png
    文件目录结构如上图所示,我们可以构造一个简单的文件遍历函数并结合前2步的代码来执行一次完整的代码库批量扫描
path =  '/代码库'
def main(path):
    businesses = os.listdir(path)
    for business in businesses:
        biz_path = os.path.join(path,business)
        if os.path.isdir(biz_path):
            projects = os.listdir(biz_path)
            for project in projects:
                pro_path = os.path.join(biz_path,project)
                if os.path.isdir(pro_path):
                    create_sonarqube_project(sonarqube_url, project, user, password)
                    if os.path.isfile(pro_path+'/pom.xml'):
                        conf_pom(pro_path+'/pom.xml')
                        exec_mvn_scan(pro_path,project)
                    elif os.path.isfile(pro_path+'/build.gradle'):
                        conf_gradle(pro_path+'/build.gradle')
                        exec_gradle_scan(pro_path,project)
                    elif os.path.isfile(pro_path+'/.net'):
                        exec_net_scan(pro_path,project)
                    else:
                        exec_other_scan(pro_path,project)
  • path对应的是树状图中的代码库目录地址
  • 该函数会检查不同项目中的配置文件名称来选择合适的扫描函数
  • 相关的全局变量在之前几步中已包含 这里就不再重复了。

至此一次完整的针对公司业务代码库的批量本地扫描就完成了。后面还会介绍一些其他的一些补充内容,有需要的可以自行观看~

五、pom.xml文件的配置问题

上文中的代码已经可以成功的执行批量的代码扫描了,但是在实际业务中,项目的pom.xml文件并没有正确的配置代码仓库,需要我们手动添加。接下来我们会通过代码来实现这个操作。
在第二篇文章中 我们在配置maven仓库中给出了需要在xml文件中添加的内容。

<repositories>
        <repository>
            <id>nexus-public</id>
            <url>https://url/repository/maven-public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
                <updatePolicy>always</updatePolicy>
                <checksumPolicy>fail</checksumPolicy>
            </snapshots>
        </repository>
    </repositories>

下面是具体的实现代码。

namespace_uri = ''

def traverse_xml(element, depth=0):
    # 打印当前元素的标签
    #print('  ' * depth + element.tag)
    if 'repositories' in element.tag:
        global namespace_uri 
        namespace_uri = element.tag
        return 
    # 遍历当前元素的子元素
    for child in element:
        traverse_xml(child, depth + 1)

#配置pom.xml文件 增加内部环境配置
def conf_pom(path):
    # 解析 POM 文件
    tree = ET.parse(path)
    root = tree.getroot()
    # 检查根元素是否有xmlns属性,这通常用于定义默认命名空间
    traverse_xml(root)
    print(namespace_uri)
    # 查找所有 <repositories> 元素 
    if namespace_uri != '':
        return
    else:
    # 如果找到至少一个 <repositories> 元素,返回 True
    # 创建要插入的 <repositories> 元素及其子元素
        repositories = ET.fromstring('''
        <repositories>
            <repository>
                <id>nexus-public</id>
                <url>https://nexus.xxx.com/repository/maven-public/</url>
                <releases>
                    <enabled>true</enabled>
                </releases>
                <snapshots>
                    <enabled>true</enabled>
                    <updatePolicy>always</updatePolicy>
                    <checksumPolicy>fail</checksumPolicy>
                </snapshots>
            </repository>
        </repositories>
        ''')
        root.append(repositories)
        # 移除命名空间前缀
        for elem in root.iter():
            if '}' in elem.tag:
                elem.tag = elem.tag.split('}', 1)[1]
        # 创建 ElementTree 对象,并指定默认的命名空间前缀为 None
        tree = ET.ElementTree(root)
        # 将修改后的 XML 写入新文件中
        tree.write(path, encoding='utf-8', xml_declaration=True,default_namespace='')

这段代码演示了如何检测缺少repositories的xml文件,并给他添加需要的参数。
正常情况下我们会通过root.find('.//repositories') 来搜索repositories,但是如果xml文件中声明了命名空间,该函数就无法正常匹配结果。因此这里使用了traverse_xml(element, depth=0) 来遍历所有的xml节点实现查找。
输出如图,可以看出,xml文件中的元素实际被包含在{http://maven.apache.org/POM/4.0.0}的命名空间之中
图片.png
如果想通过自带的正则匹配方法应该选用

root.find('.//{http://maven.apache.org/POM/4.0.0}repositories')

我们也可以注意到代码中还包含移除命名空间前缀的操作,因为我们通过代码直接插入操作之后,会给所有的节点显式ns0的命名空间,需要主动删除。

到这里sonarqube相关的使用介绍就到此结束了,后续如果还有深入操作的话,还会继续进行更新,感谢大家观看~

# 数据安全 # 代码安全 # SonarQube # 静态代码检测
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者