Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
51cb57a
protobuf update
GavinFrazar Oct 17, 2022
e48b2d3
Update proto to use dynamodb request event specific to app-access
GavinFrazar Oct 17, 2022
0d4572d
Update protos
GavinFrazar Oct 18, 2022
f5e4071
Update oneof
GavinFrazar Oct 18, 2022
0d054f9
Move AppMetaData up with the other metadata and add a 'target' field
GavinFrazar Oct 19, 2022
7e770c3
Remove operation plane
GavinFrazar Oct 19, 2022
fd9e2a5
Merge branch 'master' into gavinfrazar/improve_dynamodb_audit_events_…
GavinFrazar Oct 26, 2022
5bee7e8
Merge branch 'master' into gavinfrazar/improve_dynamodb_audit_events_…
GavinFrazar Oct 28, 2022
4ac6c44
Merge branch 'master' into gavinfrazar/improve_dynamodb_audit_events_…
GavinFrazar Oct 31, 2022
a521794
Merge branch 'master' into gavinfrazar/improve_dynamodb_audit_events_…
GavinFrazar Nov 2, 2022
4dc3ed2
Fix typo
GavinFrazar Oct 14, 2022
8624a7f
Configure signing service with transport instead of http client
GavinFrazar Oct 14, 2022
df9ef4d
Protect from resource exhaustion attacks
GavinFrazar Oct 15, 2022
54ccadf
Add IsDynamoDB to types.Application
GavinFrazar Oct 19, 2022
38509c8
Add new event and code for dynamodb requests
GavinFrazar Oct 19, 2022
8540321
Add async emitter to app access
GavinFrazar Oct 19, 2022
3aaa0e0
Add audit.go to unify app access auditing
GavinFrazar Oct 19, 2022
e298fcc
Refactor auditing in app access
GavinFrazar Oct 19, 2022
90b9bfc
Update handler test to test dynamodb events
GavinFrazar Oct 19, 2022
f90ce88
Update test to use streamCloser
GavinFrazar Oct 19, 2022
251b256
Update sever test
GavinFrazar Oct 19, 2022
4dfcc63
Add doc strings
GavinFrazar Oct 19, 2022
b6d8b80
Return error from audit interface methods so callers can choose what …
GavinFrazar Oct 19, 2022
9d8dc39
Move app session start/end into audit interface
GavinFrazar Oct 19, 2022
660198a
Remove unneeded check type
GavinFrazar Oct 19, 2022
e9d3d4d
Rename Transport -> RoundTripper
GavinFrazar Oct 19, 2022
f263436
Fix test after renaming field
GavinFrazar Oct 19, 2022
b18f379
Rename drainBody and defer body closing
GavinFrazar Oct 19, 2022
ae748e2
Fix subtle named return mistake
GavinFrazar Oct 19, 2022
5c92167
Update lib/service/service.go
GavinFrazar Oct 21, 2022
a3e9d07
Update lib/service/service.go
GavinFrazar Oct 21, 2022
1ef86b7
Rename ok->shouldSkipCleanup to make the purpose of it more clear
GavinFrazar Oct 21, 2022
e1487e2
Refactor request body decoding into aws utils
GavinFrazar Oct 21, 2022
70cf583
Use request instead of signed request for audit event
GavinFrazar Oct 26, 2022
cb93e23
Determine if req is for a dynamo endpoint instead of checking app uri
GavinFrazar Oct 26, 2022
d146200
Remove obsolete app func IsDynamoDB
GavinFrazar Oct 26, 2022
165c294
Update handler test
GavinFrazar Oct 26, 2022
c7a8c65
fix lint
GavinFrazar Nov 2, 2022
6da639b
Merge branch 'master' into gavinfrazar/improve_dynamodb_audit_events
GavinFrazar Nov 2, 2022
63e97db
Merge branch 'master' into gavinfrazar/improve_dynamodb_audit_events
GavinFrazar Nov 14, 2022
48d0301
Fixup merge
GavinFrazar Nov 15, 2022
b7a5dbf
Merge branch 'master' into gavinfrazar/improve_dynamodb_audit_events
GavinFrazar Nov 15, 2022
0649531
Merge branch 'master' into gavinfrazar/improve_dynamodb_audit_events
GavinFrazar Nov 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/events/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,10 @@ const (
// AppSessionRequestEvent is an HTTP request and response.
AppSessionRequestEvent = "app.session.request"

// AppSessionDynamoDBRequestEvent is emitted when DynamoDB client sends
// a request via app access session.
AppSessionDynamoDBRequestEvent = "app.session.dynamodb.request"

// DatabaseCreateEvent is emitted when a database resource is created.
DatabaseCreateEvent = "db.create"
// DatabaseUpdateEvent is emitted when a database resource is updated.
Expand Down
2 changes: 2 additions & 0 deletions lib/events/codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ const (
AppSessionEndCode = "T2011I"
// SessionRecordingAccessCode is the session recording view data event code.
SessionRecordingAccessCode = "T2012I"
// AppSessionDynamoDBRequestCode is the application request/response code.
AppSessionDynamoDBRequestCode = "T2013I"

// AppCreateCode is the app.create event code.
AppCreateCode = "TAP03I"
Expand Down
2 changes: 2 additions & 0 deletions lib/events/dynamic.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ func FromEventFields(fields EventFields) (events.AuditEvent, error) {
e = &events.AppSessionChunk{}
case AppSessionRequestEvent:
e = &events.AppSessionRequest{}
case AppSessionDynamoDBRequestEvent:
e = &events.AppSessionDynamoDBRequest{}
case AppCreateEvent:
e = &events.AppCreate{}
case AppUpdateEvent:
Expand Down
19 changes: 14 additions & 5 deletions lib/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4295,9 +4295,9 @@ func (process *TeleportProcess) initApps() {
return trace.Wrap(err)
}

ok := false
shouldSkipCleanup := false
defer func() {
if !ok {
if !shouldSkipCleanup {
warnOnErr(conn.Close(), log)
}
}()
Expand Down Expand Up @@ -4431,6 +4431,12 @@ func (process *TeleportProcess) initApps() {

proxyGetter := reversetunnel.NewConnectedProxyGetter()

defer func() {
if !shouldSkipCleanup {
warnOnErr(asyncEmitter.Close(), log)
}
}()

appServer, err := app.New(process.ExitContext(), &app.Config{
Clock: process.Config.Clock,
DataDir: process.Config.DataDir,
Expand All @@ -4456,7 +4462,7 @@ func (process *TeleportProcess) initApps() {
}

defer func() {
if !ok {
if !shouldSkipCleanup {
warnOnErr(appServer.Close(), log)
}
}()
Expand Down Expand Up @@ -4494,13 +4500,16 @@ func (process *TeleportProcess) initApps() {
log.Infof("All applications successfully started.")

// Cancel deferred cleanup actions, because we're going
// to regsiter an OnExit handler to take care of it
ok = true
// to register an OnExit handler to take care of it
shouldSkipCleanup = true

// Execute this when process is asked to exit.
process.OnExit("apps.stop", func(payload interface{}) {
log.Infof("Shutting down.")
warnOnErr(appServer.Close(), log)
if asyncEmitter != nil {
warnOnErr(asyncEmitter.Close(), log)
}
agentPool.Stop()
warnOnErr(asyncEmitter.Close(), log)
warnOnErr(conn.Close(), log)
Expand Down
16 changes: 16 additions & 0 deletions lib/srv/app/aws/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,22 @@ func endpointsIDFromSigningName(signingName string) string {
return signingName
}

func isDynamoDBEndpoint(re *endpoints.ResolvedEndpoint) bool {
// Some clients may sign some services with upper case letters. We use all
// lower cases in our mapping.
signingName := strings.ToLower(re.SigningName)
_, ok := dynamoDBSigningNames[signingName]
return ok
}

// dynamoDBSigningNames is a set of signing names used for DynamoDB APIs.
var dynamoDBSigningNames = map[string]struct{}{
// signing name for dynamodb and dynamodbstreams API.
"dynamodb": {},
// signing name for dynamodb accelerator API.
"dax": {},
}

// signingNameToEndpointsID is a map of AWS services' signing names to their
// endpoints IDs.
//
Expand Down
68 changes: 23 additions & 45 deletions lib/srv/app/aws/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package aws

import (
"bytes"
"context"
"io"
"net/http"
"net/url"

Expand All @@ -29,14 +29,12 @@ import (
"github.com/aws/aws-sdk-go/aws/endpoints"
awssession "github.com/aws/aws-sdk-go/aws/session"
"github.com/gravitational/oxy/forward"
"github.com/gravitational/oxy/utils"
oxyutils "github.com/gravitational/oxy/utils"
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
"github.com/sirupsen/logrus"

apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/srv/app/common"
awsutils "github.com/gravitational/teleport/lib/utils/aws"
)
Expand All @@ -52,7 +50,7 @@ func NewSigningService(config SigningServiceConfig) (*SigningService, error) {

fwd, err := forward.New(
forward.RoundTripper(svc),
forward.ErrorHandler(utils.ErrorHandlerFunc(svc.formatForwardResponseError)),
forward.ErrorHandler(oxyutils.ErrorHandlerFunc(svc.formatForwardResponseError)),
forward.PassHostHeader(true),
)
if err != nil {
Expand All @@ -74,8 +72,8 @@ type SigningService struct {

// SigningServiceConfig is the SigningService configuration.
type SigningServiceConfig struct {
// Client is an HTTP client instance used for HTTP calls.
Client *http.Client
// RoundTripper is an http.RoundTripper instance used for requests.
RoundTripper http.RoundTripper
// Log is the Logger.
Log logrus.FieldLogger
// Session is AWS session.
Expand All @@ -90,14 +88,12 @@ type SigningServiceConfig struct {

// CheckAndSetDefaults validates the SigningServiceConfig config.
func (s *SigningServiceConfig) CheckAndSetDefaults() error {
if s.Client == nil {
if s.RoundTripper == nil {
tr, err := defaults.Transport()
if err != nil {
return trace.Wrap(err)
}
s.Client = &http.Client{
Transport: tr,
}
s.RoundTripper = tr
}
if s.Clock == nil {
s.Clock = clockwork.NewRealClock()
Expand Down Expand Up @@ -137,6 +133,7 @@ func (s *SigningServiceConfig) CheckAndSetDefaults() error {
// 5. Sign HTTP request.
// 6. Forward the signed HTTP request to the AWS API.
func (s *SigningService) RoundTrip(req *http.Request) (*http.Response, error) {
defer req.Body.Close()
Comment thread
zmb3 marked this conversation as resolved.
sessionCtx, err := common.GetSessionContext(req)
if err != nil {
return nil, trace.Wrap(err)
Expand All @@ -145,46 +142,31 @@ func (s *SigningService) RoundTrip(req *http.Request) (*http.Response, error) {
if err != nil {
return nil, trace.Wrap(err)
}
signedReq, err := s.prepareSignedRequest(req, resolvedEndpoint, sessionCtx)
payload, err := awsutils.GetAndReplaceReqBody(req)
if err != nil {
return nil, trace.Wrap(err)
}
resp, err := s.Client.Do(signedReq)
signedReq, err := s.prepareSignedRequest(req, payload, resolvedEndpoint, sessionCtx)
if err != nil {
return nil, trace.Wrap(err)
}

if err := s.emitAuditEvent(req.Context(), signedReq, resp, sessionCtx, resolvedEndpoint); err != nil {
resp, err := s.RoundTripper.RoundTrip(signedReq)
if err != nil {
return nil, trace.Wrap(err)
}
// emit audit event with original request, but change the URL since we resolved and rewrote it.
signedReq.Body = io.NopCloser(bytes.NewReader(payload))
if isDynamoDBEndpoint(resolvedEndpoint) {
err = sessionCtx.Audit.OnDynamoDBRequest(req.Context(), sessionCtx, signedReq, resp, resolvedEndpoint)
} else {
err = sessionCtx.Audit.OnRequest(req.Context(), sessionCtx, signedReq, resp, resolvedEndpoint)
}
if err != nil {
s.Log.WithError(err).Warn("Failed to emit audit event.")
}
return resp, nil
}

// emitAuditEvent writes details of the AWS request to audit stream.
func (s *SigningService) emitAuditEvent(ctx context.Context, req *http.Request, resp *http.Response, sessionCtx *common.SessionContext, endpoint *endpoints.ResolvedEndpoint) error {
event := &apievents.AppSessionRequest{
Metadata: apievents.Metadata{
Type: events.AppSessionRequestEvent,
Code: events.AppSessionRequestCode,
},
Method: req.Method,
Path: req.URL.Path,
RawQuery: req.URL.RawQuery,
StatusCode: uint32(resp.StatusCode),
AppMetadata: apievents.AppMetadata{
AppURI: sessionCtx.App.GetURI(),
AppPublicAddr: sessionCtx.App.GetPublicAddr(),
AppName: sessionCtx.App.GetName(),
},
AWSRequestMetadata: apievents.AWSRequestMetadata{
AWSRegion: endpoint.SigningRegion,
AWSService: endpoint.SigningName,
AWSHost: req.Host,
},
}
return trace.Wrap(sessionCtx.Emitter.EmitAuditEvent(ctx, event))
}

func (s *SigningService) formatForwardResponseError(rw http.ResponseWriter, r *http.Request, err error) {
switch trace.Unwrap(err).(type) {
case *trace.BadParameterError:
Expand All @@ -201,15 +183,11 @@ func (s *SigningService) formatForwardResponseError(rw http.ResponseWriter, r *h

// prepareSignedRequest creates a new HTTP request and rewrites the header from the original request and returns a new
// HTTP request signed by STS AWS API.
func (s *SigningService) prepareSignedRequest(r *http.Request, re *endpoints.ResolvedEndpoint, sessionCtx *common.SessionContext) (*http.Request, error) {
func (s *SigningService) prepareSignedRequest(r *http.Request, payload []byte, re *endpoints.ResolvedEndpoint, sessionCtx *common.SessionContext) (*http.Request, error) {
url, err := urlForResolvedEndpoint(r, re)
if err != nil {
return nil, trace.Wrap(err)
}
payload, err := awsutils.GetAndReplaceReqBody(r)
if err != nil {
return nil, trace.Wrap(err)
}
reqCopy, err := http.NewRequest(r.Method, url, bytes.NewReader(payload))
if err != nil {
return nil, trace.Wrap(err)
Expand Down
Loading