当前位置:首页 > 文章列表 > Golang > Go问答 > Go Unix socket 怎样读取对端凭据

Go Unix socket 怎样读取对端凭据

来源:17golang原创 2026-10-14 23:53:55 0浏览 收藏

我在给本机服务加 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 连接提供的凭据,并且每条新连接都重新读取。

Go UnixConn 通过 SyscallConn 和 RawConn.Control 访问 fd,再读取对端 pid uid gid 的结构说明图
图1:Go 从 UnixConn 进入文件描述符并读取对端凭据的结构说明图,不是运行截图。

从 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 函数签名。

Linux 使用 SO_PEERCRED 和 Ucred,BSD macOS 使用不同接口,再汇聚到统一 peer credentials 适配层的对比说明图
图2:不同 Unix 平台读取对端凭据的适配边界说明图,不是平台截图。
// 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,业务授权仍只面对统一的身份结构和明确的“不支持/读取失败”结果。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go Unix 数据报套接字怎样处理报文边界Go Unix 数据报套接字怎样处理报文边界
上一篇
Go Unix 数据报套接字怎样处理报文边界
Go os/user Current 和 Lookup 怎样选择
下一篇
Go os/user Current 和 Lookup 怎样选择
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    474次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    532次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    551次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    504次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    324次使用