From 7a19a15365e3654fc7f1124efb3aaa0911e0cf11 Mon Sep 17 00:00:00 2001
From: eternal-flame-AD <yume@yumechi.jp>
Date: Tue, 15 Nov 2022 10:31:27 -0600
Subject: [PATCH] refactor apparmor confinement

---
 Makefile                                |  3 +-
 cmd/server/server.go                    | 40 ++++++-------
 config/config.go                        |  7 +++
 go.mod                                  | 10 +++-
 go.sum                                  | 21 +++++++
 internal/apparmor/api.go                | 65 ---------------------
 internal/apparmor/apparmor.c            | 21 -------
 internal/apparmor/apparmor.h            |  8 ---
 internal/apparmor/magic.go              | 17 ------
 internal/util/apparmor_header.go        | 43 ++++++++++++++
 internal/version/version.go             | 10 +++-
 server/server.go                        | 77 +++++++++++++++++++------
 server/webroot/log.go                   | 17 +++++-
 webroot/twilio/voice/entrypoint.tpl.xml |  8 +++
 webroot/twilio/voice/voicemail.tpl.xml  |  6 ++
 15 files changed, 195 insertions(+), 158 deletions(-)
 delete mode 100644 internal/apparmor/api.go
 delete mode 100644 internal/apparmor/apparmor.c
 delete mode 100644 internal/apparmor/apparmor.h
 delete mode 100644 internal/apparmor/magic.go
 create mode 100644 internal/util/apparmor_header.go
 create mode 100644 webroot/twilio/voice/voicemail.tpl.xml

diff --git a/Makefile b/Makefile
index 58bdbe9..150b264 100644
--- a/Makefile
+++ b/Makefile
@@ -43,8 +43,7 @@ clean:
 
 dist/%: ${CMD_DIR}/% FORCE
 	go build \
-		-ldflags "-X ${MODULE_PATH}/internal/version.Version=$(VERSION) 	\
-				  -X ${MODULE_PATH}/internal/version.BuildDate=$(BUILDDATE)" \
+		-ldflags "-X ${MODULE_PATH}/internal/version.BuildDate=$(BUILDDATE)" \
 		-o $@ ${MODULE_PATH}/$<
 
 .PHONY: build clean
diff --git a/cmd/server/server.go b/cmd/server/server.go
index 33479d8..2c7be2a 100644
--- a/cmd/server/server.go
+++ b/cmd/server/server.go
@@ -5,8 +5,9 @@ import (
 	"log"
 	"os"
 
+	"github.com/eternal-flame-AD/go-apparmor/apparmor"
+	"github.com/eternal-flame-AD/go-apparmor/apparmor/magic"
 	"github.com/eternal-flame-AD/yoake/config"
-	"github.com/eternal-flame-AD/yoake/internal/apparmor"
 	"github.com/eternal-flame-AD/yoake/internal/comm"
 	"github.com/eternal-flame-AD/yoake/internal/db"
 	"github.com/eternal-flame-AD/yoake/server"
@@ -40,26 +41,14 @@ func init() {
 	}
 }
 
