Go Unix socket 怎样读取对端凭据
来源:17golang原创
时间:2026-10-14 23:53:55 302浏览 收藏
我在给本机服务加 Unix socket 授权时,最容易混淆的是两件事:socket 文件的权限决定“谁能尝试连接”,连接建立后的对端凭据决定“这条连接究竟来自哪个进程和用户”。Go 的 net.UnixConn 没有直接返回 pid、uid、gid 的高层方法,但可以通过 SyscallConn 暴露受控的文件描述符,再读取平台提供的 socket credential。
官方文档:https://pkg.go.dev/net、https://pkg.go.dev/golang.org/x/sys/unix
- 文件模式和对端凭据是两层控制,不能用
chmod代替身份判断。 - Linux 常用
SO_PEERCRED,通过x/sys/unix.GetsockoptUcred得到 pid、uid、gid。 - 读取 fd 时必须留在
RawConn.Control回调内,不能把 fd 保存到回调外继续使用。 - 平台相关常量和结构体应放在 build-tag 文件中,业务授权只依赖自己的统一结果类型。
先分清 socket 权限与连接身份
Unix socket 路径通常会受到目录权限、socket 文件 mode 和服务进程权限的影响。这些检查发生在连接尝试阶段,适合挡住大多数无权访问者;但如果服务还需要按用户、组或进程做细粒度授权,就要在连接建立后读取对端凭据。
这两个层次不能互相替代。例如把 socket 文件设成只有某个组可写,并不等于服务端可以从业务消息里安全地相信一个“uid”字段;客户端自己发送的字段只是普通数据。真正的身份判断应来自内核为该 socket 连接提供的凭据,并且每条新连接都重新读取。

从 UnixConn 进入受控文件描述符
服务端一般从 net.Listener.Accept 得到一个 net.Conn,Unix socket 场景再断言为 *net.UnixConn。官方接口提供的 SyscallConn 返回 syscall.RawConn,而 Control 会在底层 fd 仍然有效时调用回调。这个生命周期约束很重要:不要把回调参数里的 fd 存到全局变量,也不要在回调返回后再调用系统接口。
func readPeerCredential(conn *net.UnixConn) (*unix.Ucred, error) {
raw, err := conn.SyscallConn()
if err != nil {
return nil, fmt.Errorf("获取 Unix socket fd: %w", err)
}
var cred *unix.Ucred
var optionErr error
// Control 回调期间 fd 才由 RawConn 保证有效,系统调用必须放在这里。
if err := raw.Control(func(fd uintptr) {
// SO_PEERCRED 是 Linux 的连接对端凭据选项,返回 pid、uid、gid。
cred, optionErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED)
}); err != nil {
return nil, fmt.Errorf("访问 Unix socket fd: %w", err)
}
if optionErr != nil {
return nil, fmt.Errorf("读取 SO_PEERCRED: %w", optionErr)
}
if cred == nil {
return nil, errors.New("内核没有返回对端凭据")
}
return cred, nil
}
这段实现依赖 golang.org/x/sys/unix,因此应放在 Linux 文件中,例如 peercred_linux.go。Ucred.Pid 是进程号,Uid 和 Gid 是内核返回的用户与组标识;它们不是客户端在消息体里自报的字符串。
把授权动作放在读取成功之后
读取到凭据后,先转换成业务层自己的小结构,再交给授权函数。不要让业务代码到处依赖 unix.Ucred,这样未来替换 BSD/macOS 实现时,权限规则不需要重写。
type PeerIdentity struct {
PID int32
UID uint32
GID uint32
}
func authorize(conn *net.UnixConn) error {
cred, err := readPeerCredential(conn)
if err != nil {
// 身份读取失败时拒绝连接,避免把“未知”当成可信身份。
return fmt.Errorf("无法确认对端身份: %w", err)
}
identity := PeerIdentity{PID: cred.Pid, UID: cred.Uid, GID: cred.Gid}
// 示例规则只允许指定 uid;实际项目还应结合组、部署命名空间和审计策略。
if identity.UID != 1001 {
return fmt.Errorf("uid %d 不在允许列表", identity.UID)
}
return nil
}
实际服务中,授权规则通常不应只写死一个 uid。可以把允许的 uid/gid 放进配置,把读取到的 pid 写入审计日志,并把“读取失败”“身份不允许”“业务认证失败”分成不同错误类型,方便排障。
平台差异要隔离,而不是复制 Linux 常量
SO_PEERCRED 和 Ucred 是 Linux 路径。其他 Unix 系统可能使用不同的 socket option、返回结构或系统调用;BSD/macOS 常见的是 getpeereid 一类的接口。不要为了让一份文件跨平台编译,给不存在的常量加类型转换;更稳妥的做法是让每个平台各自实现同一个 Go 函数签名。

// peercred_linux.go
//go:build linux
func peerIdentity(conn *net.UnixConn) (PeerIdentity, error) {
// Linux 实现调用 SO_PEERCRED,其他平台文件提供同名函数。
cred, err := readPeerCredential(conn)
if err != nil {
return PeerIdentity{}, err
}
return PeerIdentity{PID: cred.Pid, UID: cred.Uid, GID: cred.Gid}, nil
}
在 BSD 或 macOS 文件里,使用该平台实际支持的接口,把结果填入同一个 PeerIdentity。对于不支持 Unix socket credential 的构建目标,应明确返回“不支持”,而不是返回全零身份;全零值可能被误认为是一个真实用户。
几个容易踩到的边界
第一,pid 不是永久身份。进程退出后,操作系统可能复用同一个 pid;如果授权决定需要长期绑定进程,应该结合连接生命周期、uid/gid 和更高层的认证材料,而不是只保存 pid。
第二,容器和用户命名空间会改变“数字 uid 的含义”。服务看到的是当前内核命名空间下的标识,不能直接把宿主机配置中的数字与容器里的 uid 机械对应。部署时要把 namespace、镜像用户和 socket 所在目录权限一起说明。
第三,Unix socket 的路径名、抽象命名空间和平台实现并不完全相同。Linux 以 @ 开头的地址属于抽象命名空间,而不是普通文件路径;这会影响你对文件 mode 的预期,但不会改变“连接凭据需要从底层 socket 获取”的分层思路。
常见问题
只检查 socket 文件权限够不够?
如果需求只是限制谁能连接,文件和目录权限可能已经足够;只要还要在服务内部按 uid、gid 或进程做授权,就应该读取内核提供的对端凭据。
为什么不从请求内容中读取 uid?
请求内容由客户端控制,最多是业务字段,不能替代内核身份。它可以作为业务用户信息,但不应单独作为本地进程授权依据。
Go 标准库能直接返回对端 uid 吗?
net.UnixConn 提供的是受控原始连接入口,具体 credential option 属于平台能力。Linux 示例通常配合 golang.org/x/sys/unix 完成读取,并把这段代码隔离在平台文件中。
我的落地顺序是:先用目录和 socket mode 缩小连接入口,再在每次 Accept 后读取 peer credential,最后把平台代码收口到适配层。这样即使部署目标从 Linux 换到 BSD/macOS,业务授权仍只面对统一的身份结构和明确的“不支持/读取失败”结果。
-
405 收藏
-
221 收藏
-
418 收藏
-
109 收藏
-
418 收藏
-
291 收藏
-
491 收藏
-
141 收藏
-
385 收藏
-
406 收藏
-
412 收藏
-
373 收藏
-
171 收藏
-
336 收藏
-
471 收藏
-
366 收藏
-
122 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习