| package main |
| |
| import ( |
| "context" |
| "errors" |
| "flag" |
| "fmt" |
| "net" |
| "os" |
| "os/signal" |
| "strings" |
| |
| "github.com/golang/glog" |
| v1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| |
| "code.hackerspace.pl/hscloud/cluster/identd/ident" |
| "code.hackerspace.pl/hscloud/cluster/identd/kubenat" |
| "code.hackerspace.pl/hscloud/go/mirko" |
| ) |
| |
| func init() { |
| flag.Set("logtostderr", "true") |
| } |
| |
| var ( |
| flagIdentdListen = "127.0.0.1:8113" |
| flagContainerdSocket = "/var/run/containerd/containerd.sock" |
| flagConntrackProc = "/proc/net/nf_conntrack" |
| flagPodName = "" |
| flagPodNamespace = "" |
| ) |
| |
| func main() { |
| flag.StringVar(&flagIdentdListen, "identd_listen", flagIdentdListen, "Address at which to listen for incoming ident protocol connections") |
| flag.StringVar(&flagContainerdSocket, "identd_containerd_socket", flagContainerdSocket, "Containerd gRPC socket path") |
| flag.StringVar(&flagConntrackProc, "identd_conntrack_proc", flagConntrackProc, "Conntrack procfs file") |
| flag.StringVar(&flagPodName, "identd_pod_name", flagPodName, "Name of this pod, if on k8s. Needed for public IP resolution.") |
| flag.StringVar(&flagPodNamespace, "identd_pod_namespace", flagPodNamespace, "Namespace where this pod is running, if on k8s. Needed for public IP resolution.") |
| flag.Parse() |
| |
| ctx, ctxC := context.WithCancel(context.Background()) |
| |
| resolver, err := kubenat.NewResolver(ctx, flagConntrackProc, flagContainerdSocket) |
| if err != nil { |
| glog.Exitf("Could not start kubenet resolver: %v", err) |
| } |
| |
| var localIP net.IP |
| |
| localIPStr, _, err := net.SplitHostPort(flagIdentdListen) |
| if err != nil { |
| glog.Warningf("Could not parse identd listen flag %q", flagIdentdListen) |
| } else { |
| localIP = net.ParseIP(localIPStr) |
| if localIP == nil || !localIP.IsGlobalUnicast() { |
| glog.Warningf("Could not parse unicast IP from identd flag %q", localIPStr) |
| localIP = nil |
| } |
| } |
| |
| if localIP == nil { |
| glog.Infof("Could not figure out public IP address for identd, attempting to retrieve from k8s...") |
| cs := mirko.KubernetesClient() |
| if cs == nil { |
| glog.Exitf("Not in k8s and identd_listen set to invalid public IP address - exiting.") |
| } |
| if flagPodName == "" { |
| glog.Exitf("identd_pod_name must be set") |
| } |
| if flagPodNamespace == "" { |
| glog.Exitf("identd_pod_namespace must be set") |
| } |
| pod, err := cs.CoreV1().Pods(flagPodNamespace).Get(ctx, flagPodName, v1.GetOptions{}) |
| if err != nil { |
| glog.Exitf("Could not find pod %q in namespace %q: %v", flagPodName, flagPodNamespace, err) |
| } |
| ipStr := pod.Status.HostIP |
| if ipStr == "" { |
| glog.Exitf("HostIP in status of pod %q is empty", flagPodName) |
| } |
| glog.Infof("Resolved k8s node IP to %s", ipStr) |
| |
| localIP = net.ParseIP(ipStr) |
| if localIP == nil { |
| glog.Exitf("HostIP in status of pod %q is unparseable", flagPodName, ipStr) |
| } |
| } |
| |
| glog.Infof("Will respond to identd queries on %s...", localIP) |
| s := &service{ |
| resolver: resolver, |
| localIP: localIP, |
| } |
| |
| lis, err := net.Listen("tcp", flagIdentdListen) |
| if err != nil { |
| glog.Exitf("Could not listen for identd: %v", err) |
| } |
| isrv := ident.NewServer() |
| isrv.HandleFunc(s.handleIdent) |
| go func() { |
| glog.Infof("Starting identd on %s...", flagIdentdListen) |
| err := isrv.Serve(lis) |
| if err != nil { |
| glog.Exitf("identd Serve: %v", err) |
| } |
| }() |
| |
| signalChan := make(chan os.Signal, 1) |
| signal.Notify(signalChan, os.Interrupt) |
| go func() { |
| <-signalChan |
| ctxC() |
| }() |
| |
| <-ctx.Done() |
| glog.Infof("Stopping identd...") |
| isrv.Stop() |
| lis.Close() |
| } |
| |
| type service struct { |
| resolver *kubenat.Resolver |
| localIP net.IP |
| } |
| |
| func (s *service) handleIdent(ctx context.Context, w ident.ResponseWriter, r *ident.Request) { |
| clientIPStr, _, err := net.SplitHostPort(r.ClientAddress.String()) |
| if err != nil { |
| glog.Errorf("Unparseable ClientAddres %q", r.ClientAddress) |
| w.SendError(ident.UnknownError) |
| return |
| } |
| clientIP := net.ParseIP(clientIPStr) |
| if clientIP == nil { |
| glog.Errorf("Unparseable ClientAddres IP %q", r.ClientAddress) |
| w.SendError(ident.UnknownError) |
| return |
| } |
| |
| t4 := kubenat.Tuple4{ |
| RemoteIP: clientIP, |
| RemotePort: r.ClientPort, |
| LocalIP: s.localIP, |
| LocalPort: r.ServerPort, |
| } |
| glog.Infof("Running query for %s...", t4.String()) |
| info, err := s.resolver.ResolvePod(ctx, &t4) |
| if err != nil { |
| glog.Errorf("ResolvePod(%q): %v", t4.String(), err) |
| w.SendError(ident.NoUser) |
| return |
| } |
| |
| ns := info.KubernetesNamespace |
| pod := info.Name |
| |
| if ns == "matrix" && strings.HasPrefix(pod, "appservice-irc-") { |
| target := net.JoinHostPort(info.PodIP.String(), "1113") |
| clientPort := r.ClientPort |
| serverPort := info.PodTranslatedPort |
| glog.Infof("Forwarding to appservice-irc at %q, clientPort: %d, serverPort: %d", target, clientPort, serverPort) |
| res, err := ident.Query(ctx, target, clientPort, serverPort) |
| if err != nil { |
| var identErr *ident.IdentError |
| if errors.As(err, &identErr) { |
| glog.Infof("appservice-irc: %s", identErr.Inner) |
| w.SendError(identErr.Inner) |
| } else { |
| glog.Infof("appservice-irc: error: %v", err) |
| w.SendError(ident.UnknownError) |
| } |
| } else { |
| glog.Infof("Response from appservice-irc: %q", res.UserID) |
| w.SendIdent(&ident.IdentResponse{ |
| UserID: res.UserID, |
| }) |
| } |
| return |
| } |
| // default to kns-* |
| user := fmt.Sprintf("kns-%s", ns) |
| // q3k's old personal namespace. |
| if ns == "q3k" { |
| user = "q3k" |
| } |
| // personal-* namespaces. |
| if strings.HasPrefix(ns, "personal-") { |
| user = strings.TrimPrefix(ns, "personal-") |
| } |
| glog.Infof("Returning %q (from %q) for %q", user, ns, t4.String()) |
| w.SendIdent(&ident.IdentResponse{ |
| UserID: user, |
| }) |
| } |