From 40c52639276818d0b03643cb7be0de01ecc2774b Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Sun, 19 Sep 2021 17:54:41 +0100 Subject: [PATCH] Add initial code for generating Apple profiles This code adds new http handlers that will generate iOS and macOS configuration profiles allowing us to override the Control server of the official Tailscale.app. Currently, macOS is working, as I have not found the correct "key" to inject for iOS. This means that a profile will allow users to no longer log in via the command line, but they can use the app. --- apple_mobileconfig.go | 179 ++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + 3 files changed, 182 insertions(+) create mode 100644 apple_mobileconfig.go diff --git a/apple_mobileconfig.go b/apple_mobileconfig.go new file mode 100644 index 00000000..cfaffa06 --- /dev/null +++ b/apple_mobileconfig.go @@ -0,0 +1,179 @@ +package headscale + +import ( + "bytes" + "net/http" + "text/template" + + "github.com/gin-gonic/gin" + "github.com/gofrs/uuid" +) + +// AppleMobileConfig shows a simple message in the browser to point to the CLI +// Listens in /register +func (h *Headscale) AppleMobileConfig(c *gin.Context) { + t := template.Must(template.New("apple").Parse(` + + +

Apple configuration profiles

+

+ This page provides configuration profiles for the official Tailscale clients for iOS and macOS. +

+

+ The profiles will configure Tailscale.app to use {{.Url}} as its control server. +

+ +

Caution

+

You should always inspect the profile before installing it:

+

curl {{.Url}}/apple/ios

+

curl {{.Url}}/apple/macos

+ +

Profiles

+

+ iOS +

+ +

+ macOS +

+ + +`)) + + config := map[string]interface{}{ + "Url": h.cfg.ServerURL, + } + + var payload bytes.Buffer + if err := t.Execute(&payload, config); err != nil { + c.Error(err) + return + } + + c.Data(http.StatusOK, "text/html; charset=utf-8", payload.Bytes()) +} + +func (h *Headscale) ApplePlatformConfig(c *gin.Context) { + platform := c.Param("platform") + + id, err := uuid.NewV4() + if err != nil { + c.Error(err) + return + } + + contentId, err := uuid.NewV4() + if err != nil { + c.Error(err) + return + } + + platformConfig := AppleMobilePlatformConfig{ + UUID: contentId, + Url: h.cfg.ServerURL, + } + + var payload bytes.Buffer + + switch platform { + case "macos": + if err := macosTemplate.Execute(&payload, platformConfig); err != nil { + c.Error(err) + return + } + case "ios": + if err := iosTemplate.Execute(&payload, platformConfig); err != nil { + c.Error(err) + return + } + default: + c.Data(http.StatusOK, "text/html; charset=utf-8", []byte("Invalid platform, only ios and macos is supported")) + return + } + + config := AppleMobileConfig{ + UUID: id, + Url: h.cfg.ServerURL, + Payload: payload.String(), + } + + var content bytes.Buffer + if err := commonTemplate.Execute(&content, config); err != nil { + c.Error(err) + return + } + + c.Data(http.StatusOK, "application/x-apple-aspen-config; charset=utf-8", content.Bytes()) +} + +type AppleMobileConfig struct { + UUID uuid.UUID + Url string + Payload string +} + +type AppleMobilePlatformConfig struct { + UUID uuid.UUID + Url string +} + +var commonTemplate = template.Must(template.New("mobileconfig").Parse(` + + + + PayloadUUID + {{.UUID}} + PayloadDisplayName + Headscale + PayloadDescription + Configure Tailscale login server to: {{.Url}} + PayloadIdentifier + com.github.juanfont.headscale + PayloadRemovalDisallowed + + PayloadType + Configuration + PayloadVersion + 1 + PayloadContent + + {{.Payload}} + + +`)) + +var iosTemplate = template.Must(template.New("iosTemplate").Parse(` + + PayloadType + io.tailscale.ipn.ios + PayloadUUID + {{.UUID}} + PayloadIdentifier + com.github.juanfont.headscale + PayloadVersion + 1 + PayloadEnabled + + + ControlURL + {{.Url}} + +`)) + +var macosTemplate = template.Must(template.New("macosTemplate").Parse(` + + PayloadType + io.tailscale.ipn.macos + PayloadUUID + {{.UUID}} + PayloadIdentifier + com.github.juanfont.headscale + PayloadVersion + 1 + PayloadEnabled + + + ControlURL + {{.Url}} + +`)) diff --git a/go.mod b/go.mod index 0d8c86b5..88c314f6 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/AlecAivazis/survey/v2 v2.0.5 github.com/gin-gonic/gin v1.7.2 + github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b github.com/klauspost/compress v1.13.1 github.com/lib/pq v1.10.2 // indirect diff --git a/go.sum b/go.sum index 4751eaac..88cb0773 100644 --- a/go.sum +++ b/go.sum @@ -219,6 +219,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=