blob: b58f4423969aa53004b32bd7dc9dfad4ee4aa264 [file] [log] [blame]
Serge Bazanski044386d2021-05-24 15:10:08 +02001package main
2
3import (
4 "context"
5 "errors"
6 "flag"
7 "fmt"
8 "net"
9 "os"
10 "os/signal"
11 "strings"
12
13 "github.com/golang/glog"
14 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15
16 "code.hackerspace.pl/hscloud/cluster/identd/ident"
17 "code.hackerspace.pl/hscloud/cluster/identd/kubenat"
18 "code.hackerspace.pl/hscloud/go/mirko"
19)
20
21func init() {
22 flag.Set("logtostderr", "true")
23}
24
25var (
26 flagIdentdListen = "127.0.0.1:8113"
27 flagContainerdSocket = "/var/run/containerd/containerd.sock"
28 flagConntrackProc = "/proc/net/nf_conntrack"
29 flagPodName = ""
30 flagPodNamespace = ""
31)
32
33func main() {
34 flag.StringVar(&flagIdentdListen, "identd_listen", flagIdentdListen, "Address at which to listen for incoming ident protocol connections")
35 flag.StringVar(&flagContainerdSocket, "identd_containerd_socket", flagContainerdSocket, "Containerd gRPC socket path")
36 flag.StringVar(&flagConntrackProc, "identd_conntrack_proc", flagConntrackProc, "Conntrack procfs file")
37 flag.StringVar(&flagPodName, "identd_pod_name", flagPodName, "Name of this pod, if on k8s. Needed for public IP resolution.")
38 flag.StringVar(&flagPodNamespace, "identd_pod_namespace", flagPodNamespace, "Namespace where this pod is running, if on k8s. Needed for public IP resolution.")
39 flag.Parse()
40
41 ctx, ctxC := context.WithCancel(context.Background())
42
43 resolver, err := kubenat.NewResolver(ctx, flagConntrackProc, flagContainerdSocket)
44 if err != nil {
45 glog.Exitf("Could not start kubenet resolver: %v", err)
46 }
47
48 var localIP net.IP
49
50 localIPStr, _, err := net.SplitHostPort(flagIdentdListen)
51 if err != nil {
52 glog.Warningf("Could not parse identd listen flag %q", flagIdentdListen)
53 } else {
54 localIP = net.ParseIP(localIPStr)
55 if localIP == nil || !localIP.IsGlobalUnicast() {
56 glog.Warningf("Could not parse unicast IP from identd flag %q", localIPStr)
57 localIP = nil
58 }
59 }
60
61 if localIP == nil {
62 glog.Infof("Could not figure out public IP address for identd, attempting to retrieve from k8s...")
63 cs := mirko.KubernetesClient()
64 if cs == nil {
65 glog.Exitf("Not in k8s and identd_listen set to invalid public IP address - exiting.")
66 }
67 if flagPodName == "" {
68 glog.Exitf("identd_pod_name must be set")
69 }
70 if flagPodNamespace == "" {
71 glog.Exitf("identd_pod_namespace must be set")
72 }
73 pod, err := cs.CoreV1().Pods(flagPodNamespace).Get(ctx, flagPodName, v1.GetOptions{})
74 if err != nil {
75 glog.Exitf("Could not find pod %q in namespace %q: %v", flagPodName, flagPodNamespace, err)
76 }
77 ipStr := pod.Status.HostIP
78 if ipStr == "" {
79 glog.Exitf("HostIP in status of pod %q is empty", flagPodName)
80 }
81 glog.Infof("Resolved k8s node IP to %s", ipStr)
82
83 localIP = net.ParseIP(ipStr)
84 if localIP == nil {
85 glog.Exitf("HostIP in status of pod %q is unparseable", flagPodName, ipStr)
86 }
87 }
88
89 glog.Infof("Will respond to identd queries on %s...", localIP)
90 s := &service{
91 resolver: resolver,
92 localIP: localIP,
93 }
94
95 lis, err := net.Listen("tcp", flagIdentdListen)
96 if err != nil {
97 glog.Exitf("Could not listen for identd: %v", err)
98 }
99 isrv := ident.NewServer()
100 isrv.HandleFunc(s.handleIdent)
101 go func() {
102 glog.Infof("Starting identd on %s...", flagIdentdListen)
103 err := isrv.Serve(lis)
104 if err != nil {
105 glog.Exitf("identd Serve: %v", err)
106 }
107 }()
108
109 signalChan := make(chan os.Signal, 1)
110 signal.Notify(signalChan, os.Interrupt)
111 go func() {
112 <-signalChan
113 ctxC()
114 }()
115
116 <-ctx.Done()
117 glog.Infof("Stopping identd...")
118 isrv.Stop()
119 lis.Close()
120}
121
122type service struct {
123 resolver *kubenat.Resolver
124 localIP net.IP
125}
126
127func (s *service) handleIdent(ctx context.Context, w ident.ResponseWriter, r *ident.Request) {
128 clientIPStr, _, err := net.SplitHostPort(r.ClientAddress.String())
129 if err != nil {
130 glog.Errorf("Unparseable ClientAddres %q", r.ClientAddress)
131 w.SendError(ident.UnknownError)
132 return
133 }
134 clientIP := net.ParseIP(clientIPStr)
135 if clientIP == nil {
136 glog.Errorf("Unparseable ClientAddres IP %q", r.ClientAddress)
137 w.SendError(ident.UnknownError)
138 return
139 }
140
141 t4 := kubenat.Tuple4{
142 RemoteIP: clientIP,
143 RemotePort: r.ClientPort,
144 LocalIP: s.localIP,
145 LocalPort: r.ServerPort,
146 }
147 glog.Infof("Running query for %s...", t4.String())
148 info, err := s.resolver.ResolvePod(ctx, &t4)
149 if err != nil {
150 glog.Errorf("ResolvePod(%q): %v", t4.String(), err)
151 w.SendError(ident.NoUser)
152 return
153 }
154
155 ns := info.KubernetesNamespace
156 pod := info.Name
157
158 if ns == "matrix" && strings.HasPrefix(pod, "appservice-irc-") {
159 target := net.JoinHostPort(info.PodIP.String(), "1113")
160 clientPort := r.ClientPort
161 serverPort := info.PodTranslatedPort
162 glog.Infof("Forwarding to appservice-irc at %q, clientPort: %d, serverPort: %d", target, clientPort, serverPort)
163 res, err := ident.Query(ctx, target, clientPort, serverPort)
164 if err != nil {
165 var identErr *ident.IdentError
166 if errors.As(err, &identErr) {
167 glog.Infof("appservice-irc: %s", identErr.Inner)
168 w.SendError(identErr.Inner)
169 } else {
170 glog.Infof("appservice-irc: error: %v", err)
171 w.SendError(ident.UnknownError)
172 }
173 } else {
174 glog.Infof("Response from appservice-irc: %q", res.UserID)
175 w.SendIdent(&ident.IdentResponse{
176 UserID: res.UserID,
177 })
178 }
179 return
180 }
181 // default to kns-*
182 user := fmt.Sprintf("kns-%s", ns)
183 // q3k's old personal namespace.
184 if ns == "q3k" {
185 user = "q3k"
186 }
187 // personal-* namespaces.
188 if strings.HasPrefix(ns, "personal-") {
189 user = strings.TrimPrefix(ns, "personal-")
190 }
191 glog.Infof("Returning %q (from %q) for %q", user, ns, t4.String())
192 w.SendIdent(&ident.IdentResponse{
193 UserID: user,
194 })
195}