安全开发-Nexus
在内网 DevSecOps 体系里部署 Nexus 制品仓库:讲清制品 / 代理 / sumdb 概念与搭建过程,给出 GOPROXY/GOSUMDB 完整用法,以及流水线下载依赖、SonarScanner、Go 工具链、Docker 构建 apt 源的联动 内网 DevSecOps 体系搭到这一步,还差一个更根本的问题:内网到不了公网,开发需要的一堆“外网的东西“从哪来? Go 依赖、SonarScanner 可执行文件、某个特定版本的 Go 编译器——默认全都去公网下载,内网全都不通。 这篇在内网部署 Nexus 制品仓库( 源代码是自己写的,但把程序跑起来还要从外部下载一堆“拿来就能用的成品“:Go 第三方模块(example 项目有 16 个,含 gin 框架)、SonarScanner 可执行文件、特定版本的 Go 编译器(工具链, 代理仓库(proxy repository) 就是架在客户端和公网之间的缓存层:客户端请求某个制品,本地没有就去 上游(upstream,公网源站) 取一份、缓存下来再返回;第二次再有人要同一个制品,直接命中缓存。对内网的三重价值: Nexus Repository(3.x) 是 Sonatype 的开源制品仓库,支持 Go 模块、Maven、npm、Docker 镜像以及 raw(通用文件) 等格式。本文只用三种能力:Go 模块代理, debian源 和 raw 文件代理(Docker 镜像功能不开——镜像归 Harbor 管)。 仓库分三种核心类型: 本文建的 6 个仓库全是 Proxy;Hosted/Group 是“下一步“(见小结)。 Harbor 管容器镜像,Nexus 管代码级制品,两个仓库分工明确。 版本:Nexus 3.96.1 / nxrm-ha chart 96.1.0,PostgreSQL 18.4 / bitnami chart 18.7.9,经 Rancher + Helm 部署。 部署分四块,顺序是“先数据库、后 Nexus“(没有库连,Nexus 起不来): 添加 chart 源(Rancher 界面)。 地址填 daocloud 镜像源 图 1:Rancher「添加 Chart 仓库」页:经 daocloud OCI 镜像源(oci://docker.m.daocloud.io/bitnamicharts)添加 PostgreSQL 的 chart 仓库 安装并改关键项。 商店里找到 图 2:bitnami postgresql chart 18.7.9 的安装页(安装进 middleware 命名空间) 默认值大部分不用动,只改下面几处( 读安装输出。 安装命令末尾输出: 两个点: 怎么验证: 同样在 Rancher 加一个 chart 源,地址是 Sonatype 官方 图 3:Sonatype 官方 nxrm-ha chart 96.1.0 的安装页(对应 Nexus 3.96.1,装进 sonatype 命名空间) chart 默认值是按“云上生产“给的(16 核 16G、3 副本),本环境节点小,按实际缩水;同时把“连外部数据库 + 内网域名 + 本地存储“三件事配掉: 安装完成后,取回 admin 密码: 然后浏览器打开 图 4:安装成功后浏览器进入的 Nexus Dashboard(Usage Center)首页——管理界面的入口 Nexus 的登录也接入了 Gitea OIDC 体系:开发者用 Gitea 账号登录一次,进 Nexus 的 Web 界面不用再输密码。 建仓库之前先认识 Blob Store:Nexus 里“仓库“是目录 + 元数据,真正的文件内容存在 Blob Store 里。每个仓库单独建一个:文件独立存放,磁盘可以按仓库单独扩容、迁移,某个仓库换盘/换机器时只动它自己的目录——这也是官方 chart 的推荐布局(每个 Blob Store 是 操作(Nexus 界面): 图 5:安装成功后的 Blob Stores 页:列表为空,需要手工创建存储库 图 6:创建 Blob Store:类型 File、名称 goproxy、路径 /nexus-data/blobs/goproxy(数据根目录下的子目录) 图 7:Blob Stores 列表:go-sumdb 与 goproxy 均为 Started 状态 顺序提醒:Blob Store 必须先于仓库创建——建仓库时要在已有的 Blob Store 里选一个,没建就没得选。 Repositories 页同样是空列表起步: 图 8:Repositories 页:空列表,准备创建第一个代理仓库 点 Create Repository,第一个要选的是 Recipe(配方):Nexus 为每种制品格式预置了“表单模板“——选 go 就是 Go 模块的表单,选 raw 就是通用文件的表单。本部署共建 6 个仓库: goproxy(Go 模块代理)。 Recipe 选 go (proxy)—— 图 9:Create Repository:Recipe 下拉选择 go (proxy)——Go 模块代理的专用模板 填写:名称 图 10:创建 goproxy 代理仓库:上游填 https://goproxy.cn,Firewall 选 Disabled(本环境没有 IQ 服务) 上游为什么是 go-sumdb(校验数据库代理)。 Go 下载模块后,为了防“代理递给你一个被换过的包“,会到校验数据库(sumdb,checksum database)里核对模块的哈希(能唯一识别文件内容的摘要值,内容变一个字节哈希就变)——官方数据库是 图 11:Create Repository:Recipe 下拉选择 raw (proxy)——通用文件代理,这里用于 sumdb 查询 填写:名称 图 12:创建 go-sumdb 代理仓库:上游填 https://goproxy.cn/sumdb/sum.golang.org,Strict Content Type Validation 未勾选 验证代理生效。 打开 goproxy 仓库的 Browse 页,模块已经列出来了: 图 13:goproxy 仓库的 Browse 页:pelletier/go-toml v2.2.2(example 项目的间接依赖)已缓存在库中——代理生效的直接证据 这个模块此前在 Nexus 里不存在,是这次请求从 goproxy.cn 取回来缓存的,再跑一次秒回——全部命中缓存。 go-dev-dl(Go 工具链代理)。 CI/CD 里装 Go 经常要下载不同版本的工具链( 图 14:创建 go-dev-dl 代理仓库:Recipe raw (proxy),上游填 https://go.dev/dl/(Go 官方工具链下载目录) 建完浏览, 图 15:go-dev-dl 的 Browse 页:go1.26.3 工具链压缩包已缓存 sonar-scanner-cli(扫描器安装包代理)。 SonarQube 流水线要下载 SonarScanner CLI 可执行文件,官方站 图 16:创建 sonar-scanner-cli 代理仓库:上游填 https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/ 浏览可见扫描器包已缓存: 图 17:sonar-scanner-cli 的 Browse 页:sonar-scanner-cli-8.1.0.6389 安装包已缓存(流水线 scannerFile 引用的就是它) 这个仓库在权限上比较特别:为了让流水线免凭据下载,给 anonymous 角色(“未登录“身份的统称)授予了该仓库的读权限(权限 ID debian / debian-security(apt 源代理)。 构建镜像基于 debian bookworm,Dockerfile 里 apt 装包,官方源 六个仓库建完,Repositories 列表的最终状态: 图 18:Nexus Repositories 列表: 把 Go 项目指向 Nexus = 把下面四个环境变量配正确: 层级一:临时(当前终端)——shell 环境变量,先试通再固化: 层级二:持久(本用户所有 go 命令,推荐)—— GOSUMDB 的两种写法(最容易绕晕的一点,单独说): 关于 GOPROXY 末尾的 以 example 项目为例(16 个依赖,含 gin 框架): 再回 goproxy 仓库的 Browse 页看一眼,缓存已经备齐: 图 19:goproxy 的 Browse 页:代理缓存的模块路径列齐—— example 的 16 个依赖全是公开模块,所以 声明私有模块路径: 填的是“模块路径的域名“,不一定是 Web 域名。 模块路径是 Go 项目引用一个模块时,在 git 认证:两种写法 跳过代理之后,go 靠 git 从源码仓库取私有模块——于是需要凭据。本环境(Gitea 托管)两种写法都可行: 怎么选:长期跑在机器上的自动化(开发机、Runner)推荐 SSH——公钥没有“过期“一说,一台机器一把钥匙、互不影响;HTTPS 令牌更简单(一行配置),但令牌是长期凭据,要按密码的纪律保管(泄露即轮换)。流水线里要取私有模块,思路不变:job 级 env 设 第二步(看完报错必做): 第三步(客户端排完才走这步):到服务端查——Nexus 的 goproxy Browse 页(图 13)看该模块有没有新条目;代理本身报错的话,再看仓库的上游配置。 三条现有流水线( 实际文件的相关片段(已入库 example 仓库): 重点在 实际文件的相关片段(已入库 example 仓库): 三个值得注意的点:一是全程没传任何用户名密码——靠的是 2.5 给这个仓库开的匿名读权限,流水线侧比“机器账号“更省事(且该仓库只存公开扫描器包,无敏感内容);二是 场景:任务容器自带默认 Go 版本(1.25.14);如果某个项目要更新的版本(比如 1.26.3),流水线需要现场安装。 现状说明:example 仓库的三条流水线目前没走这条路——任务容器已预装 Go,Docker 构建拉 golang 镜像走 Harbor 镜像代理、依赖走预烤缓存。go-dev-dl 仓库属于“先建好、要用即用“,图 15 的 构建镜像(debian bookworm 基座)在 Dockerfile 里 apt 装包,官方源内网不通。Debian 12 的源文件是 deb822 格式的 两个细节: 效果和其余仓库一样:apt 请求的索引文件与 .deb 从 Nexus 取,首次取回缓存、第二次构建命中本地。两个前提:构建阶段信任内网 CA(https 源要过 TLS 校验);apt 不带凭据,所以这两个仓库开了匿名读。 回看六个仓库,它们背后的模式是同一个:凡是“公网来的“,先取回内网缓存一份,再用: Harbor 管容器镜像、Nexus 管代码级制品,两个仓库分工明确,内网的依赖下载通道就此齐了。 这一篇给内网 DevSecOps 体系补上了依赖下载通道:Nexus 制品仓库(3.96.1)装进 k3s,配好六个代理仓库( 下一步可选:建 Hosted 仓库发布内部 Go 模块与内部二进制包(团队的“自己的仓“)、用 Group 仓库把“代理 + 托管“聚合成统一入口(客户端 GOPROXY 只配一行);再往后可以接 Nexus 的 OIDC 登录细化与漏洞扫描能力(Nexus 有内置漏洞扫描插件,商业版)。至此内网各平台就位:Gitea(代码)→ Harbor(镜像)→ Nexus(制品)→ SonarQube(代码质量)→ Dependency-Track(依赖安全)。 前言
https://nexus.kali-team.internal,3.96.1):Nexus 替你去上游取一份制品、缓存在本地,第二次起就不用出网了。全文按“概念 → 搭建 → 使用 → CI/CD 联动“展开。 1 背景概念
1.1 制品与代理仓库
go/gofmt 等命令打包成的发行包)。这类统称制品(artifact)。 1.2 Nexus 与三种仓库类型
仓库类型 说明 Proxy(代理) 代理外部仓库,缓存下载的制品 Hosted(托管) 存储内部私有制品 Group(组) 聚合多个仓库,提供统一访问入口 1.3 Nexus 在体系里的位置
使用方 下载什么 Nexus 仓库 配置在哪 Go 构建(Dockerfile / 本地) Go 模块依赖(gin 等) goproxyGOPROXY 环境变量Go 模块完整性校验 sumdb 校验数据 go-sumdbGOSUMDB 环境变量(可选)需要特定 Go 版本的流水线 Go 工具链压缩包 go-dev-dlsetup-go 的 go-download-base-urlSonarQube 流水线 SonarScanner CLI 安装包 sonar-scanner-clisonarqube.yaml 的 scannerBinariesUrlDockerfile apt 装包(构建镜像) Debian apt 包(索引文件 + .deb) debian / debian-securityDockerfile 的 sed 源改写 2 搭建
2.1 总体思路:要哪些组件
middleware(中间件)命名空间单装一个共享 PostgreSQL,后面还被 Dependency-Track 复用;sonatype 命名空间;nexus.kali-team.internal,证书由内网 CA 签发。 2.2 安装 PostgreSQL(共享数据库)
oci://docker.m.daocloud.io/bitnamicharts(OCI 是容器镜像的发布格式规范,Helm chart 现在也能打成 OCI 产物放在镜像仓库兼容的地址上——daocloud 把 bitnami 的 chart 同步到这个地址,内网可达),名称起 postgresql:
postgresql chart,版本 18.7.9(对应的 PostgreSQL 实际版本是 18.4,见下面的安装输出),点安装:
- 为 chart 默认值,+ 为本次部署值):@@ auth 段 @@
- database: ''
+ database: nexus # 预创建名为 nexus 的数据库(Nexus 的元数据库)
- postgresPassword: ''
+ postgresPassword: <POSTGRES_PASSWORD> # postgres 超级用户密码(强密码,本文占位)
@@ primary.configuration 段 @@
- configuration: ''
+ configuration: |
+ listen_addresses = '*' # 监听所有网卡(默认只监听本机)
+ max_connections = 500 # 最大并发连接数(默认 100)
+ shared_buffers = 256MB # PG 内存缓冲(默认约为内存的 1/4)
@@ primary.resources / replica.resources 段 @@
- resources: {}
+ resources:
+ limits:
+ cpu: 2 # 资源上限:最多用 2 核 / 2Gi 内存
+ memory: 2Gi
+ requests:
+ cpu: 1 # 资源保底:预留 1 核 / 2Gi 内存
+ memory: 2Gi配置 原因 auth.database: nexus安装时预创建 nexus 数据库,Nexus 的元数据全部住在这个库里auth.postgresPasswordpostgres 超级用户密码,强密码,只存在于 values 与 k8s secret(K8s 内置的“密钥存储“对象)里,本文占位configuration(三个 PG 参数)最关键的是 listen_addresses='*'——PostgreSQL 默认只监听 pod 自己的本机,而 Nexus 是“隔壁 pod“,走 Service 域名来连,必须放开监听;max_connections=500 给并发留余量(Nexus 和 Dependency-Track 共享这个库);shared_buffers=256MB 是 PG 读数据页的内存缓冲resourcesrequests 是保底、limits 是上限。PostgreSQL 是吃内存的组件,不设上限可能被 OOM kill 或把节点内存吃光,2Gi 是这台小节点上求稳的值(diff 里第二段 resources 属于 replica 备库段,本次 replicaCount: 1 只起主库,备库配置留着是为了将来上高可用省事)Release "postgresql" has been upgraded. Happy Helming!
NAME: postgresql
NAMESPACE: middleware # 装进了 middleware 命名空间
STATUS: deployed # 部署成功
REVISION: 2 # 修订号:首次安装是 1,改 values 再跑一次 → 2(本次是升级)
NOTES:
CHART NAME: postgresql
CHART VERSION: 18.7.9
APP VERSION: 18.4.0 # PostgreSQL 实际版本
⚠ WARNING: Since August 28th, 2025, only a limited subset of
images/charts are available for free.
...
PostgreSQL can be accessed via port 5432 on the following DNS names
from within your cluster:
postgresql.middleware.svc.cluster.local - Read/Write connection
To get the password for "postgres" run:
export POSTGRES_PASSWORD=$(kubectl get secret --namespace middleware \
postgresql -o jsonpath="{.data.postgres-password}" | base64 -d)postgresql.middleware.svc.cluster.local 是数据库的集群内域名——K8s 里每个 Service 都有这种 <服务名>.<命名空间>.svc.cluster.local 形式的域名,别的 pod 直接用它就能连上(2.3 里 Nexus 的配置用的就是它);⚠ bitnami 警告:2025-08-28 起 bitnami 只对“有限子集“的镜像/chart 免费,本次安装不受影响,将来升级 chart 遇到“拉不到镜像“时先想到这条。kubectl get pods -n middleware 能看到 postgresql-0 处于 Running;NOTES 里的命令能读出数据库密码(secret 里是 base64 编码,base64 -d 解码);终极验证在 2.3——Nexus 能连上库并正常启动。 2.3 安装 Nexus
https://sonatype.github.io/helm3-charts/,更新完成后安装 nxrm-ha chart(版本 96.1.0,对应 Nexus 3.96.1;ha 是 high availability 的缩写,指“支持多副本部署“):
@@ ingress 段 @@
- enabled: false
+ enabled: true
- defaultRule: false
+ defaultRule: true
- host: null
+ host: nexus.kali-team.internal
- ingressClassName: null
+ ingressClassName: traefik
@@ pvc 段 @@
- storage: 2Gi
+ storage: 20Gi
- volumeClaimTemplate:
- enabled: false
+ volumeClaimTemplate:
+ enabled: true
@@ storageClass 段 @@
- name: nexus-storage
- provisioner: provisionerName
+ name: local-path
+ provisioner: rancher.io/local-path
@@ secret.db 段 @@
- host: nxrm_db_host
+ host: postgresql.middleware.svc.cluster.local
- password: nxrm_db_password
+ password: <POSTGRES_PASSWORD>
- user: nxrm_db_user
+ user: postgres
dbSecret:
- enabled: false
+ enabled: true
@@ secret.nexusAdminSecret 段 @@
adminPassword: <NEXUS_ADMIN_PASSWORD>
- enabled: false
+ enabled: true
@@ nexus.env 段 @@
- install4jAddVmParams: '-Xms2703m -Xmx2703m'
+ install4jAddVmParams: '-Xms1024m -Xmx1024m'
nexusDBName: nexus
nexusDBPort: 5432
@@ nexus.resources 段 @@
limits:
- cpu: 16
- memory: 16Gi
+ cpu: 2
+ memory: 2Gi
requests:
- cpu: 8
- memory: 8Gi
+ cpu: 1
+ memory: 2Gi
@@ nexusNs 段 @@
- enabled: true
- name: nexusrepo
+ enabled: false
+ name: sonatype
@@ config / supportContent 段 @@
config:
- enabled: false
+ enabled: true
mountPath: /sonatype-nexus-conf
supportContent:
- enabled: false
+ enabled: true
path: /nexus-data/tmp
sizeLimit: 50Gi
@@ 副本数 @@
- replicaCount: 3
+ replicaCount: 1配置 原因 ingress经域名 nexus.kali-team.internal 对内网暴露(ingress 走集群现有的网关,证书由内网 CA 签发);defaultRule: true 启用 chart 自带的默认路由规则,把入口流量转到 Nexus 的 8081 端口secret.db数据库连接: host 指向 2.2 装好的 PostgreSQL 的集群内域名,user / password 与装 PG 时相同(占位);dbSecret.enabled: true 让 chart 把数据库密码存进 k8s secret 再注入容器,密码不以明文出现在 pod 定义和日志里secret.nexusAdminSecret初始 admin 密码也写进 secret(取回命令见下,值本文占位) pvc + storageClass20Gi 持久卷(PVC 是给 pod 申请的“跨重启不丢的磁盘“;chart 默认 2Gi 对制品仓库太小); volumeClaimTemplate 是“每个副本各自一块 PVC“,将来扩副本、上高可用的前提;local-path(rancher.io/local-path)是 k3s 自带的本地存储类,数据落在节点本地磁盘install4jAddVmParams: '-Xms1024m -Xmx1024m'Nexus 是 Java 程序, -Xms/-Xmx 是 JVM 堆内存的初始与最大值;容器内存上限 2Gi,要给元空间、线程栈留余量,1G 是安全线resources + replicaCount: 1资源额度从默认 16C/16Gi、3 副本缩到 2C/2Gi、单副本;数据库已经外置(共享 PostgreSQL),将来把 replicaCount 改 3 即可多副本高可用,不用迁数据nexusNs: enabled: false关掉 chart 自带的“自动建新命名空间“开关,资源统一放 sonatype 命名空间config / supportContent两个持久化目录: config 存 admin 密码文件、license 等配置,supportContent 是临时/上传暂存(限 50Gi);都开启持久化,容器重建配置不丢nexus.docker.enabled: false(保持默认)Nexus 本身可以当 Docker 镜像仓库,本环境镜像归 Harbor 管,不重复开 # 做什么:读回 chart 存好的初始 admin 密码
# 怎么做:chart 把密码存在 sonatype 命名空间的 nxrm-ha-adminsecret 里;
# secret 内容是 base64 编码的,-o jsonpath 取出后 base64 -d 解码
# 为什么:密码不落文章、不进 shell 历史,只在凭据管理里留存
kubectl get secret nxrm-ha-adminsecret -n sonatype \
-o jsonpath='{.data.nexus-admin-password}{"\n"}' | base64 -d
# 输出即初始 admin 密码(本文占位 <NEXUS_ADMIN_PASSWORD>;
# 建议首次登录后立即修改,并按团队凭据规范存放)https://nexus.kali-team.internal,用 admin 账号登录,看到 Dashboard(Usage Center) 页面即 Nexus 起来了:
2.4 Blob Store:制品文件的存放位置
/nexus-data 数据根目录下的一个子目录)。
goproxy,路径 /nexus-data/blobs/goproxy:
go-sumdb,建完两个都是 Started(就绪)状态:
2.5 创建六个代理仓库

