150 lines
3.5 KiB
Go
150 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
|
|
)
|
|
|
|
var (
|
|
apiToken = flag.String("api-token", "", "API token for the MicroMDM instance")
|
|
url = flag.String("url", "", "URL of the MicroMDM instance")
|
|
port = flag.String("port", "4648", "Port used by the webserver")
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if *apiToken == "" || *url == "" {
|
|
log.Fatal("--api-token and --url are required.")
|
|
}
|
|
|
|
client := newMicroMDMClient(*apiToken, *url)
|
|
|
|
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
|
body, err := io.ReadAll(request.Body)
|
|
if err != nil {
|
|
slog.With("error", err).Error("reading request body")
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if len(body) == 0 {
|
|
slog.Error("empty request body")
|
|
writer.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
slog.With("raw_body", string(body)).Debug("got request")
|
|
|
|
var deviceInfo struct {
|
|
Host struct {
|
|
UUID string `json:"uuid"`
|
|
} `json:"host"`
|
|
}
|
|
if err := json.Unmarshal(body, &deviceInfo); err != nil {
|
|
slog.With("device_uuid", deviceInfo.Host.UUID, "error", err).Error("failed to unmarshal request body")
|
|
writer.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
slog.With("device_uuid", deviceInfo.Host.UUID).Info("attempting to unenroll from MicroMDM")
|
|
if err := client.unmanageDevice(deviceInfo.Host.UUID); err != nil {
|
|
slog.With("device_uuid", deviceInfo.Host.UUID, "error", err).Error("failed to unenroll device")
|
|
writer.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
slog.With("device_uuid", deviceInfo.Host.UUID).Info("device unenrolled")
|
|
})
|
|
|
|
slog.With("address", fmt.Sprintf("http://localhost:%s", *port)).Info("server running")
|
|
server := &http.Server{
|
|
Addr: fmt.Sprintf(":%s", *port),
|
|
ReadHeaderTimeout: 3 * time.Second,
|
|
}
|
|
if err := server.ListenAndServe(); err != nil {
|
|
log.Fatal(err.Error())
|
|
}
|
|
}
|
|
|
|
type microMDMClient struct {
|
|
url string
|
|
token string
|
|
}
|
|
|
|
func newMicroMDMClient(apiToken, url string) *microMDMClient {
|
|
client := µMDMClient{url: url, token: apiToken}
|
|
return client
|
|
}
|
|
|
|
func (m *microMDMClient) doWithRequest(req *http.Request) ([]byte, error) {
|
|
client := fleethttp.NewClient()
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.StatusCode > 299 {
|
|
return body, fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
|
}
|
|
|
|
return body, nil
|
|
}
|
|
|
|
func (m *microMDMClient) do(method, path string, data any) ([]byte, error) {
|
|
var body []byte
|
|
if data != nil {
|
|
b, err := json.Marshal(data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshaling request body: %w", err)
|
|
}
|
|
body = b
|
|
}
|
|
|
|
makeReq := func() (*http.Request, error) {
|
|
if len(body) > 0 {
|
|
return http.NewRequest(method, path, bytes.NewBuffer(body))
|
|
}
|
|
|
|
return http.NewRequest(method, path, nil)
|
|
}
|
|
|
|
req, err := makeReq()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Add("accept", "application/json")
|
|
req.SetBasicAuth("micromdm", m.token)
|
|
return m.doWithRequest(req)
|
|
}
|
|
|
|
func (m *microMDMClient) unmanageDevice(uuid string) error {
|
|
req := struct {
|
|
RequestType string `json:"request_type"`
|
|
UDID string `json:"udid"`
|
|
Identifier string `json:"identifier"`
|
|
}{
|
|
RequestType: "RemoveProfile",
|
|
UDID: uuid,
|
|
Identifier: "com.github.micromdm.micromdm.enroll",
|
|
}
|
|
_, err := m.do("POST", fmt.Sprintf("%s/v1/commands", m.url), &req)
|
|
return err
|
|
}
|