diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/init.go | 2 | ||||
| -rw-r--r-- | app/server.go | 24 | ||||
| -rw-r--r-- | app/server_test.go | 63 | ||||
| -rw-r--r-- | app/services/check.go | 61 | ||||
| -rw-r--r-- | app/services/check_test.go | 95 |
5 files changed, 244 insertions, 1 deletions
diff --git a/app/init.go b/app/init.go index f5d1dc4b..3c4e757e 100644 --- a/app/init.go +++ b/app/init.go @@ -10,6 +10,6 @@ import ( func init() { ioc.RegisterSingleton[*zerolog.Logger](ioc.Default, func() *zerolog.Logger { - return log.New(os.Stdout, log.Fields{}) + return log.New(os.Stdout, log.Fields{"app": "authzd"}) }) } diff --git a/app/server.go b/app/server.go new file mode 100644 index 00000000..3ce0aadc --- /dev/null +++ b/app/server.go @@ -0,0 +1,24 @@ +package app + +import ( + "context" + + auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/authzd.git/app/services" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +type Server struct { + *grpc.Server +} + +func NewServer(ctx context.Context, options ...grpc.ServerOption) *Server { + server := grpc.NewServer(options...) + auth.RegisterAuthorizationServer(server, services.NewCheckService()) + reflection.Register(server) + + return &Server{ + Server: server, + } +} diff --git a/app/server_test.go b/app/server_test.go new file mode 100644 index 00000000..ff34487a --- /dev/null +++ b/app/server_test.go @@ -0,0 +1,63 @@ +package app + +import ( + "context" + "net" + "testing" + + auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" +) + +type HTTPRequest = auth.AttributeContext_HttpRequest + +func TestServer(t *testing.T) { + socket := bufconn.Listen(1024 * 1024) + srv := NewServer(t.Context()) + + defer srv.GracefulStop() + go func() { + require.NoError(t, srv.Serve(socket)) + }() + + connection, err := grpc.DialContext( + t.Context(), + "bufnet", + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return socket.Dial() + }), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + require.NoError(t, err) + defer connection.Close() + + client := auth.NewAuthorizationClient(connection) + + t.Run("CheckRequest", func(t *testing.T) { + tt := []struct { + http *HTTPRequest + status codes.Code + }{ + {status: codes.OK, http: &HTTPRequest{Method: "GET", Path: "/"}}, + } + + for _, example := range tt { + t.Run(example.http.Path, func(t *testing.T) { + response, err := client.Check(t.Context(), &auth.CheckRequest{ + Attributes: &auth.AttributeContext{ + Request: &auth.AttributeContext_Request{ + Http: example.http, + }, + }, + }) + require.NoError(t, err) + assert.Equal(t, int32(example.status), response.Status.Code) + }) + } + }) +} diff --git a/app/services/check.go b/app/services/check.go new file mode 100644 index 00000000..d0004572 --- /dev/null +++ b/app/services/check.go @@ -0,0 +1,61 @@ +package services + +import ( + "context" + + core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + types "github.com/envoyproxy/go-control-plane/envoy/type/v3" + status "google.golang.org/genproto/googleapis/rpc/status" + "google.golang.org/grpc/codes" +) + +type CheckService struct { + auth.UnimplementedAuthorizationServer +} + +func NewCheckService() *CheckService { + return &CheckService{} +} + +func (svc *CheckService) Check(ctx context.Context, request *auth.CheckRequest) (*auth.CheckResponse, error) { + if svc.isAllowed(ctx, request) { + return svc.OK(ctx), nil + } + return svc.Denied(ctx), nil +} + +func (svc *CheckService) isAllowed(ctx context.Context, r *auth.CheckRequest) bool { + return true +} + +func (svc *CheckService) OK(ctx context.Context) *auth.CheckResponse { + return &auth.CheckResponse{ + Status: &status.Status{ + Code: int32(codes.OK), + }, + HttpResponse: &auth.CheckResponse_OkResponse{ + OkResponse: &auth.OkHttpResponse{ + Headers: []*core.HeaderValueOption{}, + HeadersToRemove: []string{}, + ResponseHeadersToAdd: []*core.HeaderValueOption{}, + }, + }, + } +} + +func (svc *CheckService) Denied(ctx context.Context) *auth.CheckResponse { + return &auth.CheckResponse{ + Status: &status.Status{ + Code: int32(codes.PermissionDenied), + }, + HttpResponse: &auth.CheckResponse_DeniedResponse{ + DeniedResponse: &auth.DeniedHttpResponse{ + Status: &types.HttpStatus{ + Code: types.StatusCode_Unauthorized, + }, + Headers: []*core.HeaderValueOption{}, + }, + }, + } +} diff --git a/app/services/check_test.go b/app/services/check_test.go new file mode 100644 index 00000000..4eb396bb --- /dev/null +++ b/app/services/check_test.go @@ -0,0 +1,95 @@ +package services + +import ( + "strings" + "testing" + + core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + auth "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func TestCheckService(t *testing.T) { + svc := NewCheckService() + + t.Run("allows access", func(t *testing.T) { + idToken := "header.payload.signature" + accessToken := "f88f60df11e458b594c80b299aee05f8e5805c65c3e779cc6fbc606c4ac36227" + refreshToken := "0847d325d6e4f021c4baaae0ddb425dbd8795807a4751cd2131bec8e8a9aee24" + + cookies := []string{ + "bearer_token=" + accessToken + ";", + "id_token=" + idToken + ";", + "refresh_token=" + refreshToken, + } + + response, err := svc.Check(t.Context(), &auth.CheckRequest{ + Attributes: &auth.AttributeContext{ + Source: &auth.AttributeContext_Peer{ + Address: &core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + Address: "127.0.0.1", + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 52358, + }, + }, + }, + }, + }, + Destination: &auth.AttributeContext_Peer{ + Address: &core.Address{ + Address: &core.Address_SocketAddress{ + SocketAddress: &core.SocketAddress{ + Address: "127.0.0.1", + PortSpecifier: &core.SocketAddress_PortValue{ + PortValue: 10000, + }, + }, + }, + }, + }, + Request: &auth.AttributeContext_Request{ + Time: ×tamppb.Timestamp{Seconds: 1747937928, Nanos: 476481000}, + Http: &auth.AttributeContext_HttpRequest{ + Id: "1248474133684962828", + Method: "GET", + Headers: map[string]string{ + ":authority": "localhost:10000", + ":method": "GET", + ":path": "/health", + ":scheme": "http", + "accept": "*/*", + "accept-encoding": "gzip, deflate, br, zstd", + "accept-language": "en-US,en;q=0.9", + "cache-control": "max-age=0", + "content-length": "64", + "content-type": "application/json", + "cookie": strings.Join(cookies, "; "), + "origin": "http://localhost:10000", + "referer": "http://localhost:10000/dashboard", + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": "Linux", + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-origin", + "x-forwarded-proto": "http", + "x-request-id": "7e064610-9e19-4a38-8354-0de0b5fbd7c6", + }, + Path: "/health", + Host: "localhost:10000", + Scheme: "http", + Protocol: "HTTP/1.1", + }, + }, + MetadataContext: &core.Metadata{}, + RouteMetadataContext: &core.Metadata{}, + }, + }) + + require.NoError(t, err) + assert.NotNil(t, response.GetOkResponse()) + }) +} |
