-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Auditd integration #14948
Auditd integration #14948
Conversation
lib/auditd/auditd_linux.go
Outdated
// if value is not set, logind PAM module will set it to the correct value | ||
// after fork. 4294967295 is -1 converted to uint32. | ||
return loginuid != 4294967295 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice getSelfLoginUID()
returns an int64
but it's compared with the max value of a uint32
, not sure of both of those are intentional
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. All uint32
values can be represented as int64,
and I didn't want any value to be incorrectly "rounded" when reading it from /proc/self/loginuid
as strconv.ParseInt
returns int64
.
lib/service/service.go
Outdated
@@ -2206,6 +2207,11 @@ func (process *TeleportProcess) initSSH() error { | |||
return trace.Wrap(err) | |||
} | |||
|
|||
if auditd.IsLoginUIDSet() { | |||
log.Warnf("login UID is set, but it shouldn't. Incorrect login UID breaks session ID when using auditd. " + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be an error? I guess that wouldn't be backward compatible. Will things break if this occurs, or the feature will be disabled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be an error?
Questionable. One field in the event log will be "incorrect". Each SSH session has a unique session ID but only when the login UID is not set. That makes it easy to trace it. If the login UID is set in the main process, then all sessions share the same session ID, so it's hard to distinguish which event belongs to each session. You can still try to filter by PID or username and at least have partial data.
We cannot also fully disable the feature here. When PAM is enabled, some events are generated outside of Teleport, so disabling anything on our side would make it even more confusing, in my opinion. Then auditd would receive "incorrect" events from PAM and none from Teleport.
The good news is that most people should run Teleport from systemd. Then login UID is correct, and the problem doesn't exist. For those few cases when you run Teleport from bash (probably the most common scenario) as a root we will print that warning.
lib/srv/reexec.go
Outdated
return errorWriter, teleport.RemoteCommandFailure, trace.Errorf("failed to login user start: %v", err) | ||
} | ||
|
||
defer func(err *error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need to get so tricky with passing the address of err
as an argument, the deferred function can just capture err
since it is a named return value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, fixed.
return status, nil | ||
} | ||
|
||
// SendMsg sends a message. Client will create a new connection if not connected already. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As Nic mentioned, it currently looks like SendMsg
and SendEvent
are always creating a new client rather than using the cached client.
I might be missing something, but I'd recommend changing the API to function like this:
client, err := auditd.NewClient()
if err != nil {
return trace.Wrap(err)
}
defer client.Close()
if err := client.SendEvent(event); err != nil {
return trace.Wrap(err)
}
Then in auditd.go
you can just introduce a mock client so that creating the client and sending events both produce noops.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about it, and the initial implementation looked like that. My main issue with this approach is that it is a bit verbose compared to only one function call. Also, you need to remember to close the connection at the end. That's why I decided to wrap everything in a function.
There are, of course, a few more things to consider if we use only SendEvent()
:
- If we create a new connection for each message, we probably create more connections when needed. This is not exactly true. In my implementation, I found only one place where we could re-use the connection. I don't think that is a big issue then.
- Why then my client internally holds a mutex if the connection is not being used from more than one goroutine? Mainly for correctness. the
auditd
package exposes the client, so if anyone decides to use it in the future, it may create a race condition without knowing it.
I think there are three possible actions here:
- Remove the internal mutex to be explicit about the fact that it is not really needed. Make
NewClient()
constructor private, so the only API in that package is theSendEvent()
function. That would make the API more explicit. - Leave it as it is. Currently, the
auditd
package exposes two APIs. Higher leverSendEvent()
function that allows you to send one message andNewClient()
that can create a new client that is thread-safe. - I could use the auditd client in that one place where the connection can be reused to reduce the overhead where possible and make use of the
NewClient()
API.
WDYGT?
@Joerger @nklaassen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the connection is being reused properly now, so looks good to me as is.
I could use the auditd client in that one place where the connection can be reused to reduce the overhead where possible and make use of the NewClient() API.
I think it would make sense to use NewClient
in that one place, otherwise we might as well remove the NewClient
API and simplify the connection logic for a single use connection.
return status, nil | ||
} | ||
|
||
// SendMsg sends a message. Client will create a new connection if not connected already. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the connection is being reused properly now, so looks good to me as is.
I could use the auditd client in that one place where the connection can be reused to reduce the overhead where possible and make use of the NewClient() API.
I think it would make sense to use NewClient
in that one place, otherwise we might as well remove the NewClient
API and simplify the connection logic for a single use connection.
if !hasCapabilities() { | ||
// Do nothing when not running as root. | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the NewClient
API also include this check? Maybe NewClient
can return a no-op client / an error, or clt.SendMsg
can also do this check and return nil?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks ok to me, my main worry is how error scenarios are handled. We've got to make sure we don't block or freeze connections if something's wrong. I would test things like what happens if auditd goes down.
lib/auditd/common.go
Outdated
Close() error | ||
} | ||
|
||
// Message is a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfinished comment.
lib/srv/reexec.go
Outdated
TerminalName string `json:"terminal_name"` | ||
|
||
ClientAddress string `json:"client_address"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Add godocs on these.
TTYName: c.TerminalName, | ||
} | ||
|
||
if err := auditd.SendEvent(auditd.AuditUserLogin, auditd.Success, auditdMsg); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will emit events for both interactive and non-interactive ("run command") sessions, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, does this work with automatic user provisioning? It should, but might be worth double-checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checked, correct events are generated in both cases (run command and user provisioning).
lib/auditd/auditd_linux.go
Outdated
if err := template.Must(template.New("auditd-message"). | ||
Parse(msgDataTmpl)). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can template.Must(template.New("...").Parse(...))
be moved to a global scope variable so this function just executes it?
Data: MsgData, | ||
} | ||
|
||
resp, err := c.conn.Execute(msg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this block or something if auditd is down or not responding? Leading to the client being deadlocked due to a mutex.
@Joerger Can you re-review so we can get this merged in? We need it for Teleport 10.2. |
Fix tty name
Added tests
Add client IP and TTY name to auditd events
Fix AuditUserEnd message
Co-authored-by: Roman Tkachenko <roman@goteleport.com>
7c76ada
to
21c2d12
Compare
Add auditd integration. Co-authored-by: Nic Klaassen <nic@goteleport.com> Co-authored-by: Roman Tkachenko <roman@goteleport.com>
Add auditd integration. Co-authored-by: Nic Klaassen <nic@goteleport.com> Co-authored-by: Roman Tkachenko <roman@goteleport.com>
This PR implements integration with auditd. Events sent to Linux Audit System (auditd) should closely match those sends by OpenSSH.
Closes #13785