Skip to content

Commit

Permalink
feat: Implement a basic auth-service client in stunnerctl
Browse files Browse the repository at this point in the history
  • Loading branch information
rg0now committed Feb 21, 2024
1 parent 0bcea5d commit c9ebbbf
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 2 deletions.
83 changes: 83 additions & 0 deletions cmd/stunnerctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/signal"
"regexp"
Expand Down Expand Up @@ -33,6 +35,7 @@ var (
watch, all, verbose bool
k8sConfigFlags *cliopt.ConfigFlags
cdsConfigFlags *cdsclient.CDSConfigFlags
authConfigFlags *cdsclient.AuthConfigFlags
podConfigFlags *cdsclient.PodConfigFlags
loggerFactory *logger.LeveledLoggerFactory
log logging.LeveledLogger
Expand Down Expand Up @@ -72,6 +75,19 @@ var (
}
},
}
authCmd = &cobra.Command{
Use: "auth",
Aliases: []string{"get-credential"},
Short: "Obtain authenticaction credentials to a gateway",
Args: cobra.RangeArgs(0, 1),
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
if err := runAuth(cmd, args); err != nil {
fmt.Println(err)
os.Exit(1)
}
},
}
)

func init() {
Expand All @@ -94,9 +110,14 @@ func init() {
podConfigFlags = cdsclient.NewPodConfigFlags()
podConfigFlags.AddFlags(statusCmd.Flags())

// Auth discovery flags: only for "auth" command
authConfigFlags = cdsclient.NewAuthConfigFlags()
authConfigFlags.AddFlags(authCmd.Flags())

// Add commands
rootCmd.AddCommand(configCmd)
rootCmd.AddCommand(statusCmd)
rootCmd.AddCommand(authCmd)
}

func main() {
Expand Down Expand Up @@ -330,6 +351,68 @@ func runStatus(cmd *cobra.Command, args []string) error {
return nil
}

func runAuth(cmd *cobra.Command, args []string) error {
loglevel := "all:WARN"
if verbose {
loglevel = "all:TRACE"
}
loggerFactory = logger.NewLoggerFactory(loglevel)
log = loggerFactory.NewLogger("stunnerctl")

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

log.Debug("Searching for authentication server")
pod, err := cdsclient.DiscoverK8sAuthServer(ctx, k8sConfigFlags, authConfigFlags,
loggerFactory.NewLogger("auth-fwd"))
if err != nil {
return fmt.Errorf("error searching for auth service: %w", err)
}

u := url.URL{
Scheme: "http",
Host: pod.Addr,
Path: "/ice",
}
q := u.Query()
q.Set("service", "turn")
u.RawQuery = q.Encode()

if authConfigFlags.TurnAuth {
// enforce TURN credential format
u.Path = ""
}

if k8sConfigFlags.Namespace != nil && *k8sConfigFlags.Namespace != "" {
q := u.Query()
q.Set("namespace", *k8sConfigFlags.Namespace)
if len(args) > 0 {
q.Set("gateway", args[0])
}
u.RawQuery = q.Encode()
}

log.Debugf("Querying to authentication server %s using URL %q", pod.String(), u.String())
res, err := http.Get(u.String())
if err != nil {
return fmt.Errorf("error querying auth service %s: %w", pod.String(), err)
}
if res.StatusCode != http.StatusOK {
return fmt.Errorf("HTTP error querying auth service %s: expected status %d, got %d",
pod.String(), http.StatusOK, res.StatusCode)
}

b, err := io.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("cannot read HTTP response: %w", err)
}

fmt.Println(string(b))

return nil
}

// ////////////////////////
var jsonRegexp = regexp.MustCompile(`^\{\.?([^{}]+)\}$|^\.?([^{}]+)$`)

// k8s.io/kubectl/pkg/cmd/get
Expand Down
6 changes: 4 additions & 2 deletions pkg/apis/v1/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@ const (
DefaultAuthName = "default-auth-config"
)

// health-check and metrics reporting defaults
// default ports
const (
DefaultMetricsPort int = 8080
DefaultHealthCheckPort int = 8086
DefaultAuthServicePort int = 8088
)

// Label/anotation defaults
// Label/annotation defaults
const (
DefaultCDSServiceLabelKey = "stunner.l7mp.io/config-discovery-service"
DefaultCDSServiceLabelValue = "enabled"
DefaultAppLabelKey = "app"
DefaultAppLabelValue = "stunner"
DefaultAuthAppLabelValue = "stunner-auth"
DefaultRelatedGatewayKey = "stunner.l7mp.io/related-gateway-name"
DefaultRelatedGatewayNamespace = "stunner.l7mp.io/related-gateway-namespace"
DefaultOwnedByLabelKey = "stunner.l7mp.io/owned-by"
Expand Down
71 changes: 71 additions & 0 deletions pkg/config/client/k8s_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,35 @@ func (f *PodConfigFlags) AddFlags(flags *pflag.FlagSet) {
flags.IntVar(&f.Port, "pod-port", f.Port, "Port of the stunnerd pod to connect to")
}

// AuthConfigFlags composes a set of flags for authentication service discovery.
type AuthConfigFlags struct {
// Addr is an explicit IP address for the server.
Addr string
// Namespace is the namespace of the server pod.
Namespace string
// Port is the port of the server pod.
Port int
// Enforce turn credential.
TurnAuth bool
}

// NewAuthConfigFlags returns auth service discovery flags with default values set.
func NewAuthConfigFlags() *AuthConfigFlags {
return &AuthConfigFlags{
Port: stnrv1.DefaultAuthServicePort,
}
}

// AddFlags binds pod discovery configuration flags to a given flagset.
func (f *AuthConfigFlags) AddFlags(flags *pflag.FlagSet) {
flags.StringVar(&f.Addr, "auth-server-address", f.Addr,
"Auth service address (disables service discovery)")
flags.StringVar(&f.Namespace, "auth-service-namespace", f.Namespace,
"Auth service namespace (disables service discovery)")
flags.IntVar(&f.Port, "auth-service-port", f.Port, "Auth service port")
flags.BoolVar(&f.TurnAuth, "auth-turn-credential", f.TurnAuth, "Request TURN credentials (default: request ICE server config)")
}

// PodConnector is a helper for discovering and connecting to pods in a Kubernetes cluster.
type PodConnector struct {
cs *kubernetes.Clientset
Expand Down Expand Up @@ -287,6 +316,48 @@ func DiscoverK8sStunnerdPods(ctx context.Context, k8sFlags *cliopt.ConfigFlags,
return ps, nil
}

// DiscoverK8sAuthServer discovers the cluster authentication service.
func DiscoverK8sAuthServer(ctx context.Context, k8sFlags *cliopt.ConfigFlags, authFlags *AuthConfigFlags, log logging.LeveledLogger) (PodInfo, error) {
if authFlags.Addr != "" {
return PodInfo{
Addr: fmt.Sprintf("%s:%d", authFlags.Addr, authFlags.Port),
Proxy: false,
}, nil
}

ns := ""
nsLog := "<all>"
if authFlags.Namespace != "" {
ns = authFlags.Namespace
nsLog = ns
}

d, err := NewK8sDiscoverer(k8sFlags, log)
if err != nil {
return PodInfo{}, fmt.Errorf("failed to init CDS discovery client: %w", err)
}

label := fmt.Sprintf("%s=%s", stnrv1.DefaultAppLabelKey, stnrv1.DefaultAuthAppLabelValue)
d.log.Debugf("Querying auth service pods in namespace %q using label-selector %q", nsLog, label)

pods, err := d.cs.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{
LabelSelector: label,
})
if err != nil {
return PodInfo{}, fmt.Errorf("failed to query Kubernetes API server: %w", err)
}

if len(pods.Items) == 0 {
return PodInfo{}, fmt.Errorf("no authentication found")
}

if len(pods.Items) > 1 {
d.log.Infof("mulitple (%d) authentication service instances found, using the first one", len(pods.Items))
}

return d.portfwd(ctx, &pods.Items[0], authFlags.Port)
}

func (d *PodConnector) portfwd(ctx context.Context, pod *corev1.Pod, port int) (PodInfo, error) {
p := PodInfo{
Name: pod.GetName(),
Expand Down

0 comments on commit c9ebbbf

Please sign in to comment.