-func changeHat() {
-	profile := config.Config().Listen.AppArmor.Serve
-	if profile != "" {
-		token, err := apparmor.GetMagicToken()
-		if err != nil {
-			log.Panicf("failed to get apparmor magic token: %v", err)
-		}
-		if err := apparmor.ChangeHat(profile, token); err != nil {
-			log.Panicf("failed to change apparmor hat: %v", err)
-		} else {
-			log.Printf("changed apparmor hat to %s", profile)
-		}
-	}
-}
-
 func main() {
 	listen := config.Config().Listen
+
+	Server := server.New()
 	if listen.Ssl.Use {
 		var sslCertBytes, sslKeyBytes []byte
-		apparmor.ExecuteInHat(listen.AppArmor.SSL, func() {
+
+		readCerts := func() {
 			var err error
 			sslCertBytes, err = os.ReadFile(listen.Ssl.Cert)
 			if err != nil {
@@ -69,16 +58,27 @@ func main() {
 			if err != nil {
 				log.Panicf("failed to read ssl key: %v", err)
 			}
-		}, true)
+		}
+		magic, err := magic.Generate(nil)
+		if err != nil {
+			log.Panicf("failed to generate apparmor magic token: %v", err)
+		}
+
 		if listen.AppArmor.SSL != "" {
+			if err := apparmor.WithHat(listen.AppArmor.SSL, func() uint64 { return magic }, readCerts); err != nil {
+				log.Panicf("failed to read ssl cert/key with apparmor hat: %v", err)
+			}
+
 			// defensive programming, try read ssl key
 			if _, err := os.ReadFile(listen.Ssl.Key); err == nil {
 				log.Panicf("AppArmor profile set for SSL but I could still read %v!", listen.Ssl.Key)
 			}
+		} else {
+			readCerts()
 		}
 
-		log.Fatalln(server.Server.StartTLS(listen.Addr, sslCertBytes, sslKeyBytes))
+		log.Fatalln(Server.StartTLS(listen.Addr, sslCertBytes, sslKeyBytes))
 	} else {
-		log.Fatalln(server.Server.Start(listen.Addr))
+		log.Fatalln(Server.Start(listen.Addr))
 	}
 }
diff --git a/config/config.go b/config/config.go
index 2fbd4f5..cbd572b 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,11 +1,14 @@
 package config
 
 import (
+	"log"
+
 	"github.com/jinzhu/configor"
 	"github.com/labstack/echo/v4/middleware"
 )
 
 type C struct {
+	parsed bool
 	Hosts  map[string]string
 	Listen struct {
 		Addr     string
@@ -69,6 +72,9 @@ var parsedC C
 var c C
 
 func Config() C {
+	if !c.parsed {
+		log.Panicln("Config() called without calling ParseConfig() first")
+	}
 	return c
 }
 
@@ -81,5 +87,6 @@ func MockConfig(freshEnv bool, wrapper func(deployedC *C)) {
 
 func ParseConfig(files ...string) {
 	configor.Load(&parsedC, files...)
+	parsedC.parsed = true
 	c = parsedC
 }
diff --git a/go.mod b/go.mod
index 9141876..250cd12 100644
--- a/go.mod
+++ b/go.mod
@@ -24,17 +24,20 @@ require (
 	github.com/cespare/xxhash/v2 v2.1.1 // indirect
 	github.com/dgraph-io/badger/v3 v3.2103.4 // indirect
 	github.com/dgraph-io/ristretto v0.1.1 // indirect
+	github.com/duo-labs/webauthn v0.0.0-20220815211337-00c9fb5711f5 // indirect
 	github.com/dustin/go-humanize v1.0.0 // indirect
+	github.com/eternal-flame-AD/go-apparmor v0.0.3 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
 	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
-	github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/mock v1.6.0 // indirect
-	github.com/golang/protobuf v1.3.1 // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/snappy v0.0.3 // indirect
 	github.com/google/flatbuffers v1.12.1 // indirect
 	github.com/gorilla/css v1.0.0 // indirect
 	github.com/gorilla/securecookie v1.1.1 // indirect
+	github.com/jsipprell/keyctl v1.0.3 // indirect
 	github.com/klauspost/compress v1.12.3 // indirect
 	github.com/labstack/gommon v0.4.0 // indirect
 	github.com/mattn/go-colorable v0.1.12 // indirect
@@ -50,6 +53,7 @@ require (
 	golang.org/x/sys v0.0.0-20221010170243-090e33056c14 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
+	google.golang.org/protobuf v1.26.0 // indirect
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
-	gopkg.in/yaml.v2 v2.2.2 // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
 )
diff --git a/go.sum b/go.sum
index b9197a0..c82ab70 100644
--- a/go.sum
+++ b/go.sum
@@ -32,9 +32,17 @@ github.com/dgraph-io/badger/v3 v3.2103.4/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5js
 github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
 github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
 github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/duo-labs/webauthn v0.0.0-20220815211337-00c9fb5711f5 h1:BaeJtFDlto/NjX9t730OebRRJf2P+t9YEDz3ur18824=
+github.com/duo-labs/webauthn v0.0.0-20220815211337-00c9fb5711f5/go.mod h1:Jcj7rFNlTknb18v9jpSA58BveX2LDhXqaoy+6YV1N9g=
 github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
+github.com/eternal-flame-AD/go-apparmor v0.0.1 h1:LBWfkf/Mx0s6inwqurWC8nME7ICg4cDmh2fmOkBeenI=
+github.com/eternal-flame-AD/go-apparmor v0.0.1/go.mod h1:K8VSDcvYN18uG+vsnR+3um4t6fX13Km6ci9mgQfDMg8=
+github.com/eternal-flame-AD/go-apparmor v0.0.2 h1:sjDN6pyyjXBB+o+oDt6kyo2xiE8vvjZRABZ0fJEzHiE=
+github.com/eternal-flame-AD/go-apparmor v0.0.2/go.mod h1:OpqESxf/LXsssooWBPzAoIAC2PtloCT1CmA+glQKYV8=
+github.com/eternal-flame-AD/go-apparmor v0.0.3 h1:nFOxi6mbE8wpd5aHbSGvjbnaEjYC156IICWTteSgEIo=
+github.com/eternal-flame-AD/go-apparmor v0.0.3/go.mod h1:OpqESxf/LXsssooWBPzAoIAC2PtloCT1CmA+glQKYV8=
 github.com/eternal-flame-AD/yubigo v0.0.0-20221005082707-ce0c8989e8b1 h1:B+ad4UMWwNAUsZhLLQCCrEx+cfLsbf0+AbbcfG7RIv0=
 github.com/eternal-flame-AD/yubigo v0.0.0-20221005082707-ce0c8989e8b1/go.mod h1:kRnqsWaIjqWNPoCV14+cxs/B9eClc0hKL/I2a3LKOQ4=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -46,12 +54,17 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
 github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c h1:iyaGYbCmcYK0Ja9a3OUa2Fo+EaN0cbLu0eKpBwPFzc8=
@@ -60,6 +73,7 @@ github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6
 github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
@@ -72,6 +86,8 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
 github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
+github.com/jsipprell/keyctl v1.0.3 h1:o72tppb3ZhP5B/v9FGUtMqJWx+S1Gs0elQ7AZmiNhsM=
+github.com/jsipprell/keyctl v1.0.3/go.mod h1:64s6WpBtruURX3w8W/vhWj1/uh+nOm7vUXSJlK5+KMs=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
@@ -221,6 +237,9 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
 google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -231,6 +250,8 @@ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AW
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/internal/apparmor/api.go b/internal/apparmor/api.go
deleted file mode 100644
index 01e805d..0000000
--- a/internal/apparmor/api.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package apparmor
-
-import (
-	// #cgo LDFLAGS: -lapparmor
-	// #include "./apparmor.h"
-	"C"
-)
-import (
-	"runtime"
-	"syscall"
-	"unsafe"
-)
-
-func ChangeHat(subprofile string, magicToken uint64) error {
-	var ret uintptr
-	if subprofile != "" {
-		subProfileC := C.CString(subprofile)
-		defer C.free(unsafe.Pointer(subProfileC))
-		ret = uintptr(C.go_aa_change_hat(subProfileC, C.ulong(magicToken)))
-	} else {
-		ret = uintptr(C.go_aa_change_hat(nil, C.ulong(magicToken)))
-	}
-
-	if ret != 0 {
-		return syscall.Errno(ret)
-	}
-	return nil
-}
-
-func ExecuteInHat(subprofile string, fn func(), lockThread bool) error {
-	if subprofile == "" {
-		fn()
-		return nil
-	}
-	if lockThread {
-		runtime.LockOSThread()
-		defer runtime.UnlockOSThread()
-	}
-	token, err := GetMagicToken()
-	if err != nil {
-		return err
-	}
-	if err := ChangeHat(subprofile, token); err != nil {
-		return err
-	}
-	fn()
-	return ChangeHat("", token)
-}
-
-func ChangeProfile(subprofile string) error {
-	var ret uintptr
-
-	if subprofile != "" {
-		subProfileC := C.CString(subprofile)
-		defer C.free(unsafe.Pointer(subProfileC))
-		ret = uintptr(C.go_aa_change_profile(subProfileC))
-	} else {
-		ret = uintptr(C.go_aa_change_profile(nil))
-	}
-
-	if ret != 0 {
-		return syscall.Errno(ret)
-	}
-	return nil
-}
diff --git a/internal/apparmor/apparmor.c b/internal/apparmor/apparmor.c
deleted file mode 100644
index c867b00..0000000
--- a/internal/apparmor/apparmor.c
+++ /dev/null
@@ -1,21 +0,0 @@
-#include "./apparmor.h"
-
-int go_aa_change_hat(const char *hat, unsigned long magic)
-{
-    int ret = aa_change_hat(hat, magic);
-    if (ret < 0)
-    {
-        return errno;
-    }
-    return 0;
-}
-
-int go_aa_change_profile(const char *profile)
-{
-    int ret = aa_change_profile(profile);
-    if (ret < 0)
-    {
-        return errno;
-    }
-    return 0;
-}
\ No newline at end of file
diff --git a/internal/apparmor/apparmor.h b/internal/apparmor/apparmor.h
deleted file mode 100644
index 7d99e36..0000000
--- a/internal/apparmor/apparmor.h
+++ /dev/null
@@ -1,8 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <sys/apparmor.h>
-
-int go_aa_change_hat(const char *hat, unsigned long magic);
-
-int go_aa_change_profile(const char *profile);
\ No newline at end of file
diff --git a/internal/apparmor/magic.go b/internal/apparmor/magic.go
deleted file mode 100644
index 5f7a282..0000000
--- a/internal/apparmor/magic.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package apparmor
-
-import (
-	"crypto/rand"
-	"fmt"
-)
-
-func GetMagicToken() (uint64, error) {
-	var buf [64 / 8]byte
-	if _, err := rand.Read(buf[:]); err != nil {
-		return 0, fmt.Errorf("failed to generate magic token: %v", err)
-	}
-	return uint64(buf[0])<<56 | uint64(buf[1])<<48 |
-		uint64(buf[2])<<40 | uint64(buf[3])<<32 |
-		uint64(buf[4])<<24 | uint64(buf[5])<<16 |
-		uint64(buf[6])<<8 | uint64(buf[7]), nil
-}
diff --git a/internal/util/apparmor_header.go b/internal/util/apparmor_header.go
new file mode 100644
index 0000000..ac1fceb
--- /dev/null
+++ b/internal/util/apparmor_header.go
@@ -0,0 +1,43 @@
+package util
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"strings"
+
+	"github.com/eternal-flame-AD/go-apparmor/apparmor"
+	"github.com/labstack/echo/v4"
+)
+
+type AAConMiddlewareEnforcer func(label string, mode string) (exit int, err error)
+
+func AAConMiddleware(enforce AAConMiddlewareEnforcer) echo.MiddlewareFunc {
+	return func(next echo.HandlerFunc) echo.HandlerFunc {
+		return func(c echo.Context) error {
+			label, mode, err := apparmor.AAGetCon()
+			if err != nil {
+				log.Printf("failed to get apparmor label: %v", err)
+				label = "[ERROR]"
+			}
+			var sanitizedLabel string
+			if idx := strings.Index(label, "//"); idx == -1 {
+				sanitizedLabel = "//"
+			} else {
+				sanitizedLabel = label[idx:]
+			}
+			c.Response().Header().Set("X-App-Con", fmt.Sprintf("%s (%s)", sanitizedLabel, mode))
+			if enforce != nil {
+				if exitCode, err := enforce(label, mode); err != nil {
+					if exitCode == 0 {
+						c.Response().After(func() {
+							os.Exit(exitCode)
+						})
+					}
+					return err
+				}
+			}
+			return next(c)
+		}
+	}
+}
diff --git a/internal/version/version.go b/internal/version/version.go
index 389d5c5..588b687 100644
--- a/internal/version/version.go
+++ b/internal/version/version.go
@@ -1,6 +1,14 @@
 package version
 
+import "runtime/debug"
+
 var (
-	Version   = "unknown"
+	Version = func() string {
+		info, ok := debug.ReadBuildInfo()
+		if !ok {
+			return "unknown"
+		}
+		return info.Main.Version
+	}()
 	BuildDate = "unknown"
 )
diff --git a/server/server.go b/server/server.go
index 8d3c009..a9332e8 100644
--- a/server/server.go
+++ b/server/server.go
@@ -1,10 +1,14 @@
 package server
 
 import (
+	"errors"
+	"log"
 	"strings"
 
+	"github.com/eternal-flame-AD/go-apparmor/apparmor"
+	"github.com/eternal-flame-AD/go-apparmor/apparmor/magic"
 	"github.com/eternal-flame-AD/yoake/config"
-	"github.com/eternal-flame-AD/yoake/internal/apparmor"
+	"github.com/eternal-flame-AD/yoake/internal/util"
 	"github.com/labstack/echo/v4"
 )
 
@@ -14,25 +18,58 @@ type (
 	}
 )
 
-var Server = echo.New()
 var hosts = map[string]*Host{}
 
-func init() {
-	hatChanged := false
-	Server.Any("/*", func(c echo.Context) (err error) {
-		if !hatChanged {
-			appArmor := config.Config().Listen.AppArmor
-			if appArmor.Serve != "" {
-				if key, err := apparmor.GetMagicToken(); err != nil {
-					return err
-				} else {
-					if err := apparmor.ChangeHat(appArmor.Serve, key); err != nil {
-						return err
-					}
-				}
-			}
-			hatChanged = true
+func New() *echo.Echo {
+	var Server = echo.New()
+	hatServe := config.Config().Listen.AppArmor.Serve
+	if hatServe != "" {
+		store, err := magic.NewKeyring(nil)
+		if err != nil {
+			log.Panicf("failed to initialize magic token store: %v", err)
 		}
+		if magic, err := magic.Generate(nil); err != nil {
+			log.Panicf("failed to generate apparmor magic token: %v", err)
+		} else {
+			if err := store.Set(magic); err != nil {
+				log.Panicf("failed to store apparmor magic token: %v", err)
+			}
+		}
+		hatMagic := func() uint64 {
+			magic, err := store.Get()
+			if err != nil {
+				log.Panicf("failed to get magic token: %v", err)
+			}
+			return magic
+		}
+		Server.Pre(func(next echo.HandlerFunc) echo.HandlerFunc {
+			return func(c echo.Context) error {
+				var err error
+				if errAppArmor := apparmor.WithHat(hatServe, hatMagic, func() {
+					err = next(c)
+				}); errAppArmor != nil {
+					c.Logger().Errorf("apparmor error: %v", errAppArmor)
+					return errors.New("apparmor process transition error")
+				}
+				return err
+			}
+		})
+		aaEnforcer := util.AAConMiddleware(func(label string, mode string) (exit int, err error) {
+			if !strings.HasSuffix(label, "//"+hatServe) {
+				return 1, errors.New("apparmor process transition error")
+			}
+			return 0, nil
+		})
+
+		Server.Pre(aaEnforcer)
+		Server.Use(aaEnforcer)
+		for _, h := range hosts {
+			h.Echo.Pre(aaEnforcer)
+			h.Echo.Use(aaEnforcer)
+		}
+	}
+
+	Server.Any("/*", func(c echo.Context) (err error) {
 		req := c.Request()
 		res := c.Response()
 		host := hosts[strings.ToLower(req.Host)]
@@ -40,13 +77,17 @@ func init() {
 		if host == nil {
 			host = hosts[""]
 			if host == nil {
-				return echo.ErrNotFound
+				err = echo.ErrNotFound
+				return
 			}
 		}
 
 		host.Echo.ServeHTTP(res, req)
+
 		return
 	})
+
+	return Server
 }
 
 func RegisterHostname(hostname string, h *Host) {
diff --git a/server/webroot/log.go b/server/webroot/log.go
index a25513b..631d6cf 100644
--- a/server/webroot/log.go
+++ b/server/webroot/log.go
@@ -7,6 +7,7 @@ import (
 	"regexp"
 	"strconv"
 
+	"github.com/eternal-flame-AD/go-apparmor/apparmor"
 	"github.com/eternal-flame-AD/yoake/internal/auth"
 	"github.com/labstack/echo/v4"
 	"github.com/labstack/echo/v4/middleware"
@@ -14,9 +15,10 @@ import (
 
 type logEntry struct {
 	middleware.RequestLoggerValues
-	Categories []string
-	CleanPath  string
-	Auth       auth.RequestAuth
+	Categories  []string
+	CleanPath   string
+	AppArmorCon string
+	Auth        auth.RequestAuth
 }
 
 func processLoggerValues(c echo.Context, values middleware.RequestLoggerValues) logEntry {
@@ -26,11 +28,20 @@ func processLoggerValues(c echo.Context, values middleware.RequestLoggerValues)
 		logSetRequestCategory(c, fmt.Sprintf("status_%s", statusString))
 		statusString[i] = 'x'
 	}
+
+	aaCon := ""
+	label, mode, err := apparmor.AAGetCon()
+	if err != nil {
+		aaCon = fmt.Sprintf("error: %s", err)
+	} else {
+		aaCon = fmt.Sprintf("%s (%s)", label, mode)
+	}
 	return logEntry{
 		RequestLoggerValues: values,
 		Categories:          logGetCategories(c),
 		CleanPath:           path.Clean(c.Request().URL.Path),
 		Auth:                auth.GetRequestAuth(c),
+		AppArmorCon:         aaCon,
 	}
 }
 
diff --git a/webroot/twilio/voice/entrypoint.tpl.xml b/webroot/twilio/voice/entrypoint.tpl.xml
index 4ca4197..db3d238 100644
--- a/webroot/twilio/voice/entrypoint.tpl.xml
+++ b/webroot/twilio/voice/entrypoint.tpl.xml
@@ -4,7 +4,15 @@
 {{ $num_visited := (get $session "num_visited") }}
 {{ if not $num_visited }}{{ $num_visited = 0 }} {{ end }}
 {{ set $session "num_visited" (math "argv(1) + 1" $num_visited) }}
+
 <Response>
+
+{{ if $src := (invoke "FormValue" .C "ForwardedFrom") }}
+    {{ if eq $src "+15122993080" }}
+        <Redirect method="POST">/twilio/voice/voicemail.xml></Redirect>
+    {{ end }}
+{{ end }}
+
     <Say voice="alice" language="en-US">This is Anne!</Say>
     <Redirect method="POST">/twilio/voice/menu.xml</Redirect>
 </Response>
\ No newline at end of file
diff --git a/webroot/twilio/voice/voicemail.tpl.xml b/webroot/twilio/voice/voicemail.tpl.xml
new file mode 100644
index 0000000..19b051b
--- /dev/null
+++ b/webroot/twilio/voice/voicemail.tpl.xml
@@ -0,0 +1,6 @@
+{{ template "/twilio/head.tpl.xml" . }}
+
+<Response>
+<!-- TODO: finish -->
+    <Hangup />
+</Response>
\ No newline at end of file