仓库 Recipe 上游 用途 goproxygo (proxy) https://goproxy.cnGo 模块依赖代理 go-sumdbraw (proxy) https://goproxy.cn/sumdb/sum.golang.org校验数据库(sumdb)查询代理 go-dev-dlraw (proxy) https://go.dev/dl/Go 工具链下载代理 sonar-scanner-cliraw (proxy) https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/SonarScanner 安装包代理 debianapt (proxy) https://deb.debian.org/debian/Docker 构建 apt 主源代理 debian-securityapt (proxy) https://deb.debian.org/debian-security/Debian 安全更新源代理 go 命令只认这套协议,raw 顶替不了:
goproxy,上游(remote)https://goproxy.cn,Blob Store 选 2.4 建的 goproxy:
goproxy.cn 而不是官方 proxy.golang.org:goproxy.cn 是公共 Go 模块代理(国内维护),内网出口到它更快更稳,“Nexus 从 goproxy.cn 取、goproxy.cn 从官方取“双重缓存更可靠。表单里的 Firewall 是 Nexus 的“仓库防火墙”:要连商业版 IQ 服务,在制品入库前做安全策略检查;本环境没有 IQ 服务,只能选 Disabled。sum.golang.org,内网到不了,所以要代理。sumdb 的查询就是普通 HTTP GET(按路径取一个小文本文件),不属于任何包管理器协议,所以用 raw (proxy)(通用文件代理)表单:
go-sumdb,上游 https://goproxy.cn/sumdb/sum.golang.org(goproxy.cn 在这个路径下聚合了官方 sumdb),Strict Content Type Validation 不勾选——开启时 Nexus 会对存入文件的“内容类型“(Content-Type)做严格校验,sumdb 查询返回的是短文本,勾了会把缓存文件拦掉:

