Skip to content

Commit

Permalink
Added a collection type struct for authorizations
Browse files Browse the repository at this point in the history
  • Loading branch information
fredjeck committed Apr 9, 2024
1 parent c2432e6 commit 753a894
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 100 deletions.
47 changes: 1 addition & 46 deletions config/authorization.go → authz/authorization.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package config
package authz

import (
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"regexp"
"strings"

Expand Down Expand Up @@ -217,46 +215,3 @@ func (auth *Authorization) ConfigurePath(path string, methods string) error {
}
return nil
}

// LoadAllAuthorizations loads all the client authorization yaml files from the provided directory
func LoadAllAuthorizations(dir string) (map[string]*Authorization, error) {

fileInfo, err := os.Stat(dir)
if err != nil {
return nil, err
}

if !fileInfo.IsDir() {
return nil, fmt.Errorf("'%s' is not a directory", dir)
}

authz := make(map[string]*Authorization)

err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

// Check if the file is a regular file and has a YAML extension
if !info.IsDir() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) {
content, err := os.ReadFile(path)
if err != nil {
fmt.Println("Error:", err)

}
conf, err := NewAuthorizationFromYaml(content)
if err != nil {
slog.Error(fmt.Sprintf("unable to load '%s' see details for errors", path), slog.Any("error", err))
}
authz[conf.ClientID] = conf
}

return nil
})

if err != nil {
slog.Error(fmt.Sprintf("an error occured while load authorization files from '%s' see details for errors", dir), slog.Any("error", err))
}

return authz, nil
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package config
package authz

import (
"testing"
Expand Down
96 changes: 96 additions & 0 deletions authz/authorizations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Package authz contains all authorizations related content
package authz

import (
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
)

// Authorizations is a collection of multiple client authorizations
type Authorizations struct {
authorizations map[string]*Authorization
}

// NewAuthorizations instantiates a new Authorizations object
func NewAuthorizations() *Authorizations {
return &Authorizations{
authorizations: make(map[string]*Authorization),
}
}

// Add appends the provided auth configuration to the collection
func (a *Authorizations) Add(auth *Authorization) error {
if len(auth.ClientID) == 0 {
return errors.New("cannot add an empty clientID")
}
a.authorizations[auth.ClientID] = auth
return nil
}

// IsAllowed ensures the provided clientID is configured for accessing the provided path with the given method
func (a *Authorizations) IsAllowed(clientID string, path string, method HTTPMethod) (bool, error) {
reason := ""
allowed := true

auth, authFound := a.authorizations[clientID]
if !authFound || !auth.IsAllowed(path, method) {
allowed = false
if !authFound {
reason = fmt.Sprintf("no authz configuration defined for %s", clientID)
} else {
reason = fmt.Sprintf("%s is not authorized to access %s %s", clientID, method, path)
}
}

if !allowed {
return false, errors.New(reason)
}
return true, nil
}

// LoadAll loads all the client authorization yaml files from the provided directory
func LoadAll(dir string) (*Authorizations, error) {

fileInfo, err := os.Stat(dir)
if err != nil {
return nil, err
}

if !fileInfo.IsDir() {
return nil, fmt.Errorf("'%s' is not a directory", dir)
}

authz := NewAuthorizations()

err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

// Check if the file is a regular file and has a YAML extension
if !info.IsDir() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) {
content, err := os.ReadFile(path)
if err != nil {
fmt.Println("Error:", err)

}
conf, err := NewAuthorizationFromYaml(content)
if err != nil {
slog.Error(fmt.Sprintf("unable to load '%s' see details for errors", path), slog.Any("error", err))
}
authz.authorizations[conf.ClientID] = conf
}

return nil
})

if err != nil {
slog.Error(fmt.Sprintf("an error occured while load authorization files from '%s' see details for errors", dir), slog.Any("error", err))
}

return authz, nil
}
7 changes: 4 additions & 3 deletions cmd/jarl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package main is the main Jarl executable content
package main

