This commit is contained in:
Joe Kralicky 2025-03-17 17:55:26 +00:00
parent d588135b3a
commit d89a7d97d7
No known key found for this signature in database
GPG key ID: 75C4875F34A9FB79
4 changed files with 225 additions and 65 deletions

View file

@ -1,27 +1,40 @@
package authorize package authorize
import ( import (
"bufio"
"bytes"
"crypto/sha256"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net/url" "net/url"
"slices" "slices"
"strconv"
"strings" "strings"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
"github.com/klauspost/compress/zstd"
extensions_ssh "github.com/pomerium/envoy-custom/api/extensions/filters/network/ssh" extensions_ssh "github.com/pomerium/envoy-custom/api/extensions/filters/network/ssh"
extensions_session_recording "github.com/pomerium/envoy-custom/api/extensions/filters/network/ssh/filters/session_recording"
"github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/sessions" "github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/pkg/identity" "github.com/pomerium/pomerium/pkg/identity"
"github.com/pomerium/pomerium/pkg/identity/oauth" "github.com/pomerium/pomerium/pkg/identity/oauth"
gossh "golang.org/x/crypto/ssh" gossh "golang.org/x/crypto/ssh"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protodelim"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"google.golang.org/protobuf/types/known/wrapperspb" "google.golang.org/protobuf/types/known/wrapperspb"
) )
@ -33,6 +46,76 @@ type StreamState struct {
MethodsAuthenticated []string MethodsAuthenticated []string
} }
func (a *Authorize) RecordingFinalized(
stream grpc.ClientStreamingServer[extensions_session_recording.RecordingData, emptypb.Empty],
) error {
msg, err := stream.Recv()
if err != nil {
return err
}
md := msg.GetMetadata()
if md == nil {
return fmt.Errorf("first message did not contain metadata")
}
log.Ctx(stream.Context()).Info().Str("info", protojson.Format(md)).Msg("new recording")
var recording []byte
READ:
for {
msg, err := stream.Recv()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
}
switch data := msg.Data.(type) {
case *extensions_session_recording.RecordingData_Chunk:
recording = append(recording, data.Chunk...)
case *extensions_session_recording.RecordingData_Checksum:
actual := sha256.Sum256(recording)
if actual != [32]byte(data.Checksum) {
return fmt.Errorf("checksum mismatch")
}
break READ
}
}
r, err := zstd.NewReader(bytes.NewReader(recording))
if err != nil {
return fmt.Errorf("failed to create zstd reader: %w", err)
}
switch md.Format {
case extensions_session_recording.Format_AsciicastFormat:
log.Ctx(stream.Context()).Info().Int("compressed_size", len(recording)).Msg("asciicast recording received")
case extensions_session_recording.Format_RawFormat:
reader := bufio.NewReader(r)
var header extensions_session_recording.Header
if err := protodelim.UnmarshalFrom(reader, &header); err != nil {
return fmt.Errorf("failed to unmarshal header: %w", err)
}
var packets []*extensions_session_recording.Packet
for {
var packet extensions_session_recording.Packet
err := protodelim.UnmarshalFrom(reader, &packet)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return fmt.Errorf("failed to unmarshal packet: %w", err)
}
packets = append(packets, &packet)
}
log.Ctx(stream.Context()).Info().Int("compressed_size", len(recording)).Int("packet_count", len(packets)).Msg("recording received")
}
return nil
}
var activeStreamIds sync.Map
func (a *Authorize) ManageStream( func (a *Authorize) ManageStream(
server extensions_ssh.StreamManagement_ManageStreamServer, server extensions_ssh.StreamManagement_ManageStreamServer,
) error { ) error {
@ -72,6 +155,7 @@ func (a *Authorize) ManageStream(
var state StreamState var state StreamState
deviceAuthSuccess := &atomic.Bool{} deviceAuthSuccess := &atomic.Bool{}
deviceAuthDone := make(chan struct{})
errC := make(chan error, 1) errC := make(chan error, 1)
a.activeStreamsMu.Lock() a.activeStreamsMu.Lock()
@ -80,7 +164,6 @@ func (a *Authorize) ManageStream(
for { for {
select { select {
case err := <-errC: case err := <-errC:
return err return err
case req, ok := <-recvC: case req, ok := <-recvC:
if !ok { if !ok {
@ -92,6 +175,10 @@ func (a *Authorize) ManageStream(
case *extensions_ssh.StreamEvent_DownstreamConnected: case *extensions_ssh.StreamEvent_DownstreamConnected:
fmt.Println("downstream connected") fmt.Println("downstream connected")
_ = event _ = event
case *extensions_ssh.StreamEvent_UpstreamConnected:
fmt.Printf("upstream connected: %d\n", event.UpstreamConnected.GetStreamId())
activeStreamIds.Store(event.UpstreamConnected.GetStreamId(), state)
defer activeStreamIds.Delete(event.UpstreamConnected.GetStreamId())
case nil: case nil:
} }
case *extensions_ssh.ClientMessage_AuthRequest: case *extensions_ssh.ClientMessage_AuthRequest:
@ -119,32 +206,35 @@ func (a *Authorize) ManageStream(
state.PublicKey = pubkeyReq.PublicKey state.PublicKey = pubkeyReq.PublicKey
if authReq.Username == "" && authReq.Hostname == "" { if authReq.Username == "" && authReq.Hostname == "" {
pkData, _ := anypb.New(&extensions_ssh.PublicKeyAllowResponse{
PublicKey: state.PublicKey,
Permissions: &extensions_ssh.Permissions{
PermitPortForwarding: true,
PermitAgentForwarding: true,
PermitX11Forwarding: true,
PermitPty: true,
PermitUserRc: true,
ValidBefore: timestamppb.New(time.Now().Add(-1 * time.Minute)),
ValidAfter: timestamppb.New(time.Now().Add(12 * time.Hour)),
},
})
resp := extensions_ssh.ServerMessage{ resp := extensions_ssh.ServerMessage{
Message: &extensions_ssh.ServerMessage_AuthResponse{ Message: &extensions_ssh.ServerMessage_AuthResponse{
AuthResponse: &extensions_ssh.AuthenticationResponse{ AuthResponse: &extensions_ssh.AuthenticationResponse{
Response: &extensions_ssh.AuthenticationResponse_Allow{ Response: &extensions_ssh.AuthenticationResponse_Allow{
Allow: &extensions_ssh.AllowResponse{ Allow: &extensions_ssh.AllowResponse{
Username: state.Username, Username: state.Username,
Hostname: state.Hostname, Target: &extensions_ssh.AllowResponse_Internal{
AllowedMethods: []*extensions_ssh.AllowedMethod{ Internal: &extensions_ssh.InternalTarget{},
{ },
Method: "publickey", },
MethodData: pkData, },
},
},
}
sendC <- &resp
continue
} else if authReq.Username == "_mirror" && authReq.Hostname != "" {
id, _ := strconv.ParseUint(authReq.Hostname, 10, 64)
resp := extensions_ssh.ServerMessage{
Message: &extensions_ssh.ServerMessage_AuthResponse{
AuthResponse: &extensions_ssh.AuthenticationResponse{
Response: &extensions_ssh.AuthenticationResponse_Allow{
Allow: &extensions_ssh.AllowResponse{
Target: &extensions_ssh.AllowResponse_MirrorSession{
MirrorSession: &extensions_ssh.MirrorSessionTarget{
SourceId: id,
Mode: extensions_ssh.MirrorSessionTarget_ReadWrite,
}, },
}, },
Target: extensions_ssh.Target_Internal,
}, },
}, },
}, },
@ -207,8 +297,8 @@ func (a *Authorize) ManageStream(
infoReq := extensions_ssh.KeyboardInteractiveInfoPrompts{ infoReq := extensions_ssh.KeyboardInteractiveInfoPrompts{
Name: "Sign in with " + idp.GetType(), Name: "Sign in with " + idp.GetType(),
Instruction: deviceAuthResp.VerificationURIComplete, Instruction: deviceAuthResp.VerificationURIComplete,
Prompts: []*extensions_ssh.KeyboardInteractiveInfoPrompts_Prompt{ Prompts: []*extensions_ssh.KeyboardInteractiveInfoPrompts_Prompt{
{}, // {},
}, },
} }
@ -243,6 +333,7 @@ func (a *Authorize) ManageStream(
} }
fmt.Println(token) fmt.Println(token)
deviceAuthSuccess.Store(true) deviceAuthSuccess.Store(true)
close(deviceAuthDone)
}() }()
} }
case *extensions_ssh.ClientMessage_InfoResponse: case *extensions_ssh.ClientMessage_InfoResponse:
@ -254,31 +345,45 @@ func (a *Authorize) ManageStream(
fmt.Println(respInfo.Responses) fmt.Println(respInfo.Responses)
} }
} }
select {
case <-deviceAuthDone:
case <-ctx.Done():
}
if deviceAuthSuccess.Load() { if deviceAuthSuccess.Load() {
state.MethodsAuthenticated = append(state.MethodsAuthenticated, "keyboard-interactive") state.MethodsAuthenticated = append(state.MethodsAuthenticated, "keyboard-interactive")
} else { } else {
retryReq := extensions_ssh.KeyboardInteractiveInfoPrompts{
Name: "",
Instruction: "Login not successful yet, try again",
Prompts: []*extensions_ssh.KeyboardInteractiveInfoPrompts_Prompt{
{},
},
}
infoReqAny, _ := anypb.New(&retryReq)
resp := extensions_ssh.ServerMessage{ resp := extensions_ssh.ServerMessage{
Message: &extensions_ssh.ServerMessage_AuthResponse{ Message: &extensions_ssh.ServerMessage_AuthResponse{
AuthResponse: &extensions_ssh.AuthenticationResponse{ AuthResponse: &extensions_ssh.AuthenticationResponse{
Response: &extensions_ssh.AuthenticationResponse_InfoRequest{ Response: &extensions_ssh.AuthenticationResponse_Deny{
InfoRequest: &extensions_ssh.InfoRequest{ Deny: &extensions_ssh.DenyResponse{},
Method: "keyboard-interactive",
Request: infoReqAny,
},
}, },
}, },
}, },
} }
sendC <- &resp sendC <- &resp
// retryReq := extensions_ssh.KeyboardInteractiveInfoPrompts{
// Name: "",
// Instruction: "Login not successful yet, try again",
// Prompts: []*extensions_ssh.KeyboardInteractiveInfoPrompts_Prompt{
// // {},
// },
// }
// infoReqAny, _ := anypb.New(&retryReq)
// resp := extensions_ssh.ServerMessage{
// Message: &extensions_ssh.ServerMessage_AuthResponse{
// AuthResponse: &extensions_ssh.AuthenticationResponse{
// Response: &extensions_ssh.AuthenticationResponse_InfoRequest{
// InfoRequest: &extensions_ssh.InfoRequest{
// Method: "keyboard-interactive",
// Request: infoReqAny,
// },
// },
// },
// },
// }
// sendC <- &resp
continue continue
} }
if slices.Contains(state.MethodsAuthenticated, "publickey") { if slices.Contains(state.MethodsAuthenticated, "publickey") {
@ -294,23 +399,35 @@ func (a *Authorize) ManageStream(
ValidAfter: timestamppb.New(time.Now().Add(12 * time.Hour)), ValidAfter: timestamppb.New(time.Now().Add(12 * time.Hour)),
}, },
}) })
sessionRecordingExt, _ := anypb.New(&extensions_session_recording.UpstreamTargetExtensionConfig{
RecordingName: fmt.Sprintf("session-%s-at-%s-%d.cast", state.Username, state.Hostname, time.Now().UnixNano()),
Format: extensions_session_recording.Format_AsciicastFormat,
})
authResponse := extensions_ssh.ServerMessage{ authResponse := extensions_ssh.ServerMessage{
Message: &extensions_ssh.ServerMessage_AuthResponse{ Message: &extensions_ssh.ServerMessage_AuthResponse{
AuthResponse: &extensions_ssh.AuthenticationResponse{ AuthResponse: &extensions_ssh.AuthenticationResponse{
Response: &extensions_ssh.AuthenticationResponse_Allow{ Response: &extensions_ssh.AuthenticationResponse_Allow{
Allow: &extensions_ssh.AllowResponse{ Allow: &extensions_ssh.AllowResponse{
Username: state.Username, Username: state.Username,
Hostname: state.Hostname, Target: &extensions_ssh.AllowResponse_Upstream{
AllowedMethods: []*extensions_ssh.AllowedMethod{ Upstream: &extensions_ssh.UpstreamTarget{
{ Hostname: state.Hostname,
Method: "publickey", AllowedMethods: []*extensions_ssh.AllowedMethod{
MethodData: pkData, {
}, Method: "publickey",
{ MethodData: pkData,
Method: "keyboard-interactive", },
{
Method: "keyboard-interactive",
},
},
Extensions: []*corev3.TypedExtensionConfig{
{
TypedConfig: sessionRecordingExt,
},
},
}, },
}, },
Target: extensions_ssh.Target_Upstream,
}, },
}, },
}, },
@ -465,10 +582,17 @@ func (a *Authorize) ServeChannel(
switch msg.Request { switch msg.Request {
case "pty-req": case "pty-req":
opts := a.currentOptions.Load()
var routes []string
for r := range opts.GetAllPolicies() {
if strings.HasPrefix(r.From, "ssh://") {
routes = append(routes, fmt.Sprintf("ubuntu@%s", strings.TrimSuffix(strings.TrimPrefix(r.From, "ssh://"), "."+opts.SSHHostname)))
}
}
req := parsePtyReq(msg.RequestSpecificData) req := parsePtyReq(msg.RequestSpecificData)
items := []list.Item{ items := []list.Item{}
item("ubuntu@vm"), for _, route := range routes {
item("joe@local"), items = append(items, item(route))
} }
downstreamPtyInfo = &extensions_ssh.SSHDownstreamPTYInfo{ downstreamPtyInfo = &extensions_ssh.SSHDownstreamPTYInfo{
TermEnv: req.TermEnv, TermEnv: req.TermEnv,
@ -502,6 +626,10 @@ func (a *Authorize) ServeChannel(
return return
} }
username, hostname, _ := strings.Cut(answer.(model).choice, "@") username, hostname, _ := strings.Cut(answer.(model).choice, "@")
sessionRecordingExt, _ := anypb.New(&extensions_session_recording.UpstreamTargetExtensionConfig{
RecordingName: fmt.Sprintf("session-%s-at-%s-%d.cast", username, hostname, time.Now().UnixNano()),
Format: extensions_session_recording.Format_AsciicastFormat,
})
handOff, _ := anypb.New(&extensions_ssh.SSHChannelControlAction{ handOff, _ := anypb.New(&extensions_ssh.SSHChannelControlAction{
Action: &extensions_ssh.SSHChannelControlAction_HandOff{ Action: &extensions_ssh.SSHChannelControlAction_HandOff{
HandOff: &extensions_ssh.SSHChannelControlAction_HandOffUpstream{ HandOff: &extensions_ssh.SSHChannelControlAction_HandOffUpstream{
@ -509,7 +637,17 @@ func (a *Authorize) ServeChannel(
DownstreamPtyInfo: downstreamPtyInfo, DownstreamPtyInfo: downstreamPtyInfo,
UpstreamAuth: &extensions_ssh.AllowResponse{ UpstreamAuth: &extensions_ssh.AllowResponse{
Username: username, Username: username,
Hostname: hostname, Target: &extensions_ssh.AllowResponse_Upstream{
Upstream: &extensions_ssh.UpstreamTarget{
AllowMirrorConnections: true,
Hostname: hostname,
Extensions: []*corev3.TypedExtensionConfig{
{
TypedConfig: sessionRecordingExt,
},
},
},
},
}, },
}, },
}, },
@ -675,11 +813,5 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
func (m model) View() string { func (m model) View() string {
if m.choice != "" {
return quitTextStyle.Render(fmt.Sprintf("%s? Sounds good to me.", m.choice))
}
if m.quitting {
return quitTextStyle.Render("Not hungry? Thats cool.")
}
return "\n" + m.list.View() return "\n" + m.list.View()
} }

View file

@ -53,9 +53,9 @@ func main() {
} }
func run(ctx context.Context, configFile string) error { func run(ctx context.Context, configFile string) error {
ctx = log.WithContext(ctx, func(c zerolog.Context) zerolog.Context { // ctx = log.WithContext(ctx, func(c zerolog.Context) zerolog.Context {
return c.Str("config_file_source", configFile).Bool("bootstrap", true) // return c.Str("config_file_source", configFile).Bool("bootstrap", true)
}) // })
var src config.Source var src config.Source

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"net/url" "net/url"
"os"
"strings" "strings"
"time" "time"
@ -11,14 +12,17 @@ import (
xds_matcher_v3 "github.com/cncf/xds/go/xds/type/matcher/v3" xds_matcher_v3 "github.com/cncf/xds/go/xds/type/matcher/v3"
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_config_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" envoy_config_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
extensions_compressor_zstd_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/compression/zstd/compressor/v3"
envoy_generic_proxy_action_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/generic_proxy/action/v3" envoy_generic_proxy_action_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/generic_proxy/action/v3"
envoy_generic_proxy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/generic_proxy/matcher/v3" envoy_generic_proxy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/generic_proxy/matcher/v3"
envoy_generic_router_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/generic_proxy/router/v3" envoy_generic_router_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/generic_proxy/router/v3"
envoy_generic_proxy_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/generic_proxy/v3" envoy_generic_proxy_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/generic_proxy/v3"
matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
extensions_ssh "github.com/pomerium/envoy-custom/api/extensions/filters/network/ssh" extensions_ssh "github.com/pomerium/envoy-custom/api/extensions/filters/network/ssh"
extensions_ssh_session_recording "github.com/pomerium/envoy-custom/api/extensions/filters/network/ssh/filters/session_recording"
"github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/config"
"google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/wrapperspb"
) )
func (b *Builder) buildSSHListener(ctx context.Context, cfg *config.Config) (*envoy_config_listener_v3.Listener, error) { func (b *Builder) buildSSHListener(ctx context.Context, cfg *config.Config) (*envoy_config_listener_v3.Listener, error) {
@ -39,6 +43,15 @@ func (b *Builder) buildSSHListener(ctx context.Context, cfg *config.Config) (*en
} else { } else {
grpcClientTimeout = durationpb.New(30 * time.Second) grpcClientTimeout = durationpb.New(30 * time.Second)
} }
os.MkdirAll("/tmp/recordings", 0o755)
authorizeService := &envoy_config_core_v3.GrpcService{
Timeout: grpcClientTimeout,
TargetSpecifier: &envoy_config_core_v3.GrpcService_EnvoyGrpc_{
EnvoyGrpc: &envoy_config_core_v3.GrpcService_EnvoyGrpc{
ClusterName: "pomerium-authorize",
},
},
}
li := &envoy_config_listener_v3.Listener{ li := &envoy_config_listener_v3.Listener{
Name: "ssh", Name: "ssh",
Address: buildTCPAddress(cfg.Options.SSHAddr, 22), Address: buildTCPAddress(cfg.Options.SSHAddr, 22),
@ -58,17 +71,30 @@ func (b *Builder) buildSSHListener(ctx context.Context, cfg *config.Config) (*en
PublicKeyFile: cfg.Options.SSHUserCAKey.PublicKeyFile, PublicKeyFile: cfg.Options.SSHUserCAKey.PublicKeyFile,
PrivateKeyFile: cfg.Options.SSHUserCAKey.PrivateKeyFile, PrivateKeyFile: cfg.Options.SSHUserCAKey.PrivateKeyFile,
}, },
GrpcService: &envoy_config_core_v3.GrpcService{ GrpcService: authorizeService,
Timeout: grpcClientTimeout,
TargetSpecifier: &envoy_config_core_v3.GrpcService_EnvoyGrpc_{
EnvoyGrpc: &envoy_config_core_v3.GrpcService_EnvoyGrpc{
ClusterName: "pomerium-authorize",
},
},
},
}), }),
}, },
Filters: []*envoy_config_core_v3.TypedExtensionConfig{ Filters: []*envoy_config_core_v3.TypedExtensionConfig{
{
Name: "envoy.filters.generic.ssh.session_recording",
TypedConfig: marshalAny(&extensions_ssh_session_recording.Config{
StorageDir: "/tmp/recordings",
GrpcService: authorizeService,
CompressorLibrary: &envoy_config_core_v3.TypedExtensionConfig{
Name: "envoy.compression.zstd.compressor",
TypedConfig: marshalAny(&extensions_compressor_zstd_v3.Zstd{
CompressionLevel: wrapperspb.UInt32(19),
EnableChecksum: false,
Strategy: extensions_compressor_zstd_v3.Zstd_BTULTRA2,
ChunkSize: wrapperspb.UInt32(8192),
}),
},
}),
},
// {
// Name: "envoy.filters.generic.ssh.session_multiplexing",
// TypedConfig: marshalAny(&extensions_ssh_session_multiplexing.Config{}),
// },
{ {
Name: "envoy.filters.generic.router", Name: "envoy.filters.generic.router",
TypedConfig: marshalAny(&envoy_generic_router_v3.Router{ TypedConfig: marshalAny(&envoy_generic_router_v3.Router{

View file

@ -10,6 +10,7 @@ import (
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
extensions_ssh "github.com/pomerium/envoy-custom/api/extensions/filters/network/ssh" extensions_ssh "github.com/pomerium/envoy-custom/api/extensions/filters/network/ssh"
extensions_session_recording "github.com/pomerium/envoy-custom/api/extensions/filters/network/ssh/filters/session_recording"
"go.uber.org/automaxprocs/maxprocs" "go.uber.org/automaxprocs/maxprocs"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@ -270,6 +271,7 @@ func setupAuthorize(ctx context.Context, src config.Source, controlPlane *contro
} }
envoy_service_auth_v3.RegisterAuthorizationServer(controlPlane.GRPCServer, svc) envoy_service_auth_v3.RegisterAuthorizationServer(controlPlane.GRPCServer, svc)
extensions_ssh.RegisterStreamManagementServer(controlPlane.GRPCServer, svc) extensions_ssh.RegisterStreamManagementServer(controlPlane.GRPCServer, svc)
extensions_session_recording.RegisterRecordingServiceServer(controlPlane.GRPCServer, svc)
log.Ctx(ctx).Info().Msg("enabled authorize service") log.Ctx(ctx).Info().Msg("enabled authorize service")
src.OnConfigChange(ctx, svc.OnConfigChange) src.OnConfigChange(ctx, svc.OnConfigChange)