clair镜像扫描的实现

一、前言

clair扫描的相关基础请先移步我的另外一篇文章镜像安全扫描工具clair与clairctl

这次我们采用clair api方式的扫描,基本思路是

  • 打包镜像
  • 解压tar包至tomcat的ROOT目录,得到每一个镜像的分层文件及描述文件
  • 调用每个镜像的分层描述文件
  • 使用clair api按照描述文件中提供的层序提交分层至clair中
  • clair依照漏洞-特征数据库进行比对
  • 使用clair api并携带最后一层的id,查询漏洞数据
  • 删除解压目录

二、启动容器

我们需要三个容器,分别是

  • Tomcat:给镜像的分层提供下载地址
docker run -d --name tomcat8 -p 6062:8080 -v /opt/deploy/clair/scanImage:/usr/local/tomcat/webapps/ROOT tomcat:jdk8-corretto
  • Postgres:存放漏洞-特征匹配数据
docker run -d -e POSTGRES_PASSWORD="password" -p 5432:5432 -v /opt/deploy/clair/postgres/data:/var/lib/postgresql/data --name postgres vmware/postgresql-photon:v1.5.0
  • Clair:扫描工具

新建目录/opt/deploy/clair/config,在该目录下新建文件config.yml

  database:type: pgsqloptions:source: postgresql://postgres:password@127.0.0.1:5432/postgres?sslmode=disablecachesize: 16384api:port: 6060healthport: 6061timeout: 900supdater:interval: 2hnotifier:attempts: 3renotifyinterval: 2h
docker run --net=host -d -p 6060-6061:6060-6061 -v /opt/deploy/clair/config:/config quay.io/coreos/clair:v2.0.1 -config=/config/config.yml

其中6060为扫描端口,6061为健康检查端口

注意,Postgres必须在clair之前启动,否则clair会连不上数据库,直接停止容器。

clair与postgres正常启动后,clair会自动去拉取漏洞数据插入数据库中,这一过程往往持续几个小时,且由于网络原因,大量漏洞数据无法拉取,此时需要另辟蹊径。


三、全部步骤

(1)检查工作

  • 判断该镜像所在的仓库是否正常,否则无法拉取该镜像
  • 判断tomcat、clair、postgres容器是否正常运行中

(2)调用脚本

    private String executeRemoteShell() {String result = null;SSH ssh = new SSH(主机ip, 用户名, 密码);try {boolean isConnect = ssh.connect();if (isConnect) {result = ssh.executeWithResult(脚本全路径,传入参数);}} catch (IOException e) {e.printStackTrace();}return result;}
#!/bin/bashbaseDir=/opt/deploy/clairif [ $? -eq 0 ];thendocker pull ${1} > /dev/null 2>&1imageName=`echo ${1} | sed 's!\/!_!g'`  > /dev/null 2>&1imageDir="${imageName}"  > /dev/null 2>&1mkdir ${baseDir}/scanImage/${imageDir}  > /dev/null 2>&1docker save -o ${baseDir}/scanImage/${imageDir}/${imageDir}.tar ${1}  > /dev/null 2>&1tar -xvf ${baseDir}/scanImage/${imageDir}/${imageDir}.tar -C ${baseDir}/scanImage/${imageDir}/ > /dev/null 2>&1cat ${baseDir}/scanImage/${imageDir}/manifest.jsonexit 0
elseecho 'scan image failed!'exit 6fi

(3)使用clair api提交镜像分层数据,postLayers为false时,此时已经提交过分层数据,可以利用最后一层的id直接查询漏洞。(postgres会将已经查询过的镜像漏洞存储,提交完分层数据后,可以使用最后一层的id获取漏洞数据)

