refactor apparmor confinement
This commit is contained in:
parent
3857e34b52
commit
7a19a15365
15 changed files with 195 additions and 158 deletions
3
Makefile
3
Makefile
|
@ -43,8 +43,7 @@ clean:
|
||||||
|
|
||||||
dist/%: ${CMD_DIR}/% FORCE
|
dist/%: ${CMD_DIR}/% FORCE
|
||||||
go build \
|
go build \
|
||||||
-ldflags "-X ${MODULE_PATH}/internal/version.Version=$(VERSION) \
|
-ldflags "-X ${MODULE_PATH}/internal/version.BuildDate=$(BUILDDATE)" \
|
||||||
-X ${MODULE_PATH}/internal/version.BuildDate=$(BUILDDATE)" \
|
|
||||||
-o $@ ${MODULE_PATH}/$<
|
-o $@ ${MODULE_PATH}/$<
|
||||||
|
|
||||||
.PHONY: build clean
|
.PHONY: build clean
|
||||||
|
|
|
@ -5,8 +5,9 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"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/config"
|
||||||
"github.com/eternal-flame-AD/yoake/internal/apparmor"
|
|
||||||
"github.com/eternal-flame-AD/yoake/internal/comm"
|
"github.com/eternal-flame-AD/yoake/internal/comm"
|
||||||
"github.com/eternal-flame-AD/yoake/internal/db"
|
"github.com/eternal-flame-AD/yoake/internal/db"
|
||||||
"github.com/eternal-flame-AD/yoake/server"
|
"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() {
|
func main() {
|
||||||
listen := config.Config().Listen
|
listen := config.Config().Listen
|
||||||
|
|
||||||
|
Server := server.New()
|
||||||
if listen.Ssl.Use {
|
if listen.Ssl.Use {
|
||||||
var sslCertBytes, sslKeyBytes []byte
|
var sslCertBytes, sslKeyBytes []byte
|
||||||
apparmor.ExecuteInHat(listen.AppArmor.SSL, func() {
|
|
||||||
|
readCerts := func() {
|
||||||
var err error
|
var err error
|
||||||
sslCertBytes, err = os.ReadFile(listen.Ssl.Cert)
|
sslCertBytes, err = os.ReadFile(listen.Ssl.Cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -69,16 +58,27 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicf("failed to read ssl key: %v", err)
|
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 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
|
// defensive programming, try read ssl key
|
||||||
if _, err := os.ReadFile(listen.Ssl.Key); err == nil {
|
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)
|
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 {
|
} else {
|
||||||
log.Fatalln(server.Server.Start(listen.Addr))
|
log.Fatalln(Server.Start(listen.Addr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/jinzhu/configor"
|
"github.com/jinzhu/configor"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
type C struct {
|
type C struct {
|
||||||
|
parsed bool
|
||||||
Hosts map[string]string
|
Hosts map[string]string
|
||||||
Listen struct {
|
Listen struct {
|
||||||
Addr string
|
Addr string
|
||||||
|
@ -69,6 +72,9 @@ var parsedC C
|
||||||
var c C
|
var c C
|
||||||
|
|
||||||
func Config() C {
|
func Config() C {
|
||||||
|
if !c.parsed {
|
||||||
|
log.Panicln("Config() called without calling ParseConfig() first")
|
||||||
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,5 +87,6 @@ func MockConfig(freshEnv bool, wrapper func(deployedC *C)) {
|
||||||
|
|
||||||
func ParseConfig(files ...string) {
|
func ParseConfig(files ...string) {
|
||||||
configor.Load(&parsedC, files...)
|
configor.Load(&parsedC, files...)
|
||||||
|
parsedC.parsed = true
|
||||||
c = parsedC
|
c = parsedC
|
||||||
}
|
}
|
||||||
|
|
10
go.mod
10
go.mod
|
@ -24,17 +24,20 @@ require (
|
||||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||||
github.com/dgraph-io/badger/v3 v3.2103.4 // indirect
|
github.com/dgraph-io/badger/v3 v3.2103.4 // indirect
|
||||||
github.com/dgraph-io/ristretto v0.1.1 // 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/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/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // 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/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/golang/snappy v0.0.3 // indirect
|
||||||
github.com/google/flatbuffers v1.12.1 // indirect
|
github.com/google/flatbuffers v1.12.1 // indirect
|
||||||
github.com/gorilla/css v1.0.0 // indirect
|
github.com/gorilla/css v1.0.0 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.1 // 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/klauspost/compress v1.12.3 // indirect
|
||||||
github.com/labstack/gommon v0.4.0 // indirect
|
github.com/labstack/gommon v0.4.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.12 // 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/sys v0.0.0-20221010170243-090e33056c14 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // 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/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
|
||||||
)
|
)
|
||||||
|
|
21
go.sum
21
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 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
|
||||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
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/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 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
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/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 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/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=
|
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/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 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
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.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 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
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.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 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
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 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c h1:iyaGYbCmcYK0Ja9a3OUa2Fo+EaN0cbLu0eKpBwPFzc8=
|
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/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.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.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 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
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/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 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
|
||||||
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
|
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/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
|
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/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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
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 h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
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=
|
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/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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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-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.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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);
|
|
|
@ -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
|
|
||||||
}
|
|
43
internal/util/apparmor_header.go
Normal file
43
internal/util/apparmor_header.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,14 @@
|
||||||
package version
|
package version
|
||||||
|
|
||||||
|
import "runtime/debug"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version = "unknown"
|
Version = func() string {
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
return info.Main.Version
|
||||||
|
}()
|
||||||
BuildDate = "unknown"
|
BuildDate = "unknown"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
"strings"
|
"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/config"
|
||||||
"github.com/eternal-flame-AD/yoake/internal/apparmor"
|
"github.com/eternal-flame-AD/yoake/internal/util"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,25 +18,58 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var Server = echo.New()
|
|
||||||
var hosts = map[string]*Host{}
|
var hosts = map[string]*Host{}
|
||||||
|
|
||||||
func init() {
|
func New() *echo.Echo {
|
||||||
hatChanged := false
|
var Server = echo.New()
|
||||||
Server.Any("/*", func(c echo.Context) (err error) {
|
hatServe := config.Config().Listen.AppArmor.Serve
|
||||||
if !hatChanged {
|
if hatServe != "" {
|
||||||
appArmor := config.Config().Listen.AppArmor
|
store, err := magic.NewKeyring(nil)
|
||||||
if appArmor.Serve != "" {
|
if err != nil {
|
||||||
if key, err := apparmor.GetMagicToken(); err != nil {
|
log.Panicf("failed to initialize magic token store: %v", err)
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
if err := apparmor.ChangeHat(appArmor.Serve, key); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hatChanged = true
|
|
||||||
}
|
}
|
||||||
|
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()
|
req := c.Request()
|
||||||
res := c.Response()
|
res := c.Response()
|
||||||
host := hosts[strings.ToLower(req.Host)]
|
host := hosts[strings.ToLower(req.Host)]
|
||||||
|
@ -40,13 +77,17 @@ func init() {
|
||||||
if host == nil {
|
if host == nil {
|
||||||
host = hosts[""]
|
host = hosts[""]
|
||||||
if host == nil {
|
if host == nil {
|
||||||
return echo.ErrNotFound
|
err = echo.ErrNotFound
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
host.Echo.ServeHTTP(res, req)
|
host.Echo.ServeHTTP(res, req)
|
||||||
|
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterHostname(hostname string, h *Host) {
|
func RegisterHostname(hostname string, h *Host) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/eternal-flame-AD/go-apparmor/apparmor"
|
||||||
"github.com/eternal-flame-AD/yoake/internal/auth"
|
"github.com/eternal-flame-AD/yoake/internal/auth"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
@ -14,9 +15,10 @@ import (
|
||||||
|
|
||||||
type logEntry struct {
|
type logEntry struct {
|
||||||
middleware.RequestLoggerValues
|
middleware.RequestLoggerValues
|
||||||
Categories []string
|
Categories []string
|
||||||
CleanPath string
|
CleanPath string
|
||||||
Auth auth.RequestAuth
|
AppArmorCon string
|
||||||
|
Auth auth.RequestAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func processLoggerValues(c echo.Context, values middleware.RequestLoggerValues) logEntry {
|
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))
|
logSetRequestCategory(c, fmt.Sprintf("status_%s", statusString))
|
||||||
statusString[i] = 'x'
|
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{
|
return logEntry{
|
||||||
RequestLoggerValues: values,
|
RequestLoggerValues: values,
|
||||||
Categories: logGetCategories(c),
|
Categories: logGetCategories(c),
|
||||||
CleanPath: path.Clean(c.Request().URL.Path),
|
CleanPath: path.Clean(c.Request().URL.Path),
|
||||||
Auth: auth.GetRequestAuth(c),
|
Auth: auth.GetRequestAuth(c),
|
||||||
|
AppArmorCon: aaCon,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,15 @@
|
||||||
{{ $num_visited := (get $session "num_visited") }}
|
{{ $num_visited := (get $session "num_visited") }}
|
||||||
{{ if not $num_visited }}{{ $num_visited = 0 }} {{ end }}
|
{{ if not $num_visited }}{{ $num_visited = 0 }} {{ end }}
|
||||||
{{ set $session "num_visited" (math "argv(1) + 1" $num_visited) }}
|
{{ set $session "num_visited" (math "argv(1) + 1" $num_visited) }}
|
||||||
|
|
||||||
<Response>
|
<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>
|
<Say voice="alice" language="en-US">This is Anne!</Say>
|
||||||
<Redirect method="POST">/twilio/voice/menu.xml</Redirect>
|
<Redirect method="POST">/twilio/voice/menu.xml</Redirect>
|
||||||
</Response>
|
</Response>
|
6
webroot/twilio/voice/voicemail.tpl.xml
Normal file
6
webroot/twilio/voice/voicemail.tpl.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{{ template "/twilio/head.tpl.xml" . }}
|
||||||
|
|
||||||
|
<Response>
|
||||||
|
<!-- TODO: finish -->
|
||||||
|
<Hangup />
|
||||||
|
</Response>
|
Loading…
Add table
Reference in a new issue