mirror of
https://gerrit.hackerspace.pl/hscloud
synced 2025-01-20 16:53:54 +00:00
Serge Bazanski
044386d638
This implements the main identd service that will run on our production hosts. It's comparatively small, as most of the functionality is implemented in //cluster/identd/ident and //cluster/identd/kubenat. Change-Id: I1861fe7c93d105faa19a2bafbe9c85fe36502f73
195 lines
5.4 KiB
Go
195 lines
5.4 KiB
Go
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,
|
|
})
|
|
}
|