//将镜像的分层文件提交至clair,进行分析并返回结果private ImageScanResult getScanResultFromClair(ImageScan imageScan, boolean postLayers) throws Exception {String imageName = imageScan.getImageName();String imageTag = imageScan.getImageTag();//1.获取manifest层序描述文件内容String result = RestTemplateUtils.get(getManifestPath(imageName, imageTag));JSONArray jsonArray = JSONArray.parseArray(result);if (jsonArray == null || jsonArray.size() == 0) {throw new Exception("无法获取或解析镜像层序的描述文件");}JSONObject jsonObject = (JSONObject) jsonArray.get(0);@SuppressWarnings("unchecked")List<String> layers = (List<String>) jsonObject.get("Layers");//提交的apiString clairApi = "http://" + systemConfig.getRelayServerIp() + ":" + systemConfig.getClairPort() + "/v1/layers";if (postLayers) {//上一层idString beforePath = "";//2.向clair逐层提交for (String layer : layers) {String name = layer.substring(0, layer.indexOf("/"));String path = getAbsoluteLayerPath(layer, imageScan.getImageName(), imageScan.getImageTag());String parentName = beforePath;String format = "Docker";beforePath = name;Map<String, Map<String, String>> postParameter = new HashMap<>();Map<String, String> layerParameter = new HashMap<>();layerParameter.put("Name", name);layerParameter.put("Path", path);layerParameter.put("ParentName", parentName);layerParameter.put("Format", format);postParameter.put("Layer", layerParameter);JSONObject layerJSONObject = JSONObject.parseObject(JSONObject.toJSONString(postParameter));RestTemplateUtils.post(clairApi, layerJSONObject, null);}}//3.所有层提交完毕后,使用最后一层进行查询漏洞int last = layers.size() - 1;String lastName = layers.get(last).substring(0, layers.get(last).indexOf("/"));String leak = RestTemplateUtils.get(clairApi + "/" + lastName + "?features&vulnerabilities");JSONObject leakJSONObject = JSONObject.parseObject(leak);JSONObject leakLayerJSONObject = leakJSONObject.getJSONObject("Layer");JSONArray featuresJSONArray = leakLayerJSONObject.getJSONArray("Features");List<Feature> featureList = new ArrayList<>();Feature feature;for (Object f : featuresJSONArray) {JSONObject featureJSONObject = (JSONObject) f;feature = new Feature();String name = featureJSONObject.getString("Name");String namespaceName = featureJSONObject.getString("NamespaceName");String versionFormat = featureJSONObject.getString("VersionFormat");String version = featureJSONObject.getString("Version");String addedBy = featureJSONObject.getString("AddedBy");List<Vulnerability> vulnerabilities = new ArrayList<>();JSONArray vulnerabilitiesJSONArray = featureJSONObject.getJSONArray("Vulnerabilities");if (vulnerabilitiesJSONArray == null || vulnerabilitiesJSONArray.size() == 0) {vulnerabilitiesJSONArray = new JSONArray();}Vulnerability vulnerability;for (Object v : vulnerabilitiesJSONArray) {JSONObject vulnerabilitiesJSONObject = (JSONObject) v;vulnerability = new Vulnerability();String vulnerabilityName = vulnerabilitiesJSONObject.getString("Name");String vulnerabilityNamespaceName = vulnerabilitiesJSONObject.getString("NamespaceName");String vulnerabilityDescription = vulnerabilitiesJSONObject.getString("Description");String vulnerabilityLink = vulnerabilitiesJSONObject.getString("Link");String vulnerabilitySeverity = vulnerabilitiesJSONObject.getString("Severity");String vulnerabilityFixedBy = vulnerabilitiesJSONObject.getString("FixedBy");vulnerability.setName(vulnerabilityName);vulnerability.setNamespaceName(vulnerabilityNamespaceName);vulnerability.setDescription(vulnerabilityDescription);vulnerability.setLink(vulnerabilityLink);vulnerability.setSeverity(vulnerabilitySeverity);vulnerability.setFixedBy(vulnerabilityFixedBy);vulnerabilities.add(vulnerability);}feature.setName(name);feature.setNamespaceName(namespaceName);feature.setVersionFormat(versionFormat);feature.setVersion(version);feature.setAddedBy(addedBy);feature.setVulnerabilities(vulnerabilities);featureList.add(feature);}//4.封装镜像扫描结果ImageScanResult imageScanResult = new ImageScanResult();int featureNum = featureList.size();long vulnerabilityNum = 0;long high = 0;long medium = 0;long low = 0;for (Feature temp : featureList) {List<Vulnerability> vulnerabilityList = temp.getVulnerabilities();vulnerabilityNum += vulnerabilityList.size();high += vulnerabilityList.stream().filter(v -> v.getSeverity().equals("High")).count();medium += vulnerabilityList.stream().filter(v -> v.getSeverity().equals("Medium")).count();low += vulnerabilityList.stream().filter(v -> v.getSeverity().equals("Low")).count();}imageScanResult.setImageName(imageScan.getImageName());imageScanResult.setImageTag(imageScan.getImageTag());imageScanResult.setFeatureNum(featureNum);imageScanResult.setVulnerabilityNum(vulnerabilityNum);imageScanResult.setHigh(high);imageScanResult.setMedium(medium);imageScanResult.setLow(low);imageScanResult.setFeatureList(featureList);return imageScanResult;}

(4)善后工作

清理扫描成功后的文件,这时我们打算清理解压出来的所有的tar包文件,否则解压目录将会异常庞大。博主在清理完一个24G的目录后,仅剩下几M的额外文件。

为什么我们不直接删除所有解压目录?这是因为我们需要留下镜像的分层描述文件(manifest.json),这个文件存放着该镜像的分层信息,包括分层id,分层tar包的地址等。后期我们可以依据此文件拿到最后一层的id,并调用clair api直接获取到该镜像的扫描结果。如果我们将扫描结果单独入库的话,那可以在扫描成功后删除整个解压目录。

    //扫描完成后删除镜像解压目录private void delImageDirAfterScan(ImageScan imageScan) {String imageIdentification = formatImageNameAndTag(imageScan.getImageName(), imageScan.getImageTag());String imageDir = systemConfig.getImageScanShellPath() + "scanImage/" + imageIdentification + "/";SSH ssh = new SSH(systemConfig.getRelayServerIp(), systemConfig.getRelayServerUser(), systemConfig.getRelayServerPwd());try {boolean isConnect = ssh.connect();if (isConnect) {ssh.execute("find " + imageDir + " -name '*.tar' -type f -print -exec rm -rf {} \\;");}} catch (IOException e) {e.printStackTrace();}}

