Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions pkg/commands/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package commands
import (
"context"
"fmt"
"log/slog"
"os"
"os/signal"
"strings"
Expand All @@ -11,6 +12,7 @@ import (

"github.com/urfave/cli/v3"
bridgev1 "github.com/vercel/bridge/api/go/bridge/v1"
"github.com/vercel/bridge/pkg/fsmount"
"github.com/vercel/bridge/pkg/proxy"
"google.golang.org/protobuf/encoding/protojson"
)
Expand Down Expand Up @@ -88,6 +90,18 @@ func runServer(ctx context.Context, c *cli.Command) error {
}
}

// Auto-discover webhook-injected secret mounts (EKS Pod Identity,
// kube-api SA auto-mount, Azure Workload Identity, etc.) — they're
// added at pod-admission time and aren't reflected in the Deployment
// spec, so the --mount-roots argument from k8s/resources misses them.
discovered, err := fsmount.DiscoverSecretMounts()
if err != nil {
slog.Warn("Failed to discover secret mounts; webhook-injected projected volumes may be inaccessible", "error", err)
} else if len(discovered) > 0 {
slog.Info("Discovered webhook-injected secret mounts", "paths", discovered)
mountRoots = append(mountRoots, discovered...)
}

grpcServer := proxy.NewGRPCServer(proxy.Config{
Addr: addr,
ListenPorts: listenPorts,
Expand Down
85 changes: 85 additions & 0 deletions pkg/fsmount/discover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package fsmount

import (
"bufio"
"fmt"
"os"
"strings"
)

const procSelfMountinfo = "/proc/self/mountinfo"

// DiscoverSecretMounts returns the mount points of webhook-injected
// secret/token projected volumes visible to the current process, read from
// /proc/self/mountinfo.
//
// It returns every tmpfs mount whose mount point starts with /run/secrets/
// or /var/run/secrets/ — the canonical paths used by the kubelet auto-mount,
// EKS Pod Identity, Azure Workload Identity, and similar webhooks. These
// mounts are added at pod-admission time and are not present in the
// Deployment's PodSpec, so callers that derive --mount-roots from the spec
// miss them entirely.
//
// /var/run is a symlink to /run on most distros, so the kernel records the
// canonical /run/secrets/... form even when the workload spec uses
// /var/run/secrets/.... For each discovered mount, both forms are returned so
// authorize() prefix-matches whichever form a client uses.
func DiscoverSecretMounts() ([]string, error) {
f, err := os.Open(procSelfMountinfo)
if err != nil {
return nil, fmt.Errorf("open %s: %w", procSelfMountinfo, err)
}
defer f.Close()

var paths []string
scanner := bufio.NewScanner(f)
for scanner.Scan() {
mp, ok := parseSecretMountLine(scanner.Text())
if !ok {
continue
}
paths = append(paths, mp)
if alias, ok := strings.CutPrefix(mp, "/run/"); ok {
paths = append(paths, "/var/run/"+alias)
} else if alias, ok := strings.CutPrefix(mp, "/var/run/"); ok {
paths = append(paths, "/run/"+alias)
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("scan %s: %w", procSelfMountinfo, err)
}
return paths, nil
}

// parseSecretMountLine extracts the mount point from one /proc/self/mountinfo
// line if it represents a tmpfs mount under /run/secrets/ or
// /var/run/secrets/. Returns ("", false) for any other line.
//
// mountinfo format (one line per mount):
//
// <mount id> <parent id> <maj:min> <root> <mount point> <opts> [<optional>...] - <fs type> <source> <super opts>
//
// The fixed fields are space-separated up to a literal " - " separator; after
// the separator the filesystem type is the first token. See
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt section 3.5.
func parseSecretMountLine(line string) (string, bool) {
const sep = " - "
sepIdx := strings.Index(line, sep)
if sepIdx < 0 {
return "", false
}
head := strings.Fields(line[:sepIdx])
tail := strings.Fields(line[sepIdx+len(sep):])
if len(head) < 5 || len(tail) < 1 {
return "", false
}
mountPoint, fsType := head[4], tail[0]
if fsType != "tmpfs" {
return "", false
}
if !strings.HasPrefix(mountPoint, "/run/secrets/") &&
!strings.HasPrefix(mountPoint, "/var/run/secrets/") {
return "", false
}
return mountPoint, true
}
71 changes: 71 additions & 0 deletions pkg/fsmount/discover_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package fsmount

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestParseSecretMountLine(t *testing.T) {
cases := []struct {
name string
line string
want string
ok bool
}{
{
name: "eks pod identity token",
line: "2381 2334 0:272 / /run/secrets/eks.amazonaws.com/serviceaccount ro,relatime - tmpfs tmpfs rw,size=61656332k",
want: "/run/secrets/eks.amazonaws.com/serviceaccount",
ok: true,
},
{
name: "kubelet service-account auto-mount",
line: "2380 2334 0:273 / /run/secrets/kubernetes.io/serviceaccount ro,relatime - tmpfs tmpfs rw,size=61656332k",
want: "/run/secrets/kubernetes.io/serviceaccount",
ok: true,
},
{
name: "azure workload identity token",
line: "2377 2334 0:271 / /run/secrets/azure/tokens ro,relatime - tmpfs tmpfs rw,size=61656332k",
want: "/run/secrets/azure/tokens",
ok: true,
},
{
name: "var/run alias form (some distros don't symlink /var/run to /run)",
line: "100 1 0:99 / /var/run/secrets/eks.amazonaws.com/serviceaccount ro,relatime - tmpfs tmpfs rw",
want: "/var/run/secrets/eks.amazonaws.com/serviceaccount",
ok: true,
},
{
name: "rejects non-tmpfs mount under same prefix",
line: "100 1 259:1 / /run/secrets/something rw - xfs /dev/nvme0n1p1 rw",
ok: false,
},
{
name: "rejects tmpfs mount outside /run/secrets",
line: "100 1 0:99 / /etc/bridge/tls ro,relatime - tmpfs tmpfs rw",
ok: false,
},
{
name: "rejects malformed line (no separator)",
line: "100 1 0:99 / /run/secrets/x ro,relatime tmpfs tmpfs rw",
ok: false,
},
{
name: "handles optional fields before separator",
line: "2381 2334 0:272 / /run/secrets/eks.amazonaws.com/serviceaccount ro,relatime master:1 - tmpfs tmpfs rw,size=61656332k",
want: "/run/secrets/eks.amazonaws.com/serviceaccount",
ok: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, ok := parseSecretMountLine(tc.line)
assert.Equal(t, tc.ok, ok)
if tc.ok {
assert.Equal(t, tc.want, got)
}
})
}
}
Loading