Skip to content
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

Merged
merged 42 commits into from
Sep 5, 2022
Merged

Auditd integration #14948

merged 42 commits into from
Sep 5, 2022

Conversation

jakule
Copy link
Contributor

@jakule jakule commented Jul 27, 2022

This PR implements integration with auditd. Events sent to Linux Audit System (auditd) should closely match those sends by OpenSSH.

Closes #13785

@jakule jakule mentioned this pull request Aug 15, 2022
@jakule jakule marked this pull request as ready for review August 15, 2022 21:40
@github-actions github-actions bot requested review from Joerger and nklaassen August 15, 2022 21:40
lib/auditd/auditd_linux.go Outdated Show resolved Hide resolved
Comment on lines 88 to 90
// 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
Copy link
Contributor

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

Copy link
Contributor Author

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/auditd/auditd_linux.go Outdated Show resolved Hide resolved
lib/auditd/auditd_linux.go Outdated Show resolved Hide resolved
lib/auditd/auditd_linux.go Outdated Show resolved Hide resolved
lib/service/service.go Outdated Show resolved Hide resolved
lib/service/service.go Outdated Show resolved Hide resolved
@@ -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. " +
Copy link
Contributor

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?

Copy link
Contributor Author

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.

return errorWriter, teleport.RemoteCommandFailure, trace.Errorf("failed to login user start: %v", err)
}

defer func(err *error) {
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, fixed.

lib/srv/reexec.go Outdated Show resolved Hide resolved
lib/auditd/auditd.go Show resolved Hide resolved
lib/auditd/auditd_linux.go Outdated Show resolved Hide resolved
lib/auditd/auditd_linux.go Outdated Show resolved Hide resolved
lib/auditd/auditd_linux.go Outdated Show resolved Hide resolved
return status, nil
}

// SendMsg sends a message. Client will create a new connection if not connected already.
Copy link
Contributor

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.

Copy link
Contributor Author

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():

  1. 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.
  2. 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:

  1. 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 the SendEvent() function. That would make the API more explicit.
  2. Leave it as it is. Currently, the auditd package exposes two APIs. Higher lever SendEvent() function that allows you to send one message and NewClient() that can create a new client that is thread-safe.
  3. 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

Copy link
Contributor

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.

lib/srv/reexec.go Outdated Show resolved Hide resolved
return status, nil
}

// SendMsg sends a message. Client will create a new connection if not connected already.
Copy link
Contributor

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.

Comment on lines +129 to +138
if !hasCapabilities() {
// Do nothing when not running as root.
return nil
}
Copy link
Contributor

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?

Copy link
Collaborator

@r0mant r0mant left a 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.

Close() error
}

// Message is a
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfinished comment.

lib/srv/authhandlers.go Outdated Show resolved Hide resolved
Comment on lines 101 to 107
TerminalName string `json:"terminal_name"`

ClientAddress string `json:"client_address"`
Copy link
Collaborator

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.

lib/auditd/common.go Show resolved Hide resolved
TTYName: c.TerminalName,
}

if err := auditd.SendEvent(auditd.AuditUserLogin, auditd.Success, auditdMsg); err != nil {
Copy link
Collaborator

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?

Copy link
Collaborator

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.

Copy link
Contributor Author

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 Show resolved Hide resolved
lib/auditd/auditd_linux.go Outdated Show resolved Hide resolved
lib/auditd/auditd_linux.go Outdated Show resolved Hide resolved
Comment on lines 255 to 256
if err := template.Must(template.New("auditd-message").
Parse(msgDataTmpl)).
Copy link
Collaborator

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)
Copy link
Collaborator

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.

@russjones
Copy link
Contributor

@Joerger Can you re-review so we can get this merged in? We need it for Teleport 10.2.

@jakule jakule enabled auto-merge (squash) September 5, 2022 13:49
@jakule jakule merged commit 7744f72 into master Sep 5, 2022
@github-actions
Copy link

github-actions bot commented Sep 5, 2022

@jakule See the table below for backport results.

Branch Result
branch/v10 Failed

jakule added a commit that referenced this pull request Sep 6, 2022
Add auditd integration.

Co-authored-by: Nic Klaassen <nic@goteleport.com>
Co-authored-by: Roman Tkachenko <roman@goteleport.com>
@jakule jakule mentioned this pull request Sep 6, 2022
jakule added a commit that referenced this pull request Sep 6, 2022
Backport of #14948

Add auditd integration.
capnspacehook pushed a commit that referenced this pull request Sep 12, 2022
Add auditd integration.

Co-authored-by: Nic Klaassen <nic@goteleport.com>
Co-authored-by: Roman Tkachenko <roman@goteleport.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement auditd support
5 participants