Skip to content

Commit

Permalink
NETOBSERV-870 implement TokenReview
Browse files Browse the repository at this point in the history
Requires operator PR to grant permission for TokenReviews

- 3 auth modes: check for cluster-admin, check for any user, no check
  (insecure; only for debugging/dev mode)
- add tests
- fix broken dev mode
  • Loading branch information
jotak committed Feb 6, 2023
1 parent d734248 commit 3565287
Show file tree
Hide file tree
Showing 17 changed files with 406 additions and 1,610 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ GOLANGCI_LINT_VERSION = v1.50.1
COVERPROFILE = coverage.out
NPM_INSTALL ?= install

CMDLINE_ARGS ?= --loglevel trace --loki-tenant-id netobserv --frontend-config config/sample-frontend-config.yaml
CMDLINE_ARGS ?= --loglevel trace --loki-tenant-id netobserv --frontend-config config/sample-frontend-config.yaml --auth-check none

ifeq (,$(shell which podman 2>/dev/null))
OCI_BIN ?= docker
Expand Down
14 changes: 12 additions & 2 deletions cmd/plugin-backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

"github.com/sirupsen/logrus"

"github.com/netobserv/network-observability-console-plugin/pkg/handler/auth"
"github.com/netobserv/network-observability-console-plugin/pkg/kubernetes/auth"
"github.com/netobserv/network-observability-console-plugin/pkg/loki"
"github.com/netobserv/network-observability-console-plugin/pkg/server"
)
Expand Down Expand Up @@ -39,6 +39,7 @@ var (
lokiMock = flag.Bool("loki-mock", false, "Fake loki results using saved mocks")
logLevel = flag.String("loglevel", "info", "log level (default: info)")
frontendConfig = flag.String("frontend-config", "", "path to the console plugin config file")
authCheck = flag.String("auth-check", "admin", "type of authentication check: authenticated, admin or none (default is admin)")
versionFlag = flag.Bool("v", false, "print version")
log = logrus.WithField("module", "main")
)
Expand Down Expand Up @@ -81,6 +82,15 @@ func main() {
log.Fatal("labels cannot be empty")
}

checkType := auth.CheckType(*authCheck)
if checkType == auth.CheckNone {
log.Warn("INSECURE: auth checker is disabled")
}
checker, err := auth.NewChecker(checkType)
if err != nil {
log.WithError(err).Fatal("auth checker error")
}

