Skip to content

Commit

Permalink
[Forward-port] Update audit events with additional fields. (#2655)
Browse files Browse the repository at this point in the history
  • Loading branch information
r0mant authored Apr 17, 2019
1 parent 9d9daa7 commit 1828e21
Show file tree
Hide file tree
Showing 36 changed files with 556 additions and 105 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ endif

.PHONY: test-package
test-package: remove-temp-files
go test -v -test.parallel=0 ./$(p)
go test -v ./$(p)

.PHONY: test-grep-package
test-grep-package: remove-temp-files
Expand Down
2 changes: 1 addition & 1 deletion e
Submodule e updated from 91be21 to dfb02d
20 changes: 17 additions & 3 deletions lib/auth/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1865,17 +1865,31 @@ func (s *APIServer) searchSessionEvents(auth ClientI, w http.ResponseWriter, r *
}

type auditEventReq struct {
Type string `json:"type"`
// Event is the event that's being emitted.
Event events.Event `json:"event"`
// Fields is the additional event fields.
Fields events.EventFields `json:"fields"`
// Type is the event type.
//
// This field is obsolete and kept for backwards compatibility.
Type string `json:"type"`
}

// HTTP POST /:version/events
func (s *APIServer) emitAuditEvent(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req auditEventReq
if err := httplib.ReadJSON(r, &req); err != nil {
err := httplib.ReadJSON(r, &req)
if err != nil {
return nil, trace.Wrap(err)
}
if err := auth.EmitAuditEvent(req.Type, req.Fields); err != nil {
// For backwards compatibility, check if the full event struct has
// been sent in the request or just the event type.
if req.Event.Name != "" {
err = auth.EmitAuditEvent(req.Event, req.Fields)
} else {
err = auth.EmitAuditEvent(events.Event{Name: req.Type}, req.Fields)
}
if err != nil {
return nil, trace.Wrap(err)
}
return message("ok"), nil
Expand Down
4 changes: 2 additions & 2 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -1044,14 +1044,14 @@ func (a *AuthWithRoles) ValidateGithubAuthCallback(q url.Values) (*GithubAuthRes
return a.authServer.ValidateGithubAuthCallback(q)
}

func (a *AuthWithRoles) EmitAuditEvent(eventType string, fields events.EventFields) error {
func (a *AuthWithRoles) EmitAuditEvent(event events.Event, fields events.EventFields) error {
if err := a.action(defaults.Namespace, services.KindEvent, services.VerbCreate); err != nil {
return trace.Wrap(err)
}
if err := a.action(defaults.Namespace, services.KindEvent, services.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.alog.EmitAuditEvent(eventType, fields)
return a.alog.EmitAuditEvent(event, fields)
}

func (a *AuthWithRoles) PostSessionSlice(slice events.SessionSlice) error {
Expand Down
8 changes: 5 additions & 3 deletions lib/auth/clt.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ func NewClient(addr string, dialer Dialer, params ...roundtrip.ClientParam) (*Cl
dialer = net.Dial
}
transport := &http.Transport{
Dial: dialer,
Dial: dialer,
ResponseHeaderTimeout: defaults.DefaultDialTimeout,
}
params = append(params,
Expand Down Expand Up @@ -1945,10 +1945,12 @@ func (c *Client) ValidateGithubAuthCallback(q url.Values) (*GithubAuthResponse,
}

// EmitAuditEvent sends an auditable event to the auth server (part of evets.IAuditLog interface)
func (c *Client) EmitAuditEvent(eventType string, fields events.EventFields) error {
func (c *Client) EmitAuditEvent(event events.Event, fields events.EventFields) error {
_, err := c.PostJSON(c.Endpoint("events"), &auditEventReq{
Type: eventType,
Event: event,
Fields: fields,
// Send "type" as well for backwards compatibility.
Type: event.Name,
})
if err != nil {
return trace.Wrap(err)
Expand Down
6 changes: 3 additions & 3 deletions lib/auth/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ type GithubAuthResponse struct {
func (a *AuthServer) ValidateGithubAuthCallback(q url.Values) (*GithubAuthResponse, error) {
re, err := a.validateGithubAuthCallback(q)
if err != nil {
a.EmitAuditEvent(events.UserLoginEvent, events.EventFields{
a.EmitAuditEvent(events.UserSSOLoginFailure, events.EventFields{
events.LoginMethod: events.LoginMethodGithub,
events.AuthAttemptSuccess: false,
events.AuthAttemptErr: err.Error(),
})
} else {
a.EmitAuditEvent(events.UserLoginEvent, events.EventFields{
a.EmitAuditEvent(events.UserSSOLogin, events.EventFields{
events.EventUser: re.Username,
events.AuthAttemptSuccess: true,
events.LoginMethod: events.LoginMethodGithub,
Expand Down Expand Up @@ -507,7 +507,7 @@ func (c *githubAPIClient) getTeams() ([]teamResponse, error) {

// Print warning to Teleport logs as well as the Audit Log.
log.Warnf(warningMessage)
c.authServer.EmitAuditEvent(events.UserLoginEvent, events.EventFields{
c.authServer.EmitAuditEvent(events.UserSSOLoginFailure, events.EventFields{
events.LoginMethod: events.LoginMethodGithub,
events.AuthAttemptMessage: warningMessage,
})
Expand Down
4 changes: 2 additions & 2 deletions lib/auth/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ type SessionCreds struct {
func (s *AuthServer) AuthenticateUser(req AuthenticateUserRequest) error {
err := s.authenticateUser(req)
if err != nil {
s.EmitAuditEvent(events.UserLoginEvent, events.EventFields{
s.EmitAuditEvent(events.UserLocalLoginFailure, events.EventFields{
events.EventUser: req.Username,
events.LoginMethod: events.LoginMethodLocal,
events.AuthAttemptSuccess: false,
events.AuthAttemptErr: err.Error(),
})
} else {
s.EmitAuditEvent(events.UserLoginEvent, events.EventFields{
s.EmitAuditEvent(events.UserLocalLogin, events.EventFields{
events.EventUser: req.Username,
events.LoginMethod: events.LoginMethodLocal,
events.AuthAttemptSuccess: true,
Expand Down
4 changes: 2 additions & 2 deletions lib/auth/new_web_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ func (a *AuthServer) UpsertUser(user services.User) error {
} else {
connectorName = user.GetCreatedBy().Connector.ID
}
a.EmitAuditEvent(events.UserUpdatedEvent, events.EventFields{
a.EmitAuditEvent(events.UserUpdate, events.EventFields{
events.EventUser: user.GetName(),
events.UserExpires: user.Expiry(),
events.UserRoles: user.GetRoles(),
Expand Down Expand Up @@ -422,7 +422,7 @@ func (a *AuthServer) DeleteUser(user string) error {
}

// If the user was successfully deleted, emit an event.
a.EmitAuditEvent(events.UserDeleteEvent, events.EventFields{
a.EmitAuditEvent(events.UserDelete, events.EventFields{
events.EventUser: user,
})

Expand Down
6 changes: 3 additions & 3 deletions lib/auth/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,14 @@ func (s *AuthServer) CreateOIDCAuthRequest(req services.OIDCAuthRequest) (*servi
func (a *AuthServer) ValidateOIDCAuthCallback(q url.Values) (*OIDCAuthResponse, error) {
re, err := a.validateOIDCAuthCallback(q)
if err != nil {
a.EmitAuditEvent(events.UserLoginEvent, events.EventFields{
a.EmitAuditEvent(events.UserSSOLoginFailure, events.EventFields{
events.LoginMethod: events.LoginMethodOIDC,
events.AuthAttemptSuccess: false,
// log the original internal error in audit log
events.AuthAttemptErr: trace.Unwrap(err).Error(),
})
} else {
a.EmitAuditEvent(events.UserLoginEvent, events.EventFields{
a.EmitAuditEvent(events.UserSSOLogin, events.EventFields{
events.EventUser: re.Username,
events.AuthAttemptSuccess: true,
events.LoginMethod: events.LoginMethodOIDC,
Expand Down Expand Up @@ -554,7 +554,7 @@ collect:

// Print warning to Teleport logs as well as the Audit Log.
log.Warnf(warningMessage)
g.auditLog.EmitAuditEvent(events.UserLoginEvent, events.EventFields{
g.auditLog.EmitAuditEvent(events.UserSSOLoginFailure, events.EventFields{
events.LoginMethod: events.LoginMethodOIDC,
events.AuthAttemptMessage: warningMessage,
})
Expand Down
4 changes: 2 additions & 2 deletions lib/auth/saml.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,13 @@ type SAMLAuthResponse struct {
func (a *AuthServer) ValidateSAMLResponse(samlResponse string) (*SAMLAuthResponse, error) {
re, err := a.validateSAMLResponse(samlResponse)
if err != nil {
a.EmitAuditEvent(events.UserLoginEvent, events.EventFields{
a.EmitAuditEvent(events.UserSSOLoginFailure, events.EventFields{
events.LoginMethod: events.LoginMethodSAML,
events.AuthAttemptSuccess: false,
events.AuthAttemptErr: err.Error(),
})
} else {
a.EmitAuditEvent(events.UserLoginEvent, events.EventFields{
a.EmitAuditEvent(events.UserSSOLogin, events.EventFields{
events.EventUser: re.Username,
events.AuthAttemptSuccess: true,
events.LoginMethod: events.LoginMethodSAML,
Expand Down
42 changes: 35 additions & 7 deletions lib/events/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ const (
EventType = "event"
// EventID is a unique event identifier
EventID = "uid"
// EventCode is a code that uniquely identifies a particular event type
EventCode = "code"
// EventSeverity contains event severity (info, warning, error)
EventSeverity = "severity"
// EventMessage contains human-friendly event message
EventMessage = "message"
// EventTime is event time
EventTime = "time"
// EventLogin is OS login
Expand All @@ -49,6 +55,13 @@ const (
// EventCursor is an event ID (used as cursor value for enumeration, not stored)
EventCursor = "id"

// SeverityInfo represents severity for informational events.
SeverityInfo = "info"
// SeverityWarning represents severity for events that need attention.
SeverityWarning = "warning"
// SeverityError represents severity for events caused by an error.
SeverityError = "error"

// EventIndex is an event index as received from the logging server
EventIndex = "ei"

Expand Down Expand Up @@ -157,12 +170,12 @@ const (
AuthAttemptMessage = "message"

// SCPEvent means data transfer that occurred on the server
SCPEvent = "scp"
SCPPath = "path"
SCPLengh = "len"
SCPAction = "action"
SCPUpload = "upload"
SCPDownload = "download"
SCPEvent = "scp"
SCPPath = "path"
SCPLengh = "len"
SCPAction = "action"
SCPActionUpload = "upload"
SCPActionDownload = "download"

// ResizeEvent means that some user resized PTY on the client
ResizeEvent = "resize"
Expand Down Expand Up @@ -196,7 +209,7 @@ type IAuditLog interface {
io.Closer

// EmitAuditEvent emits audit event
EmitAuditEvent(eventType string, fields EventFields) error
EmitAuditEvent(Event, EventFields) error

// DELETE IN: 2.7.0
// This method is no longer necessary as nodes and proxies >= 2.7.0
Expand Down Expand Up @@ -264,6 +277,21 @@ func (f EventFields) GetID() string {
return f.GetString(EventID)
}

// GetCode returns the event code
func (f EventFields) GetCode() string {
return f.GetString(EventCode)
}

// GetTimestamp returns the event timestamp (when it was emitted)
func (f EventFields) GetTimestamp() time.Time {
return f.GetTime(EventTime)
}

// GetMessage returns the event user message
func (f EventFields) GetMessage() string {
return f.GetString(EventMessage)
}

// GetString returns a string representation of a logged field
func (f EventFields) GetString(key string) string {
val, found := f[key]
Expand Down
43 changes: 37 additions & 6 deletions lib/events/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,20 @@ limitations under the License.

package events

import "gopkg.in/check.v1"
import "time"
import (
"time"

type AuditApiTestSuite struct {
}
"github.com/gravitational/teleport/lib/fixtures"
"github.com/gravitational/teleport/lib/utils"
"github.com/jonboulle/clockwork"
"gopkg.in/check.v1"
)

type AuditAPITestSuite struct{}

var _ = check.Suite(&AuditApiTestSuite{})
var _ = check.Suite(&AuditAPITestSuite{})

func (a *AuditApiTestSuite) TestFields(c *check.C) {
func (a *AuditAPITestSuite) TestFields(c *check.C) {
now := time.Now().Round(time.Minute)

f := EventFields{
Expand All @@ -48,3 +53,29 @@ func (a *AuditApiTestSuite) TestFields(c *check.C) {
t := f.GetTime("time")
c.Assert(t, check.Equals, now)
}

func (a *AuditAPITestSuite) TestUpdateFields(c *check.C) {
event := Event{
Name: "test.event",
Code: "TEST0001I",
Severity: SeverityInfo,
Message: "User {{.user}} logged in via {{.method}}",
}
fields := EventFields{
EventUser: "[email protected]",
LoginMethod: LoginMethodOIDC,
}
c.Assert(UpdateEventFields(event, fields, clockwork.NewFakeClock(), utils.NewFakeUID()), check.IsNil)

// Check the fields have been updated appropriately.
c.Assert(fields, check.DeepEquals, EventFields{
EventType: event.Name,
EventID: fixtures.UUID,
EventCode: event.Code,
EventTime: time.Date(1984, time.April, 4, 0, 0, 0, 0, time.UTC),
EventSeverity: SeverityInfo,
EventUser: "[email protected]",
EventMessage: "User [email protected] logged in via oidc",
LoginMethod: LoginMethodOIDC,
})
}
10 changes: 5 additions & 5 deletions lib/events/auditlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ func (l *AuditLog) UploadSessionRecording(r SessionRecording) error {
return trace.Wrap(err)
}
l.WithFields(log.Fields{"duration": time.Now().Sub(start), "session-id": r.SessionID}).Debugf("Session upload completed.")
return l.EmitAuditEvent(SessionUploadEvent, EventFields{
return l.EmitAuditEvent(SessionUpload, EventFields{
SessionEventID: string(r.SessionID),
URL: url,
EventIndex: math.MaxInt32,
Expand Down Expand Up @@ -382,7 +382,7 @@ func (l *AuditLog) processSlice(sl SessionLogger, slice *SessionSlice) error {
if err != nil {
return trace.Wrap(err)
}
if err := l.EmitAuditEvent(chunk.EventType, fields); err != nil {
if err := l.EmitAuditEvent(Event{Name: chunk.EventType}, fields); err != nil {
return trace.Wrap(err)
}
}
Expand Down Expand Up @@ -876,10 +876,10 @@ func (l *AuditLog) fetchSessionEvents(fileName string, afterN int) ([]EventField

// EmitAuditEvent adds a new event to the log. If emitting fails, a Prometheus
// counter is incremented.
func (l *AuditLog) EmitAuditEvent(eventType string, fields EventFields) error {
func (l *AuditLog) EmitAuditEvent(event Event, fields EventFields) error {
// If an external logger has been set, use it as the emitter, otherwise
// fallback to the local disk based emitter.
var emitAuditEvent func(eventType string, fields EventFields) error
var emitAuditEvent func(event Event, fields EventFields) error
if l.ExternalLog != nil {
emitAuditEvent = l.ExternalLog.EmitAuditEvent
} else {
Expand All @@ -888,7 +888,7 @@ func (l *AuditLog) EmitAuditEvent(eventType string, fields EventFields) error {

// Emit the event. If it fails for any reason a Prometheus counter is
// incremented.
err := emitAuditEvent(eventType, fields)
err := emitAuditEvent(event, fields)
if err != nil {
auditFailedEmit.Inc()
return trace.Wrap(err)
Expand Down
4 changes: 2 additions & 2 deletions lib/events/auditlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ func (a *AuditTestSuite) TestBasicLogging(c *check.C) {
alog.Clock = clockwork.NewFakeClockAt(now)

// emit regular event:
err = alog.EmitAuditEvent("user.joined", EventFields{"apples?": "yes"})
err = alog.EmitAuditEvent(Event{Name: "user.joined"}, EventFields{"apples?": "yes"})
c.Assert(err, check.IsNil)
logfile := alog.localLog.file.Name()
c.Assert(alog.Close(), check.IsNil)
Expand Down Expand Up @@ -348,7 +348,7 @@ func (a *AuditTestSuite) TestLogRotation(c *check.C) {
clock.Advance(duration)

// emit regular event:
err = alog.EmitAuditEvent("user.joined", EventFields{"apples?": "yes"})
err = alog.EmitAuditEvent(Event{Name: "user.joined"}, EventFields{"apples?": "yes"})
c.Assert(err, check.IsNil)
logfile := alog.localLog.file.Name()

Expand Down
Loading

0 comments on commit 1828e21

Please sign in to comment.