import (
Expand All @@ -22,7 +23,7 @@ import (
"os/signal"
"syscall"

"github.com/fredjeck/jarl/config"
"github.com/fredjeck/jarl/authz"
"github.com/fredjeck/jarl/logging"
"github.com/fredjeck/jarl/server"
)
Expand All @@ -40,14 +41,14 @@ func main() {

flag.Parse()

conf := &config.Configuration{
conf := &server.Configuration{
HTTPListenOn: fmt.Sprintf(":%s", *httpPort),
GRPCListenOn: fmt.Sprintf(":%s", *grpcPort),
HTTPAuthZHeader: *header,
ClientsConfigurationPath: *configuration,
}

auths, err := config.LoadAllAuthorizations(*configuration)
auths, err := authz.LoadAll(*configuration)
if err != nil {
slog.Error(fmt.Sprintf("unable to load client configurations from '%s'", *configuration), slog.Any("error", logging.KeyError))
os.Exit(1)
Expand Down
11 changes: 0 additions & 11 deletions config/config.go

This file was deleted.

13 changes: 13 additions & 0 deletions server/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Package config provides configuration support for the Jarl server
package server

import "github.com/fredjeck/jarl/authz"

// Configuration stores the configuration options for the Jarl server
type Configuration struct {
HTTPListenOn string // HTTPListenOn stores the InetAddr on which the HTTP Server is listening for inbound connections
GRPCListenOn string // GRPCListenOn stores the InetAddr on which the HTTP Server is listening for inbound connections
ClientsConfigurationPath string // ClientsConfigurationPath stores the path where the client configurations are stored
HTTPAuthZHeader string // HTTPAuthZHeader contains the name of the http header element which will be matchted for clientID
Authorizations *authz.Authorizations // Authorizations stores the configured authorizations
}
5 changes: 2 additions & 3 deletions server/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

authv2 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2"
authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/fredjeck/jarl/config"
"github.com/fredjeck/jarl/logging"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
Expand All @@ -18,7 +17,7 @@ import (
// GRPCAuthzServer implements an Envoy custom GRPC V2 and V3 authorization filter
type GRPCAuthzServer struct {
grpcServer *grpc.Server
configuration *config.Configuration
configuration *Configuration
port int
ready chan bool
state ServingStatus
Expand All @@ -27,7 +26,7 @@ type GRPCAuthzServer struct {
}

// NewGRPCAuthzServer instantiates a new GRPC AuthZ serer but does not start it
func NewGRPCAuthzServer(configuration *config.Configuration) *GRPCAuthzServer {
func NewGRPCAuthzServer(configuration *Configuration) *GRPCAuthzServer {
return &GRPCAuthzServer{
configuration: configuration,
ready: make(chan bool),
Expand Down
18 changes: 7 additions & 11 deletions server/grpcv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
corev2 "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
authv2 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v2"
typev2 "github.com/envoyproxy/go-control-plane/envoy/type"
"github.com/fredjeck/jarl/config"
"github.com/fredjeck/jarl/authz"
"github.com/fredjeck/jarl/logging"
"google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
Expand All @@ -16,7 +16,7 @@ import (
// GRPCAuthzServerV2 implements Envoy custom GRPC V3 authorization filter
type GRPCAuthzServerV2 struct {
AuthzHeader string
Authorizations map[string]*config.Authorization
Authorizations *authz.Authorizations
}

func (s *GRPCAuthzServerV2) allow(request *authv2.CheckRequest) *authv2.CheckResponse {
Expand Down Expand Up @@ -72,23 +72,19 @@ func (s *GRPCAuthzServerV2) deny(request *authv2.CheckRequest, reason string) *a
// Check implements gRPC v2 check request.
func (s *GRPCAuthzServerV2) Check(_ context.Context, request *authv2.CheckRequest) (*authv2.CheckResponse, error) {
attrs := request.GetAttributes()
method := config.HTTPMethod(attrs.Request.Http.Method)
method := authz.HTTPMethod(attrs.Request.Http.Method)
// Determine whether to allow or deny the request.
clientID, headerExists := attrs.GetRequest().GetHttp().GetHeaders()[s.AuthzHeader]

reason := ""
allowed := true

if headerExists {
auth, authFound := s.Authorizations[clientID]
if !authFound || !auth.IsAllowed(attrs.Request.Http.Path, method) {
allowed = false
if !authFound {
reason = fmt.Sprintf("no authz configuration defined for %s", clientID)
} else {
reason = fmt.Sprintf("%s is not authorized to access %s %s", clientID, method, attrs.Request.Http.Path)
}
al, err := s.Authorizations.IsAllowed(clientID, attrs.Request.Http.Path, method)
if err != nil {
reason = err.Error()
}
allowed = al
} else {
allowed = false
reason = fmt.Sprintf("missing authz configuration header %s", s.AuthzHeader)
Expand Down
18 changes: 7 additions & 11 deletions server/grpcv3.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"github.com/fredjeck/jarl/config"
"github.com/fredjeck/jarl/authz"
"github.com/fredjeck/jarl/logging"
"google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc/codes"
Expand All @@ -16,7 +16,7 @@ import (
// GRPCAuthzServerV3 implements Envoy custom GRPC V3 authorization filter
type GRPCAuthzServerV3 struct {
AuthzHeader string
Authorizations map[string]*config.Authorization
Authorizations *authz.Authorizations
}

// Allows the requests by returning a positive outcoume
Expand Down Expand Up @@ -74,23 +74,19 @@ func (s *GRPCAuthzServerV3) deny(request *authv3.CheckRequest, reason string) *a
// Check implements gRPC v3 check request.
func (s *GRPCAuthzServerV3) Check(_ context.Context, request *authv3.CheckRequest) (*authv3.CheckResponse, error) {
attrs := request.GetAttributes()
method := config.HTTPMethod(attrs.Request.Http.Method)
method := authz.HTTPMethod(attrs.Request.Http.Method)
// Determine whether to allow or deny the request.
clientID, headerExists := attrs.GetRequest().GetHttp().GetHeaders()[s.AuthzHeader]

reason := ""
allowed := true

if headerExists {
auth, authFound := s.Authorizations[clientID]
if !authFound || !auth.IsAllowed(attrs.Request.Http.Path, method) {
allowed = false
if !authFound {
reason = fmt.Sprintf("no authz configuration defined for %s", clientID)
} else {
reason = fmt.Sprintf("%s is not authorized to access %s %s", clientID, method, attrs.Request.Http.Path)
}
al, err := s.Authorizations.IsAllowed(clientID, attrs.Request.Http.Path, method)
if err != nil {
reason = err.Error()
}
allowed = al
} else {
allowed = false
reason = fmt.Sprintf("missing authz configuration header %s", s.AuthzHeader)
Expand Down
5 changes: 2 additions & 3 deletions server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"net/http"
"sync"

"github.com/fredjeck/jarl/config"
"github.com/fredjeck/jarl/logging"
)

Expand All @@ -16,14 +15,14 @@ import (
// HTTP Server is only kept for health check purposes
type HTTPAuthzServer struct {
httpServer *http.Server
configuration *config.Configuration
configuration *Configuration
port int
ready chan bool
state ServingStatus
}

// NewHTTPAuthzServer instantiates a new HTTPAuthzServer but does not start it
func NewHTTPAuthzServer(configuration *config.Configuration) *HTTPAuthzServer {
func NewHTTPAuthzServer(configuration *Configuration) *HTTPAuthzServer {
return &HTTPAuthzServer{
ready: make(chan bool),
state: Stopped,
Expand Down
4 changes: 1 addition & 3 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ package server
import (
"fmt"
"sync"

"github.com/fredjeck/jarl/config"
)

const (
Expand Down Expand Up @@ -57,7 +55,7 @@ func (s *JarlAuthzServer) Stop() {
}

// NewJarlAuthzServer instantiates a new Authz server based on the provided configuration
func NewJarlAuthzServer(conf *config.Configuration) *JarlAuthzServer {
func NewJarlAuthzServer(conf *Configuration) *JarlAuthzServer {
return &JarlAuthzServer{
grpcServer: NewGRPCAuthzServer(conf),
httpServer: NewHTTPAuthzServer(conf),
Expand Down
Loading

0 comments on commit 753a894

Please sign in to comment.