server.Start(&server.Config{
Port: *port,
CertFile: *cert,
Expand All @@ -91,5 +101,5 @@ func main() {
CORSMaxAge: *corsMaxAge,
Loki: loki.NewConfig(lURL, lStatusURL, *lokiTimeout, *lokiTenantID, *lokiTokenPath, *lokiForwardUserToken, *lokiSkipTLS, *lokiCAPath, *lokiMock, strings.Split(lLabels, ",")),
FrontendConfig: *frontendConfig,
}, &auth.BearerTokenChecker{})
}, checker)
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.8.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.26.1
k8s.io/apimachinery v0.26.1
k8s.io/client-go v0.26.1
)
Expand Down Expand Up @@ -48,7 +49,6 @@ require (
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/api v0.26.1 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
Expand Down
66 changes: 0 additions & 66 deletions pkg/handler/auth/check_auth.go

This file was deleted.

2 changes: 1 addition & 1 deletion pkg/handler/loki.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import (
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"

"github.com/netobserv/network-observability-console-plugin/pkg/handler/auth"
"github.com/netobserv/network-observability-console-plugin/pkg/handler/lokiclientmock"
"github.com/netobserv/network-observability-console-plugin/pkg/httpclient"
"github.com/netobserv/network-observability-console-plugin/pkg/kubernetes/auth"
"github.com/netobserv/network-observability-console-plugin/pkg/loki"
"github.com/netobserv/network-observability-console-plugin/pkg/metrics"
"github.com/netobserv/network-observability-console-plugin/pkg/model"
Expand Down
142 changes: 142 additions & 0 deletions pkg/kubernetes/auth/check_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package auth

import (
"context"
"errors"
"fmt"
"net/http"
"strings"

"github.com/netobserv/network-observability-console-plugin/pkg/kubernetes/client"
"github.com/sirupsen/logrus"
authv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var hlog = logrus.WithField("module", "handler.auth")

type CheckType string

const (
AuthHeader = "Authorization"
CheckAuthenticated CheckType = "authenticated"
CheckAdmin CheckType = "admin"
CheckNone CheckType = "none"
)

type Checker interface {
CheckAuth(ctx context.Context, header http.Header) error
}

func NewChecker(typez CheckType) (Checker, error) {
switch typez {
case CheckNone:
return &NoopChecker{}, nil
case CheckAuthenticated:
return &ValidBearerTokenChecker{apiProvider: client.NewInCluster}, nil
case CheckAdmin:
return &AdminBearerTokenChecker{apiProvider: client.NewInCluster}, nil
}
return nil, fmt.Errorf("auth checker type unknown: %s. Must be one of %s, %s, %s", typez, CheckAdmin, CheckAuthenticated, CheckNone)
}

type NoopChecker struct {
Checker
}

func (b *NoopChecker) CheckAuth(ctx context.Context, header http.Header) error {
hlog.Debug("noop auth checker: ignore auth")
return nil
}

func getUserToken(header http.Header) (string, error) {
authValue := header.Get(AuthHeader)
if authValue != "" {
parts := strings.Split(authValue, "Bearer ")
if len(parts) != 2 {
return "", errors.New("missing Bearer token in Authorization header")
}
return parts[1], nil
}
return "", errors.New("missing Authorization header")
}

func runTokenReview(ctx context.Context, apiProvider client.APIProvider, token string, preds []tokenReviewPredicate) error {
client, err := apiProvider()
if err != nil {
return err
}

rvw, err := client.CreateTokenReview(ctx, &authv1.TokenReview{
Spec: authv1.TokenReviewSpec{
Token: token,
},
}, &metav1.CreateOptions{})
if err != nil {
return err
}
for _, predFunc := range preds {
if err = predFunc(rvw); err != nil {
return err
}
}
return nil
}

type tokenReviewPredicate func(*authv1.TokenReview) error

func mustBeAuthenticated(rvw *authv1.TokenReview) error {
if !rvw.Status.Authenticated {
return errors.New("user not authenticated")
}
return nil
}

func mustBeClusterAdmin(rvw *authv1.TokenReview) error {
for _, group := range rvw.Status.User.Groups {
if group == "system:cluster-admins" {
return nil
}
}
return errors.New("user not in cluster-admins group")
}

type ValidBearerTokenChecker struct {
Checker
apiProvider client.APIProvider
}

func (c *ValidBearerTokenChecker) CheckAuth(ctx context.Context, header http.Header) error {
hlog.Debug("Checking authenticated user")
token, err := getUserToken(header)
if err != nil {
return err
}
hlog.Debug("Checking auth: token found")
if err = runTokenReview(ctx, c.apiProvider, token, []tokenReviewPredicate{mustBeAuthenticated}); err != nil {
return err
}

hlog.Debug("Checking auth: passed")
return nil
}

type AdminBearerTokenChecker struct {
Checker
apiProvider client.APIProvider
}

func (c *AdminBearerTokenChecker) CheckAuth(ctx context.Context, header http.Header) error {
hlog.Debug("Checking authenticated user")
token, err := getUserToken(header)
if err != nil {
return err
}
hlog.Debug("Checking auth: token found")
if err = runTokenReview(ctx, c.apiProvider, token, []tokenReviewPredicate{mustBeAuthenticated, mustBeClusterAdmin}); err != nil {
return err
}

hlog.Debug("Checking auth: passed")
return nil
}
Loading

0 comments on commit 3565287

Please sign in to comment.