Skip to content

Commit

Permalink
feat: use embedded open policy evaluation
Browse files Browse the repository at this point in the history
  • Loading branch information
lsjostro committed Feb 22, 2024
1 parent 8b9a18e commit 6fab342
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 586 deletions.
67 changes: 13 additions & 54 deletions authz/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ package authz

import (
"context"
"crypto/tls"
"errors"
"log/slog"
"net"
"net/http"
"net/http/httptrace"
"net/url"
"strings"
"time"
Expand All @@ -20,17 +17,15 @@ import (
"connectrpc.com/otelconnect"
cache_store "github.com/eko/gocache/lib/v4/store"
"github.com/gogo/googleapis/google/rpc"
"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.23.1"
"go.opentelemetry.io/otel/trace"
"golang.org/x/net/http2"
rpcstatus "google.golang.org/genproto/googleapis/rpc/status"

pb "github.com/shelmangroup/envoy-oidc-authserver/internal/gen/session/v1"
"github.com/shelmangroup/envoy-oidc-authserver/policy"
"github.com/shelmangroup/envoy-oidc-authserver/session"
"github.com/shelmangroup/envoy-oidc-authserver/store"
)
Expand All @@ -47,34 +42,10 @@ type Service struct {
cfg *Config
secretKey []byte
store *store.Store
authClient authv3connect.AuthorizationClient
sessionExpiration time.Duration
}

func NewService(cfg *Config, opaURL, secretKey string, redisAddrs []string) *Service {
var c authv3connect.AuthorizationClient
if opaURL != "" {
u, err := url.Parse(opaURL)
if err != nil {
slog.Error("OPA URL is invalid", slog.String("err", err.Error()))
}
slog.Info("OPA is enabled, all requests will be sent to OPA for authorization", slog.String("url", u.String()))
client := &http.Client{
Timeout: time.Second * 5,
Transport: otelhttp.NewTransport(
&http2.Transport{
AllowHTTP: true,
DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
},
otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace {
return otelhttptrace.NewClientTrace(ctx)
})),
}
c = authv3connect.NewAuthorizationClient(client, u.String(), connect.WithGRPC())
}

func NewService(cfg *Config, secretKey string, redisAddrs []string) *Service {
// Parse the session expiration time
ttl, err := time.ParseDuration(cfg.SessionExpiration)
if err != nil {
Expand All @@ -84,7 +55,6 @@ func NewService(cfg *Config, opaURL, secretKey string, redisAddrs []string) *Ser

return &Service{
cfg: cfg,
authClient: c,
sessionExpiration: ttl,
store: store.NewStore(redisAddrs, ttl),
secretKey: []byte(secretKey),
Expand All @@ -107,15 +77,9 @@ func (s *Service) Check(ctx context.Context, req *connect.Request[auth.CheckRequ
reqHeaders := httpReq.GetHeaders()
var resp *auth.CheckResponse
var provider *OIDCProvider
var hasAuthHeader bool

slog.Debug("client request headers", slog.Any("headers", reqHeaders))

if _, ok := reqHeaders["authorization"]; ok {
slog.Debug("client request authorization header is present")
hasAuthHeader = true
}

for name, value := range reqHeaders {
provider = s.cfg.Match(name, value)
if provider != nil {
Expand All @@ -135,29 +99,26 @@ func (s *Service) Check(ctx context.Context, req *connect.Request[auth.CheckRequ
attribute.String("client_id", provider.ClientID),
attribute.String("callback_uri", provider.CallbackURI),
attribute.String("cookie_name_prefix", provider.CookieNamePrefix),
attribute.Bool("opa_enabled", provider.OPAEnabled),
attribute.Bool("allow_auth_header", provider.AllowAuthHeader),
attribute.String("opa_policy", provider.OPAPolicy),
attribute.Bool("secure_cookie", provider.SecureCookie),
attribute.StringSlice("scopes", provider.Scopes),
attribute.String("header_match_name", provider.HeaderMatch.Name),
),
)

if provider.OPAEnabled && s.authClient != nil {
slog.Debug("OPA is enabled, sending request to OPA for authorization")
opaResp, err := s.authClient.Check(ctx, req)
// if OPA Policy is defined evaluate the request
if provider.OPAPolicy != "" {
allowed, err := policy.Eval(ctx, req.Msg, provider.OPAPolicy)
if err != nil {
span.RecordError(err, trace.WithStackTrace(true))
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
return connect.NewResponse(s.authResponse(false, envoy_type.StatusCode_BadGateway, nil, nil, err.Error())), nil
}
if opaResp.Msg.GetStatus().GetCode() == int32(rpc.PERMISSION_DENIED) {
slog.Debug("OPA denied request")
return opaResp, nil
}
if hasAuthHeader && provider.AllowAuthHeader && (opaResp.Msg.GetStatus().GetCode() == int32(rpc.OK)) {
slog.Debug("request has auth header and OPA allowed the request")
return opaResp, nil

if !allowed {
slog.Debug("OPA Policy denied the request")
span.SetStatus(codes.Error, "OPA Policy denied request")
return connect.NewResponse(s.authResponse(false, envoy_type.StatusCode_Forbidden, nil, nil, "OPA Policy denied request")), nil
}
}

Expand All @@ -169,8 +130,6 @@ func (s *Service) Check(ctx context.Context, req *connect.Request[auth.CheckRequ
resp = s.authResponse(false, envoy_type.StatusCode_BadGateway, nil, nil, err.Error())
}

// TODO: merge opaResp headers with resp headers (only okResponse headers)

// Return response to envoy
span.SetStatus(codes.Ok, "success")
return connect.NewResponse(resp), nil
Expand Down
3 changes: 1 addition & 2 deletions authz/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ type OIDCProvider struct {
Scopes []string `yaml:"scopes"`
CookieNamePrefix string `yaml:"cookieNamePrefix"`
SecureCookie bool `yaml:"secureCookie"`
OPAEnabled bool `yaml:"opaEnabled"`
AllowAuthHeader bool `yaml:"allowAuthHeaderPassThrough"`
OPAPolicy string `yaml:"opaPolicy"`
HeaderMatch HeaderMatch `yaml:"headerMatch"`
}

Expand Down
18 changes: 0 additions & 18 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,6 @@ services:
- serve
- /dex.yaml

opa:
image: docker.io/openpolicyagent/opa:latest-envoy-static
volumes:
- ./run/config/policy.rego:/policies/policy.rego
ports:
- "${PORT_OPA:-9191}:9191"
command:
- run
- --server
- --log-level=debug
- --set=plugins.envoy_ext_authz_grpc.addr=:9191
- --set=plugins.envoy_ext_authz_grpc.path=envoy/authz/allow
- --set=decision_logs.console=true
- --set=status.console=true
- --ignore=.*
- /policies/policy.rego
network_mode: host

otel:
image: docker.io/otel/opentelemetry-collector-contrib
volumes:
Expand Down
30 changes: 21 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/eko/gocache/store/redis/v4 v4.2.1
github.com/fernet/fernet-go v0.0.0-20240119011108-303da6aec611
github.com/gogo/googleapis v1.4.1
github.com/google/uuid v1.5.0
github.com/google/uuid v1.6.0
github.com/grokify/go-pkce v0.2.3
github.com/stretchr/testify v1.8.4
github.com/zitadel/oidc/v3 v3.8.1
Expand All @@ -24,20 +24,31 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0
go.opentelemetry.io/otel/sdk v1.21.0
golang.org/x/net v0.19.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97
google.golang.org/grpc v1.60.1
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c
google.golang.org/grpc v1.61.0
)

require (
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/prometheus/client_golang v1.18.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/yashtewari/glob-intersection v0.2.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

require (
Expand All @@ -57,6 +68,7 @@ require (
github.com/lmittmann/tint v1.0.3
github.com/mattn/go-isatty v0.0.20
github.com/muhlemmer/gu v0.3.1 // indirect
github.com/open-policy-agent/opa v0.61.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/peterbourgon/ff/v4 v4.0.0-alpha.4
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand All @@ -75,7 +87,7 @@ require (
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/protobuf v1.32.0
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1
Expand Down
Loading

0 comments on commit 6fab342

Please sign in to comment.