auditd_linux.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux && !android
  4. package tailssh
  5. import (
  6. "bytes"
  7. "encoding/binary"
  8. "fmt"
  9. "os"
  10. "syscall"
  11. "golang.org/x/sys/unix"
  12. "tailscale.com/types/logger"
  13. )
  14. const (
  15. auditUserLogin = 1112 // audit message type for user login (from linux/audit.h)
  16. netlinkAudit = 9 // AF_NETLINK protocol number for audit (from linux/netlink.h)
  17. nlmFRequest = 0x01 // netlink message flag: request (from linux/netlink.h)
  18. // maxAuditMessageLength is the maximum length of an audit message payload.
  19. // This is derived from MAX_AUDIT_MESSAGE_LENGTH (8970) in the Linux kernel
  20. // (linux/audit.h), minus overhead for the netlink header and safety margin.
  21. maxAuditMessageLength = 8192
  22. )
  23. // hasAuditWriteCap checks if the process has CAP_AUDIT_WRITE in its effective capability set.
  24. func hasAuditWriteCap() bool {
  25. var hdr unix.CapUserHeader
  26. var data [2]unix.CapUserData
  27. hdr.Version = unix.LINUX_CAPABILITY_VERSION_3
  28. hdr.Pid = int32(os.Getpid())
  29. if err := unix.Capget(&hdr, &data[0]); err != nil {
  30. return false
  31. }
  32. const capBit = uint32(1 << (unix.CAP_AUDIT_WRITE % 32))
  33. const capIdx = unix.CAP_AUDIT_WRITE / 32
  34. return (data[capIdx].Effective & capBit) != 0
  35. }
  36. // buildAuditNetlinkMessage constructs a netlink audit message.
  37. // This is separated from sendAuditMessage to allow testing the message format
  38. // without requiring CAP_AUDIT_WRITE or a netlink socket.
  39. func buildAuditNetlinkMessage(msgType uint16, message string) ([]byte, error) {
  40. msgBytes := []byte(message)
  41. if len(msgBytes) > maxAuditMessageLength {
  42. msgBytes = msgBytes[:maxAuditMessageLength]
  43. }
  44. msgLen := len(msgBytes)
  45. totalLen := syscall.NLMSG_HDRLEN + msgLen
  46. alignedLen := (totalLen + syscall.NLMSG_ALIGNTO - 1) & ^(syscall.NLMSG_ALIGNTO - 1)
  47. nlh := syscall.NlMsghdr{
  48. Len: uint32(totalLen),
  49. Type: msgType,
  50. Flags: nlmFRequest,
  51. Seq: 1,
  52. Pid: uint32(os.Getpid()),
  53. }
  54. buf := bytes.NewBuffer(make([]byte, 0, alignedLen))
  55. if err := binary.Write(buf, binary.NativeEndian, nlh); err != nil {
  56. return nil, err
  57. }
  58. buf.Write(msgBytes)
  59. for buf.Len() < alignedLen {
  60. buf.WriteByte(0)
  61. }
  62. return buf.Bytes(), nil
  63. }
  64. // sendAuditMessage sends a message to the audit subsystem using raw netlink.
  65. // It logs errors but does not return them.
  66. func sendAuditMessage(logf logger.Logf, msgType uint16, message string) {
  67. if !hasAuditWriteCap() {
  68. return
  69. }
  70. fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, netlinkAudit)
  71. if err != nil {
  72. logf("auditd: failed to create netlink socket: %v", err)
  73. return
  74. }
  75. defer syscall.Close(fd)
  76. bindAddr := &syscall.SockaddrNetlink{
  77. Family: syscall.AF_NETLINK,
  78. Pid: uint32(os.Getpid()),
  79. Groups: 0,
  80. }
  81. if err := syscall.Bind(fd, bindAddr); err != nil {
  82. logf("auditd: failed to bind netlink socket: %v", err)
  83. return
  84. }
  85. kernelAddr := &syscall.SockaddrNetlink{
  86. Family: syscall.AF_NETLINK,
  87. Pid: 0,
  88. Groups: 0,
  89. }
  90. msgBytes, err := buildAuditNetlinkMessage(msgType, message)
  91. if err != nil {
  92. logf("auditd: failed to build audit message: %v", err)
  93. return
  94. }
  95. if err := syscall.Sendto(fd, msgBytes, 0, kernelAddr); err != nil {
  96. logf("auditd: failed to send audit message: %v", err)
  97. return
  98. }
  99. }
  100. // logSSHLogin logs an SSH login event to auditd with whois information.
  101. func logSSHLogin(logf logger.Logf, c *conn) {
  102. if c == nil || c.info == nil || c.localUser == nil {
  103. return
  104. }
  105. exePath := c.srv.tailscaledPath
  106. if exePath == "" {
  107. exePath = "tailscaled"
  108. }
  109. srcIP := c.info.src.Addr().String()
  110. srcPort := c.info.src.Port()
  111. dstIP := c.info.dst.Addr().String()
  112. dstPort := c.info.dst.Port()
  113. tailscaleUser := c.info.uprof.LoginName
  114. tailscaleUserID := c.info.uprof.ID
  115. tailscaleDisplayName := c.info.uprof.DisplayName
  116. nodeName := c.info.node.Name()
  117. nodeID := c.info.node.ID()
  118. localUser := c.localUser.Username
  119. localUID := c.localUser.Uid
  120. localGID := c.localUser.Gid
  121. hostname, err := os.Hostname()
  122. if err != nil {
  123. hostname = "unknown"
  124. }
  125. // use principally the same format as ssh / PAM, which come from the audit userspace, i.e.
  126. // https://github.com/linux-audit/audit-userspace/blob/b6f8c208435038df113a9795e3e202720aee6b70/lib/audit_logging.c#L515
  127. msg := fmt.Sprintf(
  128. "op=login acct=%s uid=%s gid=%s "+
  129. "src=%s src_port=%d dst=%s dst_port=%d "+
  130. "hostname=%q exe=%q terminal=ssh res=success "+
  131. "ts_user=%q ts_user_id=%d ts_display_name=%q ts_node=%q ts_node_id=%d",
  132. localUser, localUID, localGID,
  133. srcIP, srcPort, dstIP, dstPort,
  134. hostname, exePath,
  135. tailscaleUser, tailscaleUserID, tailscaleDisplayName, nodeName, nodeID,
  136. )
  137. sendAuditMessage(logf, auditUserLogin, msg)
  138. logf("audit: SSH login: user=%s uid=%s from=%s ts_user=%s node=%s",
  139. localUser, localUID, srcIP, tailscaleUser, nodeName)
  140. }
  141. func init() {
  142. hookSSHLoginSuccess.Set(logSSHLogin)
  143. }