fleetdm / fleet

Open-source platform for IT, security, and infrastructure teams. (Linux, macOS, Chrome, Windows, cloud, data center)
https://fleetdm.com
Other
3.01k stars 418 forks source link

Create new `POST /mdm/apple_bm/keys` endpoint that generates and returns public/private keys #8969

Closed lukeheath closed 1 year ago

lukeheath commented 1 year ago

Problem

As a Fleet admin, I want to connect Fleet to my Apple Business Manager account to automatically enroll new macOS hosts to Fleet. This way, I can order a new MacBook that automatically appears in Fleet when it's unboxed.

Related

Requirements

Default response

{
    "public_key": "base64 encoded contents",
    "private_key": "base64 encoded contents"
}
lukeheath commented 1 year ago

@michalnicp @roperzh Here is the issue for the API endpoint that generates new keys for Apple Business Manager. This is a blocker for something we're currently working on in the UI. Does either of you have the capacity to take this on?

roperzh commented 1 year ago

@lukeheath I will take this on, and have a PR ready so tomorrow @michalnicp can review/use it for the fleetctl command too.

lukeheath commented 1 year ago

@roperzh Great, thank you!

michalnicp commented 1 year ago

Shouldn't this be a POST request? Generally anything that has side effects, or creates something shouldn't be a GET.

@roperzh mentions here https://github.com/fleetdm/fleet/issues/8724#issuecomment-1341363555 that we have some existing strategies that the frontend users. Looks like the hosts csv report uses an api endpoint /hosts/report that returns

content-disposition: attachment; filename="Hosts 2022-12-08.csv"
content-type: text/csv

This endpoint needs to download multiple files, the private and public keys. Do we need 2 separate endpoints? how do we ensure that that the public key matches the private key? Should we consider adding them to a zip archive and telling the user to extract it?

@lukeheath

michalnicp commented 1 year ago

It may be possible to download multiple files using a multipart form response, but I have not tried this and would require testing.

roperzh commented 1 year ago

@michalnicp thanks! I didn't get started yet but these are super useful questions.

@lukeheath would it be acceptable to return a zip file with the key pair? that would certainly make thing easier for you folks in the front-end, otherwise a multipart download as Michal suggested should be possible but a bit more involved.

we could always add a second endpoint and make two requests of course.

lukeheath commented 1 year ago

@michalnicp @roperzh

Shouldn't this be a POST request?

That was my first thought, but then I saw the host CSV endpoint is GET so I wanted to be consistent. But then, I referenced the other endpoint that uses content-type application/octet-stream and it is POST, which makes more sense. I've updated the ticket to use POST.

Question: Do you think we should change the host CSV endpoint to use POST, as well? Or does it make sense to stay GET because it's it's only returning data?

would it be acceptable to return a zip file with the key pair?

I think that might be acceptable, but we should do whatever is best for the user. In this case, I think that means two endpoints, so the user doesn't have to deal with a zip file. Any thoughts on what those endpoints should be named? POST /mdm/apple_bm/keys/public and POST /mdm/apple_bm/keys/private?

roperzh commented 1 year ago

@lukeheath we chatted with Michal and this overlaps a lot with work he's already done, so he'll take over.

lukeheath commented 1 year ago

Sounds good. I chatted with @michalnicp and:

lukeheath commented 1 year ago

@michalnicp Feel free to update the description/specs of this ticket as needed to reflect your implementation.

michalnicp commented 1 year ago

I have an alternative idea. We could return the public and private keys together in a json payload like this

{
    "public-key.crt": "base64 encoded contents",
    "private.key": "base64 encoded contents"
}

The frontend would need to handle multiple files and provide some way of downloading them, perhaps by inlining them inside an a element. For example, create some buttons to download the files individually and inline the base64 encoded contents from the json response

<a download="public-key.crt" href="data:application/octet-stream;base64,..."><input type="button" value="Download Public Key"></a>
<a download="private.key" href="data:application/octet-stream;base64,..."><input type="button" value="Download Private Key"></a>
lukeheath commented 1 year ago

@michalnicp This approach works for me. As you pointed out, there's a straightforward way to convert the base64 string to a file. Something like:

function downloadPem(pem) {
    const linkSource = `data:application/octet-stream;base64,${pem}`;
    const downloadLink = document.createElement("a");
    const fileName = "fleet-mdm-apple-bm-public-key.pem";

    downloadLink.href = linkSource;
    downloadLink.download = fileName;
    downloadLink.click();
}

@ghernandez345 Do you have any concerns with this approach?

ghernandez345 commented 1 year ago

@michalnicp @lukeheath Still trying to get my head around the MDM flow. Just to clarify are you suggesting there are two buttons that a user would have to click to download a public and private key with this approach? Would that mean a UI like this would need to change to accommodate two download buttons?

image

If this is the case I'd rather we have one endpoint that sends both the public and private keys, something like downloading a .zip with these in there. that way the user only has to press one button.

If I'm completely off on the flow here let me know.

lukeheath commented 1 year ago

@ghernandez345 Not quite. There's still only be one download button. What Michal is proposing is that instead of making two separate API calls for two separate files, there's a single endpoint that returns the files as JSON with base64 encoded strings:

{
    "public-key.crt": "base64 encoded contents",
    "private.key": "base64 encoded contents"
}

This is because the public/private keys depend on each other, so they need to be generated simultaneously.

That means we'd need to decode the base64 string to the text files using the method I outlined above. That shows one function, but we'd have another function to decode the other file and call them both when the user clicks "Download."

This isn't unheard of, but since it's a non-standard way to transmit files, I wanted to ensure you didn't see any potential issues on the front end.