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

add support for collecitng multiple user keys on via secure enclave #1644

Merged

Conversation

James-Pickett
Copy link
Contributor

@James-Pickett James-Pickett commented Mar 7, 2024

  • adds a launcher cmd for creating secure enclave key
  • adds secureenclavesigner that implements crypto.Signer
  • secureenclavesigner determines current console user and returns public secureenclave key for that user, execing cmd in user context

for now this just creates keys, follow on work will implement actual signing

cmd/launcher/secure_enclave_darwin.go Outdated Show resolved Hide resolved
ses.pathToLauncherBinary = p
}

return ses, nil
Copy link
Contributor Author

Choose a reason for hiding this comment

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

wonder if we should do some kind of key verification here? we can at least query secure enclave to make sure it recognizes the users key.

Copy link
Contributor Author

@James-Pickett James-Pickett Mar 7, 2024

Choose a reason for hiding this comment

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

ehh .. not sure it's worth the extra execs

we don't save a key unless secure enclave creates it
once we store the key server side, if the key is lost some how launcher would need a new install if were strict on validating SE keys

ee/secureenclavesigner/secureenclavesigner_darwin.go Outdated Show resolved Hide resolved
ee/secureenclavesigner/secureenclavesigner_darwin.go Outdated Show resolved Hide resolved
@James-Pickett James-Pickett marked this pull request as ready for review March 7, 2024 21:31
if err != nil {
return nil, fmt.Errorf("marshalling hardware keys: %w", err)
}
results[0]["hardware_key"] = string(jsonBytes)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this produces a json array of objects with uid & keys, need to see how K2 handles this

Copy link
Contributor

Choose a reason for hiding this comment

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

This is changes the meaning of the column from prior and makes it different per platform. That's going to be hard to manage inside k2. Instead, I would recommend we move to using hardware_keys (and hardware_keys_source) and be consistent consistent across platforms.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed in #1647

ee/agent/keys.go Show resolved Hide resolved
ee/agent/keys_darwin.go Outdated Show resolved Hide resolved
ee/secureenclavesigner/secureenclavesigner_darwin.go Outdated Show resolved Hide resolved
ee/agent/keys_tpm.go Fixed Show fixed Hide fixed
RebeccaMahany
RebeccaMahany previously approved these changes Mar 8, 2024
Copy link
Contributor

@RebeccaMahany RebeccaMahany left a comment

Choose a reason for hiding this comment

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

🔥 let's try it!

RebeccaMahany
RebeccaMahany previously approved these changes Mar 8, 2024
Copy link
Contributor

@RebeccaMahany RebeccaMahany left a comment

Choose a reason for hiding this comment

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

💯

@James-Pickett James-Pickett added this pull request to the merge queue Mar 8, 2024
Merged via the queue into kolide:main with commit 0abfa8b Mar 8, 2024
29 checks passed
@James-Pickett James-Pickett deleted the james/secure-enclave-key-init branch March 8, 2024 22:43
Copy link
Contributor

@directionless directionless left a comment

Choose a reason for hiding this comment

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

Looks pretty good. Mostly nits and questions. I'm a bit ambivalent about the signature changes, but okay.

I think we do need to change how these are exposed to k2. The change in column meaning is hard to support.

// It's available as a separate command because launcher runs as root by default and since it's
// not in a user security context, it can't use the secure enclave directly. However, this command
// can be run in the user context using launchctl.
func runSecureEnclave(args []string) 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 cannot decide, but we could try to examine the parent process to see if launcher is in there. Though the parent is just going to be sudo, so this may not be feasible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Early on we considered trying to do this, but decided not to since it was easy to spoof ppid. If we're feeling different now and we do go this route, we could even exec codesign and verify that the calling binary is signed as we expect.

cmd/launcher/secure_enclave_test.go Show resolved Hide resolved
Comment on lines +307 to +308
// hardware signing is not implemented for darwin
if runtime.GOOS != "darwin" && e.hardwareSigner != nil && e.hardwareSigner.Public() != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why change this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the current state, the signer will not be nil, but will error when sign function is called. So it didn't make sense to me to just let it error.

return nil, fmt.Errorf("creating key: %w", err)
}

span.AddEvent("new_key_created")
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need this? It's somewhat implied by the function name. I guess we're still learning the tools

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 like seeing the events when I look at a span, but I can live without them.

@@ -92,6 +92,9 @@ type ExtensionOpts struct {
// RunDifferentialQueriesImmediately allows the client to execute a new query the first time it sees it,
// bypassing the scheduler.
RunDifferentialQueriesImmediately bool
// skipHardwareKeysSetup is a flag to indicate if we should skip setting up hardware keys.
// This is useful for testing environments where we don't have required hardware.
skipHardwareKeysSetup bool
Copy link
Contributor

Choose a reason for hiding this comment

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

I like that this is explicit, but it does require changing a lot of test filed. Do you think it makes more sense than switching off the environment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ehh, I waffled on this one. It felt worse to have logic in the production code to try and figure out if we were under test.

if err != nil {
return nil, fmt.Errorf("marshalling hardware keys: %w", err)
}
results[0]["hardware_key"] = string(jsonBytes)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is changes the meaning of the column from prior and makes it different per platform. That's going to be hard to manage inside k2. Instead, I would recommend we move to using hardware_keys (and hardware_keys_source) and be consistent consistent across platforms.

return nil, fmt.Errorf("getting public ecc data from store: %w", err)
}

if data != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if it's nil? That branch isn't populated. Maybe early return?

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 don't we would gain anything with an early return. With the intention of having key available as soon as possible, were making a call to currentConsoleUserKey that will get/create keys near bottom of func. We would have to add another line calling this if we early returned.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hrm. That's reasonable. But I think points to some lurking need to refactor. Maybe the quick path is to leave a comment, and call it good enough

"err", err,
)

if err := store.Delete([]byte(PublicEccDataKey)); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's probably correct to delete the key if it's corrupt, but it feels a little odd to do it here in the secureenclavesigner.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea it feels a little odd. I though it might be better than switching on errors in the calling function, deciding what to do, then calling new again. Also, since, without some big changes to the way SetUpKeys works, we have to let secureEnclaveSIgner handle the saving of new keys. So it kinda made sense to just let it handle the whole storage layer.

opt(ses)
}

if ses.pathToLauncherBinary == "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Feels a little odd to record this. Why not look at os.Executable each time?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is for testing. If we look at os.Executable in a test, it will have a go test binary and will error out.

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 will leave a comment about this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

comment left in #1647

"asuser",
u.Uid,
"sudo",
"--preserve-env",
Copy link
Contributor

Choose a reason for hiding this comment

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

Hrm. No idea what the risk is here. It's probably fine, but it's a bit of a yellow flag

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 believe we need this to retain the os variable LAUNCHER_SKIP_UPDATES, though we could move the cmd to the top of main and bypass the update logic completely.

Copy link
Contributor

Choose a reason for hiding this comment

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

There are a couple of ways to approach this.

If that's the only one we care about, maybe we could --preserve-env= LAUNCHER_SKIP_UPDATES.

I think we could also invoke sudo with LAUNCHER_SKIP_UPDATES=true in the command line. from the man page:

Environment variables to be set for the command may also be passed as options to sudo in the form VAR=value, for example LD_LIBRARY_PATH=/usr/local/pkg/lib.

@James-Pickett
Copy link
Contributor Author

follow up PR addressing post merge comments here #1647

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.

3 participants