Skip to content

Commit

Permalink
v2 first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
groob committed Mar 14, 2017
1 parent a5a5d30 commit 51944b1
Show file tree
Hide file tree
Showing 14 changed files with 509 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ cmd/squirrel/squirrel
cmd/caddy-squirrel/squirrel
Caddyfile
build/
vendor/
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
FROM alpine:3.4
RUN apk --update add \
ca-certificates

COPY ./build/squirrel-linux-amd64 /squirrel

CMD ["/squirrel", "serve"]

36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,35 @@
Squirrel is a simple HTTP server for munki
Squirrel is a Simple HTTPs server for munki.
See the [legacy](https://github.com/micromdm/squirrel/tree/legacy) branch for the API implementation. I plan on adding it back with Munki v3 support.

# Features

* [X] **Automatic HTTPS** - squirrel provides a built in Let's Encrypt Client.
* [X] Basic Authentication - Basic Auth for munki repo

#Quickstart

```
squirrel serve -repo=/path/to/munki_repo -tls-domain=munki.corp.example.com --basic-auth=CHANGEME
```

`-repo` flag must be set to the path of a munki repository.
`-tls-domain` flag must be set to the domain of your munki repo. This value is used by squirrel to obtain new TLS certificates.
`-basic-auth` flag must be set to a password which will be used for authentication to the munki repo.

Once the server starts, you will see a prompt which prints the correct Authorization header that you need to add to your munki configuration profile.

Example:
```
Authorization: Basic c3F1aXJyZWw6Q0hBTkdFTUU=
```

See `squirrel help` for full usage.

# Keep the process running with systemd

For help with systemd see the example/systemd folder.

# Enroll mac hosts:

For help enrolling macOS hosts, check out the example/profile folder.

45 changes: 45 additions & 0 deletions cli/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package cli

import (
"fmt"
"os"

"github.com/micromdm/squirrel/version"
)

func Main() {
if len(os.Args) < 2 {
printHelp()
}
switch os.Args[1] {
case "serve":
Serve()
return
case "help", "-h", "--help":
printHelp()
return
case "version":
version.Print()
return
default:
fmt.Printf("no such command")
return
}
}

const usage = `
Usage:
squirrel <COMMAND>
Available Commands:
help
serve
version
Use squirel <command> -h for additional usage of each command.
Example: squirrel serve -h
`

func printHelp() {
fmt.Println(usage)
}
167 changes: 167 additions & 0 deletions cli/serve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package cli

import (
"crypto/tls"
"encoding/base64"
"flag"
"fmt"
"log"
"net/http"
"os"
"time"

"golang.org/x/crypto/acme/autocert"
)

func Serve() {
serveCMD := flag.NewFlagSet("ca", flag.ExitOnError)
status := serve(serveCMD)
os.Exit(status)
}

const authUsername = "squirrel"

func serve(cmd *flag.FlagSet) int {
var (
flRepo = cmd.String("repo", envString("SQUIRREL_MUNKI_REPO_PATH", ""), "path to munki repo")
flBasicPassword = cmd.String("basic-auth", envString("SQUIRREL_BASIC_AUTH", ""), "http basic auth password for /repo/")
flTLS = cmd.Bool("tls", envBool("SQUIRREL_USE_TLS", true), "use https")
flTLSCert = cmd.String("tls-cert", envString("SQUIRREL_TLS_CERT", ""), "path to TLS certificate")
flTLSKey = cmd.String("tls-key", envString("SQUIRREL_TLS_KEY", ""), "path to TLS private key")
flTLSAuto = cmd.String("tls-domain", envString("SQUIRREL_AUTO_TLS_DOMAIN", ""), "Automatically fetch certs from Let's Encrypt")
)
cmd.Parse(os.Args[2:])
mux := http.NewServeMux()
mux.Handle("/repo/", repoHandler(*flBasicPassword, *flRepo))

srv := &http.Server{
Addr: ":https",
Handler: mux,
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
ReadHeaderTimeout: 10 * time.Second,
IdleTimeout: 10 * time.Minute,
MaxHeaderBytes: 1 << 18, // 0.25 MB
TLSConfig: tlsConfig(),
}

printMunkiHeadersHelp(*flBasicPassword)
if !*flTLS {
log.Fatal(http.ListenAndServe(":80", mux))
return 0
}

if *flTLSAuto != "" {
serveACME(srv, *flTLSAuto)
return 0
}

tlsFromFile := *flTLSAuto == "" || (*flTLSCert != "" && *flTLSKey != "")
if tlsFromFile {
serveTLS(srv, *flTLSCert, *flTLSKey)
return 0
}

return 0
}

func printMunkiHeadersHelp(password string) {
const help = `
To connect your clients to the server, you will need to set the authentication headers.
See https://github.com/munki/munki/wiki/Using-Basic-Authentication#configuring-the-clients-to-use-a-password
for additional details:
The headers header you should use is:
%s
To configure manually, use:
sudo defaults write /Library/Preferences/ManagedInstalls AdditionalHttpHeaders -array "%s"
`
auth := basicAuth(password)
header := fmt.Sprintf("Authorization: Basic %s", auth)
fmt.Println(fmt.Sprintf(help, header, header))
}

func serveTLS(server *http.Server, certPath, keyPath string) {
redirectTLS()
log.Fatal(server.ListenAndServeTLS(certPath, keyPath))
}

func serveACME(server *http.Server, domain string) {
m := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(domain),
Cache: autocert.DirCache("./certificates"),
}
server.TLSConfig.GetCertificate = m.GetCertificate
redirectTLS()
log.Fatal(server.ListenAndServeTLS("", ""))
}

