Skip to content

Commit

Permalink
Add RBAC to Schellar
Browse files Browse the repository at this point in the history
  • Loading branch information
Jozef Volak committed Jun 27, 2024
1 parent 461e6cd commit a692969
Show file tree
Hide file tree
Showing 10 changed files with 1,908 additions and 158 deletions.
6 changes: 5 additions & 1 deletion schellar/.env-SAMPLE
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ POSTGRES_PASSWORD=postgres

# Default Endpoint Query used in Playground
# If schellar is behind krakend, use PLAYGROUND_QUERY_ENDPOIND=/api/schedule
# PLAYGROUND_QUERY_ENDPOINT="/query"
# PLAYGROUND_QUERY_ENDPOINT="/query"

ADMIN_ROLES=OWNER,FRINXio
ADMIN_GROUPS=network-admin,OWNER,FRINXio
ADMIN_USER=schellar
54 changes: 18 additions & 36 deletions schellar/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,30 @@ module github.com/frinx/schellar
go 1.21

require (
github.com/99designs/gqlgen v0.17.44
github.com/jackc/pgx/v4 v4.18.1
github.com/99designs/gqlgen v0.17.49
github.com/jackc/pgx/v4 v4.18.3
github.com/jackc/tern v1.13.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.1
github.com/prometheus/client_golang v1.19.1
github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.0
github.com/vektah/gqlparser/v2 v2.5.11
github.com/sirupsen/logrus v1.9.3
github.com/vektah/gqlparser/v2 v2.5.16
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22
)

require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.9 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/puddle v1.3.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/sosodev/duration v1.2.0 // indirect
golang.org/x/crypto v0.6.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgtype v1.14.3 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/prometheus/common v0.54.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
google.golang.org/protobuf v1.34.2 // indirect
)
1,622 changes: 1,578 additions & 44 deletions schellar/go.sum

Large diffs are not rendered by default.

110 changes: 55 additions & 55 deletions schellar/graph/generated.go

Large diffs are not rendered by default.

92 changes: 92 additions & 0 deletions schellar/graph/helper.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package graph

import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"time"

"github.com/frinx/schellar/graph/model"
"github.com/frinx/schellar/ifc"
"github.com/frinx/schellar/utils"

"github.com/frinx/schellar/scheduler"

"github.com/99designs/gqlgen/graphql"
)

func ValidateName(name string) error {
Expand Down Expand Up @@ -231,3 +237,89 @@ func getSchedulesLastBefore(schedules []*model.Schedule, last *int, before *stri

return filteredSchedules, hasPreviousPage, hasNextPage
}

func extractAuthHeader(ctx context.Context) []string {

// Extract auth headers from request
headers := graphql.GetOperationContext(ctx).Headers
rbacHeaders := []string{headers.Get("x-auth-user-roles"), headers.Get("x-auth-user-groups")}

// Filter out empty strings
var nonEmptyHeaders []string
for _, header := range rbacHeaders {
if header != "" {
nonEmptyHeaders = append(nonEmptyHeaders, header)
}
}

// Join non-empty headers with a comma
userHeaderList := strings.Split(strings.Join(nonEmptyHeaders, ","), ",")
return utils.RemoveDuplicates(userHeaderList)
}

func extractUserHeader(ctx context.Context) error {

// Extract auth headers from request
headers := graphql.GetOperationContext(ctx).Headers
userHeader := headers.Get("from")

if userHeader == "" {
return errors.New("Missing header From")
}
return nil
}

func getAdminValues() []string {

adminRoles := []string{os.Getenv("ADMIN_ROLES"), os.Getenv("ADMIN_GROUPS")}

// Filter out empty strings
var nonEmptyRoles []string
for _, header := range adminRoles {
if header != "" {
nonEmptyRoles = append(nonEmptyRoles, header)
}
}

// Join non-empty headers with a comma
adminRolesList := strings.Split(strings.Join(nonEmptyRoles, ","), ",")
return utils.RemoveDuplicates(adminRolesList)
}

// hasCommonElement checks if at least one string from list1 exists in list2
func hasCommonElement(list1, list2 []string) bool {
// Create a map to store adminHeaders for O(1) average time complexity lookups
adminHeaderMap := make(map[string]bool)
for _, header := range list2 {
adminHeaderMap[header] = true
}

// Check each userHeader if it exists in adminHeaderMap
for _, header := range list1 {
if adminHeaderMap[header] {
return true
}
}

return false
}

func checkPermissions(ctx context.Context) error {

// err := extractUserHeader(ctx)

// if err != nil {
// return err
// }

userHaders := extractAuthHeader(ctx)
adminRoles := utils.GetAdminValues()

// Check if at least one userHeader exists in adminHeaders
if hasCommonElement(userHaders, adminRoles) {
return nil
} else {
return errors.New("User has no permission to process operation")
}

}
43 changes: 39 additions & 4 deletions schellar/graph/schema.resolvers.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 52 additions & 12 deletions schellar/main.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package main

import (
"context"
"log"
"net/http"
"os"
"strings"
"time"

"github.com/99designs/gqlgen/graphql"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/frinx/schellar/graph"
"github.com/frinx/schellar/ifc"
"github.com/vektah/gqlparser/v2/gqlerror"

"github.com/frinx/schellar/scheduler"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/sirupsen/logrus"
Expand All @@ -22,8 +27,21 @@ var config = scheduler.Configuration

func main() {

logLevel := ifc.GetEnvOrDefault("LOG_LEVEL", "DEBUG")
setupLogging()

if err := scheduler.StartScheduler(); err != nil {
logrus.Fatalf("Error during scheduler startup: %v", err)
}

port := getPort()
startApi()

log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}

func setupLogging() {
logLevel := ifc.GetEnvOrDefault("LOG_LEVEL", "DEBUG")
switch strings.ToLower(logLevel) {
case "debug":
logrus.SetLevel(logrus.DebugLevel)
Expand All @@ -37,22 +55,14 @@ func main() {
default:
logrus.SetLevel(logrus.InfoLevel)
}
}

var err error
err = scheduler.StartScheduler()
if err != nil {
logrus.Fatalf("Error during scheduler startup: %v", err)
}

func getPort() string {
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}

startApi()

log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
return port
}

func startApi() {
Expand All @@ -64,6 +74,36 @@ func startApi() {

srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}}))

srv.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
timestamp := time.Now().Format("2006-01-02 15:04:05")
oc := graphql.GetOperationContext(ctx)

userHeader := oc.Headers.Get("from")
roleHeader := []string{oc.Headers.Get("x-auth-user-roles")}
groupHeader := []string{oc.Headers.Get("x-auth-user-groups")}

logrus.WithFields(logrus.Fields{
"timestamp": timestamp,
"operation": oc.OperationName,
"user": userHeader,
"roles": roleHeader,
"groups": groupHeader,
}).Info("Audit: ")

if userHeader == "" {
return func(ctx context.Context) *graphql.Response {
logrus.Warnf("Missing header From")
return &graphql.Response{
Errors: []*gqlerror.Error{
gqlerror.Errorf("Missing header From"),
},
}
}
}

return next(ctx)
})

http.Handle("/", playground.ApolloSandboxHandler("GraphQL playground", playgroundQeryEndpoint))
http.Handle("/query", srv)
http.Handle("/metrics", promhttp.Handler())
Expand Down
Loading

0 comments on commit a692969

Please sign in to comment.