package main import ( "errors" "fmt" "io" "log" "net" "os" "path/filepath" "strings" "syscall" "unsafe" ) type inotifyRequest struct { filename string key string conn *net.UnixConn } type connection struct { fd int key string connection *net.UnixConn } func readEvents(inotifyFd int, events chan string) { defer syscall.Close(inotifyFd) var buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events for { n, err := syscall.Read(inotifyFd, buf[:]) // If a signal interrupted execution, see if we've been asked to close, and try again. // http://man7.org/linux/man-pages/man7/signal.7.html : // "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable" if errors.Is(err, syscall.EINTR) { continue } if n < syscall.SizeofInotifyEvent { if n == 0 { log.Fatalf("notify: read EOF from inotify (cause: %v)", err) } else if n < 0 { log.Fatalf("notify: Received error while reading from inotify: %v", err) } else { log.Fatal("notify: short read in readEvents()") } continue } var offset uint32 for offset+syscall.SizeofInotifyEvent <= uint32(n) { // Point "raw" to the event in the buffer raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset])) mask := uint32(raw.Mask) nameLen := uint32(raw.Len) if mask&syscall.IN_Q_OVERFLOW != 0 { // TODO Re-scan all files in this case log.Fatal("Overflow in inotify") } if nameLen > 0 { // Point "bytes" at the first byte of the filename bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[uintptr(offset)+unsafe.Offsetof(raw.Name)])) // The filename is padded with NULL bytes. TrimRight() gets rid of those. fname := strings.TrimRight(string(bytes[0:nameLen]), "\000") log.Printf("Detected added file: %s", fname) events <- fname } else { log.Printf("file added without length!?") } // Move to the next event in the buffer offset += syscall.SizeofInotifyEvent + nameLen } } } func connFd(conn *net.UnixConn) (int, error) { file, err := conn.File() if err != nil { return -1, err } return int(file.Fd()), nil } func (s *server) watch(inotifyFd int) { connsForPath := make(map[string][]connection) fdToPath := make(map[int]string) fsEvents := make(chan string) go readEvents(inotifyFd, fsEvents) for { select { case req, ok := <-s.inotifyRequests: if !ok { return } fd, err := connFd(req.conn) if err != nil { log.Println("Received inotify request for closed connection") continue } fdToPath[fd] = req.filename conns, ok := connsForPath[req.filename] if ok { connsForPath[req.filename] = append(conns, connection{fd, req.key, req.conn}) continue } connsForPath[req.filename] = []connection{{fd, req.key, req.conn}} case fname, ok := <-fsEvents: if !ok { return } conns := connsForPath[fname] if conns == nil { log.Printf("Ignore unknown file: %s", fname) continue } delete(connsForPath, fname) var secretMap map[string]interface{} var err error if isEnvironmentFile(fname) { content, err := os.ReadFile(filepath.Join(s.SecretDir, fname)) if err != nil { log.Printf("Failed to process service file: %v", err) continue } secretMap = map[string]interface{}{fname: string(content)} } else { secretMap, err = parseServiceSecrets(filepath.Join(s.SecretDir, fname)) if err != nil { log.Printf("Failed to process service file: %v", err) continue } } for _, conn := range conns { defer delete(fdToPath, conn.fd) if err == nil { val, ok := secretMap[conn.key] if !ok { log.Printf("Secret map %s has no value for key %s", fname, conn.key) continue } _, err = io.WriteString(conn.connection, fmt.Sprint(val)) if err == nil { log.Printf("Served %s to %s", fname, conn.connection.RemoteAddr().String()) } else { log.Printf("Failed to send secret: %v", err) } if err := s.epollDelete(conn.fd); err != nil && !errors.Is(err, syscall.ENOENT) { log.Printf("failed to remove socket from epoll: %s", err) } if err := syscall.Shutdown(conn.fd, syscall.SHUT_RDWR); err != nil { log.Printf("Failed to shutdown socket: %v", err) } } else { log.Printf("Failed to open secret: %v", err) } } case fd, ok := <-s.connectionClosed: if !ok { return } path := fdToPath[fd] delete(fdToPath, fd) conns := connsForPath[path] if conns == nil { // watcher has been already deregistered continue } for idx, c := range conns { if c.fd == fd { last := len(conns) - 1 conns[idx] = conns[last] conns = conns[:last] c.connection.Close() break } } if len(conns) == 0 { delete(connsForPath, path) } } } } func (s *server) setupWatcher(dir string) error { fd, err := syscall.InotifyInit1(syscall.IN_CLOEXEC) if err != nil { return fmt.Errorf("Failed to initialize inotify: %v", err) } flags := uint32(syscall.IN_CREATE | syscall.IN_MOVED_TO | syscall.IN_ONLYDIR) // Allow processes to read files from this directory if they have the // permissions on the files, but don't allow them to list files in it. res := os.MkdirAll(dir, 0o711) if err != nil && !os.IsNotExist(res) { return fmt.Errorf("Failed to create secret directory: %v", err) } if _, err = syscall.InotifyAddWatch(fd, dir, flags); err != nil { return fmt.Errorf("Failed to initialize inotify on secret directory %s: %v", dir, err) } go s.watch(fd) return nil }