// redirects port 80 to port 443
func redirectTLS() {
srv := &http.Server{
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close")
url := "https://" + req.Host + req.URL.String()
http.Redirect(w, req, url, http.StatusMovedPermanently)
}),
}
go func() { log.Fatal(srv.ListenAndServe()) }()
}

func repoHandler(repoPassword string, path string) http.HandlerFunc {
repo := http.StripPrefix("/repo/", http.FileServer(http.Dir(path)))
return func(w http.ResponseWriter, r *http.Request) {
_, password, ok := r.BasicAuth()
if !ok || password != repoPassword {
w.Header().Set("WWW-Authenticate", `Basic realm="munki"`)
http.Error(w, "you need to log in", http.StatusUnauthorized)
return
}
repo.ServeHTTP(w, r)
}
}

func tlsConfig() *tls.Config {
cfg := &tls.Config{
PreferServerCipherSuites: true,
CurvePreferences: []tls.CurveID{
tls.CurveP256,
tls.X25519,
},
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
}
return cfg
}

func envString(key, def string) string {
if env := os.Getenv(key); env != "" {
return env
}
return def
}

func envBool(key string, def bool) bool {
if env := os.Getenv(key); env == "true" {
return true
}
return def
}

func basicAuth(password string) string {
auth := authUsername + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}
22 changes: 22 additions & 0 deletions example/profile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
To enroll a mac into the `squirrel` server, install an updated version of this profile on the mac.

First, you'll have to make a few changes. Open the profile in your text editor andupdate the values as needed.

You _must_ set the correct value under
```
<array>
<string>Authorization: Basic CHANGEME</string>
</array>
```
The correct header will be printed when you run the `squirrel serve` command to start the server.


And SoftwareRepoURL:
```
<key>SoftwareRepoURL</key>
<string>https://munki.corp.micromdm.io/repo</string>
```

Note that `/repo` is a required path for squirrel.


65 changes: 65 additions & 0 deletions example/profile/munki.mobileconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadContent</key>
<dict>
<key>ManagedInstalls</key>
<dict>
<key>Forced</key>
<array>
<dict>
<key>mcx_preference_settings</key>
<dict>
<key>AppleSoftwareUpdatesOnly</key>
<false/>
<key>FollowHTTPRedirects</key>
<string>https</string>
<key>InstallAppleSoftwareUpdates</key>
<true/>
<key>SoftwareRepoURL</key>
<string>https://munki.corp.micromdm.io/repo</string>
<key>AdditionalHttpHeaders</key>
<array>
<string>Authorization: Basic CHANGEME</string>
</array>
</dict>
</dict>
</array>
</dict>
</dict>
<key>PayloadEnabled</key>
<true/>
<key>PayloadIdentifier</key>
<string>com.github.munki.munki_config</string>
<key>PayloadType</key>
<string>com.apple.ManagedClient.preferences</string>
<key>PayloadUUID</key>
<string>8d3324fc-b63a-4560-b9d3-f0f588158da1</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key>
<string>Configures ManagedSoftwareUpdate</string>
<key>PayloadDisplayName</key>
<string>ManagedSoftwareUpdate settings</string>
<key>PayloadIdentifier</key>
<string>com.github.munki.munki_config</string>
<key>PayloadOrganization</key>
<string>MicroMDM</string>
<key>PayloadRemovalDisallowed</key>
<true/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>c8782978-a43b-44eb-b5a0-786d94e10bcd</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
27 changes: 27 additions & 0 deletions example/systemd/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
The `squirrel.unit` file is an example systemd unit file for keeping the squirrel service running.


- change the configuration flags under `[Service]`.
- `sudo cp squirrel.unit /etc/systemd/system/`
- `sudo systemctl start squirrel.service`

# Useful commands
Reload Unit File:
```
sudo systemctl daemon-reload
```


Start/Stop/Restart/status:

```
sudo systemctl start squirrel.service
sudo systemctl stop squirrel.service
sudo systemctl restart squirrel.service
sudo systemctl status squirrel.service
```

Tail logs:
```
journalctl -u squirrel.service -f
```
14 changes: 14 additions & 0 deletions example/systemd/squirrel.unit
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[Unit]
Description=Squirrel Server
Documentation=https://github.com/micromdm/squirrel
After=network.target

[Service]
ExecStart=/usr/bin/squirrel serve \
-basic-auth=CHANGEME \
-repo=/munki-repo \
-tls-domain=munki.corp.micromdm.io
Restart=on-failure

[Install]
WantedBy=multi-user.target
Loading

0 comments on commit 51944b1

Please sign in to comment.