go/gofmt 等命令打包的 tar 包),官方下载目录 https://go.dev/dl/ 内网访问不稳且慢,所以代理:
go1.26.3 的工具链压缩包已缓存(团队 CI 引用的就是这个版本):
sonarsource.com 内网不通,代理它:跑工作流时免出网、省流量:

nx-repository-view:sonar-scanner-cli:*);其余仓库保持默认“必须登录”。deb.debian.org 内网不通,再建两个 apt (proxy) 仓库(Nexus 的 apt 专用表单,理解成“专门代理 Debian 源的 raw 仓库“即可),上游分别指向官方主源与安全更新源。apt 请求不带凭据,所以这两个仓库同样开匿名读(只代理公开内容)。
goproxy(Go 模块代理,已缓存 589.6 MB)/ go-sumdb(校验数据库代理)/ go-dev-dl(工具链代理)/ sonar-scanner-cli(扫描器安装包存放)/ debian / debian-security(apt 主源与安全更新源代理)全部 Online 3 使用:把 Go 项目指到内网 Nexus
3.1 四个 Go 环境变量,各司其职
变量 干什么 本环境的值 GOPROXY模块下载走哪个渠道。逗号分隔 = “前面 404 再试后面”;特殊值 direct = “直接去源码仓库(git)拿”本地开发 …/goproxy/,direct;流水线 …/goproxy/(不带 ,direct)GOSUMDB下载后到校验数据库核对模块哈希 流水线指向内网 go-sumdb 代理,在线校验保持开启GOPRIVATE声明“这些模块路径是私有的“:同时跳过代理和校验库(私有模块公共代理里没有) 未设置(本例模块都是公开的) GOINSECURE对指定域名跳过 TLS 证书校验(给自签证书的自家服务用) 未设置(Nexus 由内网 CA 正规签发,系统已信任) 3.2 怎么配
# 做什么:让当前 shell 里的 go 命令走 Nexus
# 怎么做:export 成环境变量
# 为什么:先试通再固化;关掉终端即失效,不影响别人
export GOPROXY="https://nexus.kali-team.internal/repository/goproxy/,direct"
export GOSUMDB="sum.golang.org https://nexus.kali-team.internal/repository/go-sumdb/"go env -w 写进 Go 自己的用户级配置文件(路径用 go env GOENV 可查,与 shell 无关):go env -w GOPROXY="https://nexus.kali-team.internal/repository/goproxy/,direct"
go env -w GOSUMDB="sum.golang.org https://nexus.kali-team.internal/repository/go-sumdb/"# 写法一(推荐,流水线在用):sumdb 查询指向 Nexus 的 go-sumdb 代理
go env -w GOSUMDB="sum.golang.org https://nexus.kali-team.internal/repository/go-sumdb/"
# 写法二(不推荐):关掉在线校验
go env -w GOSUMDB="off""校验库域名 + 代理基础 URL"。Go 要查 sum.golang.org 时不再直连公网,而是按这个基础 URL 拼出查询地址(大致是 <基础URL>/sum.golang.org/lookup/<模块>@<版本>),请求走内网的 go-sumdb 仓库转发到 goproxy.cn——“在线校验在内网也通了”。流水线与 Dockerfile 都用这一写法。off(不推荐):off 是“不去校验数据库在线核对“,但不等于“完全不校验“——go.sum 文件记录的哈希在构建时仍会逐个本地比对,依赖内容被换掉、哈希对不上,构建直接失败。但 go.sum 是仓库里自己维护的文件:条目缺失、和恶意改动一起提交、新拉一个没有历史记录的模块时,它拦不住——sumdb 的在线校验(官方签名的校验数据)才是防“模块被篡改 / 供应链投毒“的最后一道防线,内网也不能把这条通道断掉:go-sumdb 代理就是为此而建。只有“代理确实无法部署、且团队明确接受完整性降级“时才允许用 off,并把风险写进文档——本环境不采用。GOSUMDB="https://nexus…/go-sumdb/sumdb/sum.golang.org" 会直接报错(malformed verifier id)。GOSUMDB 接受的是“域名(可选再加公钥/基础 URL)“,带路径的 URL 不是合法域名。那个带路径的 URL 是“Go 最终查询到的地址”,不是“要填的值“。,direct:带上它,模块在 Nexus 里 404 时,go 会退回“直接去源码仓库(git)拿“——在内网到不了公网的场景里,这个回退基本必然失败,它的价值更多是让报错信息说清楚“是代理里没有这个模块“而不是含糊的网络错。流水线(dependency-track.yaml)选择不写 ,direct:失败就失败,绝不偷偷出网;本地开发可以保留,无实质差别。 3.3 怎么验证:三层验证
# 客户端:在项目目录里跑依赖下载
cd ~/IdeaProjects/example # 换成你自己的 Go 项目目录
go mod download -x # -x 打印执行细节,方便看"卡在哪一步"
# 检查当前生效的配置
go env GOPROXY GOSUMDB
# 预期输出(与 10.12 配的值一致):
# https://nexus.kali-team.internal/repository/goproxy/,direct
# sum.golang.org https://nexus.kali-team.internal/repository/go-sumdb/go mod download 成功——内网到不了公网,这是唯一路径,成功即证明代理链路通了;go mod download,秒级完成(全部命中缓存,不再请求上游)。
golang.org、github.com、k8s.io 等顶层路径;example 项目的 16 个依赖及其间接依赖都在这个仓库的缓存里 3.4 扩展场景:有私有模块时怎么配
GOPRIVATE / GOINSECURE 都没设。但团队将来难免要把私有模块(代码放在自家 Gitea 上、公网代理里不可能有的 Go 模块)引进来,核心原则一句话:私有模块不能走公开代理、也不能去公开校验库查哈希——代理里没有、校验库里也没有,硬走只会得到 404。GOPRIVATE# 做什么:告诉 go "这个域名前缀下的模块路径都是私有的"
# 怎么做:填模块路径里的域名(本环境示例 hub.kali-team.internal)
# 为什么:命中后 go 对该模块同时跳过 GOPROXY 与 GOSUMDB,
# 直接用 git 去源码仓库取
export GOPRIVATE="hub.kali-team.internal"go.mod 的 require 行里写的那个地址——比如 Gitea 上 dev-sec-ops/example 组织里的代码,被引用时写 hub.kali-team.internal/dev-sec-ops/example,hub.kali-team.internal 就是模块路径的第一段。Gitea 的惯例是模块路径默认用代码托管域名,所以本环境两者恰好重合——但规则本身是“看模块路径“:如果哪天用独立域名给模块寻址(vanity path),要填那个域名,而不是 Web 域名。GOPRIVATE 支持逗号分隔多个、也支持通配符(如 *.example.com)。# 写法一:HTTPS + Gitea 访问令牌
# 做什么:git 每次要访问 "https://hub.kali-team.internal" 前,
# 自动改写成"带令牌"的地址
# 怎么做:git config 的 url.<新前缀>.insteadOf "<旧前缀>"——git 的"URL 改写"机制,
# 发送请求前把旧前缀替换成新前缀,令牌随之带上
# 为什么:不用每次手动输凭据;<GITEA_TOKEN> 是 Gitea 的访问令牌
# ("让程序代表你操作仓库"的凭据,Gitea 个人设置里创建、长期有效,
# 按密码级别管理:只进凭据管理,不进文章 / 代码)
git config --global url."https://<GITEA_TOKEN>@hub.kali-team.internal".insteadOf "https://hub.kali-team.internal"# 写法二:SSH 公钥(本机公钥已加入 Gitea)
# 做什么:git 去 "https://hub.kali-team.internal/" 取模块时,改走 SSH 通道
# 怎么做:同样的 insteadOf 改写;SSH 通道是 Gitea 的 NodePort 32222
# 为什么:机器不用持有令牌(长期凭据),认证靠公钥;本机同步内网仓库用的就是这条通道
git config --global url."ssh://[email protected]:32222/".insteadOf "https://hub.kali-team.internal/"GOPRIVATE,再在步骤里加一段上面的 git config。GOINSECURE:只给自签证书的域名用,本环境用不到:它专门服务于“证书自签、系统不信任“的服务。Nexus 与 Gitea 的证书都是内网 CA 正规签发、系统信任库已包含该 CA,没有跳过校验的必要——而且跳过校验是安全降级,不该顺手就开。 3.5 Go 模块下载报错怎么读:排查顺序
go mod download 的报错五花八门,但第一行错误基本都标明了卡在哪一层。按“客户端配置 → 网络 → 凭据 → 服务端代理“的顺序逐层排除,大多数问题能在前三层定位:报错关键字(第一行) 卡在哪 先做什么 取模块时 404 Not Found代理里没有这个模块 先怀疑自己:模块路径拼写错误(大小写敏感);是私有模块却报 404 → GOPRIVATE 没设;公开模块代理里没有 → 模块不存在 / 已改名 / 上游也没有malformed verifier idGOSUMDB 写法非法改成 3.2 的两字段写法 lookup …: no such host / connection refused / i/o timeout网络层:根本到不了代理 查 go env GOPROXY 的地址端口;内网域名要能解析(hosts / DNS);用 curl 直接访问代理基础 URL——返回任何 HTTP 状态码都说明网络通,连不上才是网络问题git 取私有模块报 401 / 403git 认证没配或失效 检查 3.4“git 认证“里的 insteadOf 改写是否存在、令牌是否有效 checksum mismatch下载内容的哈希与 go.sum 记录不符依赖被篡改、或 go.sum 本身损坏——不要顺手删掉 go.sum 重新生成(那等于放弃这道防线),先核对 go.sum 的来源与模块版本module lookup disabled by GOPROXY=off代理通道被关了 本环境不该出现;查 go env GOPROXY 为什么是 offgo env GOPROXY GOSUMDB GOPRIVATE GOINSECURE 核对实际生效值——常见情况是“改了没生效“:shell 环境变量与 go env -w 写的不是一处,临时 export 关掉终端就没了。 4 CI/CD 联动:流水线怎么用 Nexus
4.1 全景:谁在用哪个仓库
docker.yaml / sonarqube.yaml / dependency-track.yaml)和将来的工具链安装,都以 Nexus 为下载源:使用方 下载什么 Nexus 仓库 配置在哪 Dockerfile 构建阶段Go 模块依赖(gin 等) goproxyENV GOPROXY=… GOSUMDB="sum.golang.org https://nexus.kali-team.internal/repository/go-sumdb/"(当前依赖已预烤进 gomod-cache 镜像,此 env 是现场需要取件时的兜底)dependency-track.yamlcyclonedx-gomod 及其模块依赖goproxyjob 级 env + “准备 Go 模块依赖“步骤sonarqube.yamlSonarScanner CLI 安装包 sonar-scanner-cliscannerBinariesUrl + scannerFileDockerfile apt 步骤Debian apt 包(索引文件 + .deb) debian / debian-securityDockerfile 的 sed 源改写 (将来)需要特定 Go 版本的流水线 Go 工具链压缩包 go-dev-dlsetup-go 的 go-download-base-url 4.2 dependency-track.yaml:模块依赖走 goproxy
jobs:
dependency-track:
runs-on: ubuntu-host
env:
# 做什么:本 job 里所有 Go 模块下载走 Nexus 的 goproxy
# 为什么:任务容器到不了公网;
# 不写 ,direct——失败就失败,绝不偷偷出网
GOPROXY: "https://nexus.kali-team.internal/repository/goproxy/"
GOSUMDB: "sum.golang.org https://nexus.kali-team.internal/repository/go-sumdb/" # sumdb 校验走 Nexus go-sumdb 代理,保持在线校验不关闭(go.sum 本地哈希校验始终生效)
steps:
# … Init / Checkout / Verify Certificate(与另两条流水线一致,此处省略)…
- name: 准备 Go 模块依赖
run: |
go version # 确认任务容器里可用的 Go 版本(1.25.14)
go mod download # 先把项目依赖批量拉好(经代理,缓存进任务容器)
- name: 生成 CycloneDX SBOM
uses: https://hub.kali-team.internal/actions/cyclonedx-gomod-generate-sbom@v2
with:
# cyclonedx-gomod 的 Go 模块版本:action 用 `go run` 现场编译,
# 它这个模块本身也经 Nexus goproxy 下载
version: "v1.10.0"
args: "app -json -output bom.json -packages -files -licenses -main ."env 两行:写在 job 级,意味着本 job 所有步骤(包括生成 SBOM 那步——它自己就要编译 Go 代码)都走同一个内网下载源——整条流水线零公网连接。go mod download 步骤是“把依赖提前“:先把项目的 16 个模块批量拉好,后面生成 SBOM 的步骤不用再拉。补一句容易忽略的:常规构建(模块已在 gomod-cache 预烤缓存里、go.sum 完整)根本不会触发 sumdb 查询,go-sumdb 代理只在“全新模块要校验“时出场——兜底通道平时安静,但必须活着。 4.3 sonarqube.yaml:scanner 来自 raw 代理
- name: SonarQube Scan 静态代码扫描
uses: https://hub.kali-team.internal/actions/sonarqube-scan-action@v9
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # SonarQube 用户级 API token(长期有效)
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} # https://sonarqube.kali-team.internal
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/kali-team-ca.crt # 信任内网 CA
with:
# 做什么:scanner 的下载源改为 Nexus 的 sonar-scanner-cli 仓库
# 为什么:官方站 sonarsource.com 内网不通;代理更快更稳、省流量
scannerBinariesUrl: https://nexus.kali-team.internal/repository/sonar-scanner-cli/
# 做什么:显式指定要下载的文件名
# 为什么:raw 仓库没有目录列表接口,
# 不能"运行时列目录挑一个",必须写死文件名
scannerFile: sonar-scanner-8.1.0.6389-linux.tgz
projectBaseDir: .
# 官方 action 原本会对下载包做 GPG 签名校验;内网 Nexus 里的包
# 没有签名可验(是从官方站取回缓存的同一文件),兼容需要关掉
skipSignatureVerification: truescannerFile 显式写死是 raw 仓库“无目录列表“逼出来的;三是 action 用 wget 下载、解压后执行——scanner 本体约 9MB,大头(JRE 运行时)已在任务镜像里预装。 4.4 go-dev-dl:Go 工具链这样下
setup-go 这个 action 的 go-download-base-url 输入就是为此设计的: - name: 安装 Go 1.26.3
# 注意:setup-go 官方包托管在 github.com,内网到不了;
# 在本环境使用前需自托管进 actions 组织
uses: actions/setup-go@v7
with:
go-version: '1.26.3' # 要装的 Go 版本
# 做什么:把工具链下载源从官方站改为 Nexus
# 为什么:第一次请求由 Nexus 转发到 go.dev/dl 并缓存(图 15 即缓存证据),
# 之后装同一版本直接命中缓存,不再依赖外网
go-download-base-url: 'https://nexus.kali-team.internal/repository/go-dev-dl/'go1.26.3 缓存就是它已就绪的证据。 4.5 Dockerfile:apt 源走两个代理
/etc/apt/sources.list.d/debian.sources,在 Dockerfile 里直接 sed 把源域名改写到 Nexus 的两个代理仓库:RUN set -eux; \
sed -i \
-e 's|http://deb.debian.org/debian|https://nexus.kali-team.internal/repository/debian|g' \
-e 's|https://deb.debian.org/debian|https://nexus.kali-team.internal/repository/debian|g' \
/etc/apt/sources.list.d/debian.sources-security 是域名后的前缀延续,替换域名后 deb.debian.org/debian-security/... 变成 …/repository/debian-security/...,主源与安全更新源各进各的仓库,不需要第二条规则; 4.6 价值:内网统一下载入口
小结
goproxy / go-sumdb / go-dev-dl / sonar-scanner-cli / debian / debian-security),Go 依赖、校验数据、工具链、扫描器、apt 源这些“公网来的“东西,从此在内网都能取到并缓存。off(不推荐,本环境不采用);验证三层:客户端下载成功、服务端 Browse 有条目、再跑命中缓存;dependency-track.yaml 的 job env 把 GOPROXY 指到 goproxy(整条流水线零公网)、sonarqube.yaml 的 scanner 走 raw 代理(文件名必须显式写死)、go-dev-dl 为将来的“特定版本 Go 安装“备好、Dockerfile 的 apt 源 sed 改写走 debian 代理; 参考