【Docker】clair镜像扫描的实现相关推荐

  1. 使用clair镜像扫描

    文章目录 目的 安装clair 使用clair扫描镜像 Usage 使用docker镜像的klar扫描 镜像作为drone插件执行 目的 执行镜像扫描,扫描镜像仓库的镜像,生成报告 安装clair 操 ...

  2. Harbor集成Clair镜像安全扫描并手动导入漏洞数据

    通过这篇文章,你会了解到: harbor启停方法 clair镜像扫描原理 harbor数据库(MySQL)一览 clair数据库(PostgreSql)一览 harbor手动导入漏洞数据方法 背景 先 ...

  3. 【Docker】镜像安全扫描工具clair与clairctl

    镜像扫描结构图 方式2的具体操作步骤 clair是什么? clair是一个开源项目,用于静态分析appc和docker容器中的漏洞. 漏洞元数据从一组已知的源连续导入,并与容器映像的索引内容相关联,以 ...

  4. Harbor集成Clair镜像安全扫描原理探知

    上一篇文章中我们简单了解了Harbor集成Clair的安装方案及内网模式下CVE漏洞数据的手动导入功能.本篇文章,我们再梳理下漏洞扫描的具体原理和实现. 关于clair Clair是CoreOS 20 ...

  5. Harbor仓库镜像扫描原理

    harbor仓库中的镜像扫描这个功能,看似很高大上,其实等你了解了它的底层原理与流程,你就会发现就是做了那么一件事而已,用通俗的一句话概括,就是找到每个镜像文件系统中已经安装的软件包与版本,然后跟官方 ...

  6. Docker仓库管理镜像 -- 公共仓库【Docker Hub】和私人仓库【Registry】和【harbor】

    镜像仓库管理 docker仓库,用来管理镜像.主要分为公共仓库和私人仓库.下面介绍了公共仓库Docker Hub.私人仓库Registry和harbor. DockerHUb仓库管理 什么是Docke ...

  7. Nydus 镜像扫描加速

    文|余硕 上海交通大学22届毕业生 阿里云开发工程师 从事云原生底层系统的开发和探索工作. 本文 6369 字 阅读 16 分钟 GitLink 编程夏令营是在 CCF 中国计算机学会指导下,由 CC ...

  8. 生产中的12种容器镜像扫描最佳实践

    现在很多团队面临着这么一个挑战:如何在不减慢应用交付速度的情况下,管理好安全风险.有种方法可以解决该问题,就是采用安全的 DevOps 工作流程. 安全的DevOps(也称为DevSecOps)会在从 ...

  9. Docker容器镜像安全最佳实践指南

    文章目录: 0x02 Docker 容器安全最佳实践 1.主机安全配置 1.1 更新docker到最新版本 1.2 为容器创建一个单独的分区 1.3 只有受信任的用户才能控制docker守护进程 1. ...

最新文章

  1. php读取本地xlsx格式文件的数据并按json格式返回
  2. EntLib 3.1学习笔记(6) : Security Application Block
  3. Rust学习资料大全
  4. 计算机毕设最快多长时间,大学几年快结束了,计算机毕设到底该怎么做?
  5. 三星开出的57619美元年薪 却还是留不住千禧一代
  6. Element-UI-简单组合效果---Element-UI工作笔记002
  7. 开源文档管理系统LogicalDOC测试报告---安装篇
  8. autoresizingMask的用法
  9. Keil5二步解决中文乱码,注释乱码问题
  10. Xshell官网免费版下载实用
  11. Arduino手自两用蓝牙避障小车
  12. Git for Windows 国内下载站,发布
  13. Hexo博客主题安装及Next主题个性化修改
  14. 树莓派(zero w)——硬件介绍与系统开机
  15. 软件生命周期和开发模型
  16. 陈佩斯曾受邀喜剧综艺:被酬劳吓的恍惚好几天
  17. BUUCTF 每日打卡 2021-8-18
  18. 今日头条快手等大厂刨根问底之APP启动流程篇
  19. 聊聊互联网广告前世今生,你想不想要流量?反正我想!
  20. CSDN周刊:Google Cloud大规模宕机;中国正式进入 5G 商用元年!苹果发布SwiftUI

热门文章

  1. USB Redirector 6.1.1中文注册版(USB共享工具)
  2. iStylePDF安全电子文档解决方案之评标报告专家签字
  3. 股票自选股基本函数大全-2
  4. “互联网+”与我:今年有哪些新板眼
  5. 变电站运维云平台系统在台商大厦的设计与应用
  6. 一个好用的在线思维导图工具,拥有灵感快速画出精品思维导图
  7. 代码签名证书如何申请,有什么好处?
  8. IAM简介与常见的访问控制模型
  9. C/C++头文件汇总
  10. 楚留香打开服务器全部维护,​《楚留香》07月05日维护公告