Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NETOBSERV-870 implement TokenReview #283

Merged
merged 3 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
25 changes: 23 additions & 2 deletions cmd/plugin-backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ 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/kubernetes/client"
"github.com/netobserv/network-observability-console-plugin/pkg/loki"
"github.com/netobserv/network-observability-console-plugin/pkg/server"
)
Expand Down Expand Up @@ -39,6 +40,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", "auto", "type of authentication check: authenticated, admin, auto or none (default is auto, based on loki auth mode)")
versionFlag = flag.Bool("v", false, "print version")
log = logrus.WithField("module", "main")
)
Expand Down Expand Up @@ -81,6 +83,25 @@ func main() {
log.Fatal("labels cannot be empty")
}

var checkType auth.CheckType
if *authCheck == "auto" {
if *lokiForwardUserToken {
checkType = auth.CheckAuthenticated
} else {
checkType = auth.CheckAdmin
}
log.Info(fmt.Sprintf("auth-check 'auto' resolved to '%s'", checkType))
} else {
checkType = auth.CheckType(*authCheck)
}
if checkType == auth.CheckNone {
log.Warn("INSECURE: auth checker is disabled")
}
checker, err := auth.NewChecker(checkType, client.NewInCluster)
if err != nil {
log.WithError(err).Fatal("auth checker error")
}

server.Start(&server.Config{
Port: *port,
CertFile: *cert,
Expand All @@ -91,5 +112,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.

23 changes: 12 additions & 11 deletions pkg/handler/loki.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package handler

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
Expand All @@ -12,9 +11,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 Expand Up @@ -66,21 +65,24 @@ func EncodeQuery(url string) string {
return unspaced
}

func getLokiError(resp []byte, code int) string {
func getLokiError(resp []byte, code int) (int, string) {
var f map[string]string
if code == http.StatusBadRequest {
return fmt.Sprintf("Loki message: %s", resp)
return code, fmt.Sprintf("Loki message: %s", resp)
}
if code == http.StatusForbidden {
return code, fmt.Sprintf("Forbidden: %s", resp)
}
err := json.Unmarshal(resp, &f)
if err != nil {
hlog.WithError(err).Errorf("cannot unmarshal, response was: %v", string(resp))
return fmt.Sprintf("Unknown error from Loki\ncannot unmarshal\n%s", resp)
return http.StatusBadRequest, fmt.Sprintf("Unknown error from Loki\ncannot unmarshal\n%s", resp)
}
message, ok := f["message"]
if !ok {
return "Unknown error from Loki\nno message found"
return http.StatusBadRequest, "Unknown error from Loki\nno message found"
}
return fmt.Sprintf("Loki message: %s", message)
return http.StatusBadRequest, fmt.Sprintf("Loki message: %s", message)
}

func executeLokiQuery(flowsURL string, lokiClient httpclient.Caller) ([]byte, int, error) {
Expand All @@ -96,11 +98,10 @@ func executeLokiQuery(flowsURL string, lokiClient httpclient.Caller) ([]byte, in
return nil, http.StatusServiceUnavailable, err
}
if code != http.StatusOK {
msg := getLokiError(resp, code)
return nil, http.StatusBadRequest, errors.New(msg)
newCode, msg := getLokiError(resp, code)
return nil, newCode, fmt.Errorf("[%d] %s", code, msg)
}
code = http.StatusOK
return resp, code, nil
return resp, http.StatusOK, nil
}

func fetchSingle(lokiClient httpclient.Caller, flowsURL string, merger loki.Merger) (int, error) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/handler/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ func getLabelValues(cfg *loki.Config, lokiClient httpclient.Caller, label string
return nil, http.StatusServiceUnavailable, err
}
if code != http.StatusOK {
msg := getLokiError(resp, code)
return nil, http.StatusBadRequest, errors.New(msg)
newCode, msg := getLokiError(resp, code)
return nil, newCode, errors.New(msg)
}
hlog.Tracef("GetFlows raw response: %s", resp)
var lvr model.LabelValuesResponse
Expand Down
123 changes: 123 additions & 0 deletions pkg/kubernetes/auth/check_auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
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, apiProvider client.APIProvider) (Checker, error) {
switch typez {
case CheckNone:
return &NoopChecker{}, nil
case CheckAuthenticated:
return &BearerTokenChecker{apiProvider: apiProvider, predicates: []tokenReviewPredicate{mustBeAuthenticated}}, nil
case CheckAdmin:
return &BearerTokenChecker{apiProvider: apiProvider, predicates: []tokenReviewPredicate{mustBeAuthenticated, mustBeClusterAdmin}}, 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 BearerTokenChecker struct {
Checker
apiProvider client.APIProvider
predicates []tokenReviewPredicate
}

func (c *BearerTokenChecker) 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, c.predicates); err != nil {
return err
}

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