diff --git a/cmd/violet/conf.go b/cmd/violet/conf.go index f6a327c..8c56ac9 100644 --- a/cmd/violet/conf.go +++ b/cmd/violet/conf.go @@ -1,15 +1,12 @@ package main type startUpConfig struct { - Database string `json:"db"` - MjwtPubKey string `json:"mjwt_pub_key"` - CertPath string `json:"cert_path"` - KeyPath string `json:"key_path"` SelfSigned bool `json:"self_signed"` ErrorPagePath string `json:"error_page_path"` Listen listenConfig `json:"listen"` InkscapeCmd string `json:"inkscape"` RateLimit uint64 `json:"rate_limit"` + SignerIssuer string `json:"signer_issuer"` } type listenConfig struct { diff --git a/cmd/violet/serve.go b/cmd/violet/serve.go index 2164f98..6de861a 100644 --- a/cmd/violet/serve.go +++ b/cmd/violet/serve.go @@ -18,9 +18,11 @@ import ( "github.com/google/subcommands" "io/fs" "log" + "math/rand" "net/http" "os" "os/signal" + "path/filepath" "syscall" "time" ) @@ -63,26 +65,24 @@ func (s *serveCmd) Execute(ctx context.Context, f *flag.FlagSet, _ ...interface{ return subcommands.ExitFailure } - normalLoad(conf) + // working directory is the parent of the config file + wd := filepath.Dir(s.configPath) + normalLoad(conf, wd) return subcommands.ExitSuccess } -func normalLoad(conf startUpConfig) { +func normalLoad(conf startUpConfig, wd string) { // the cert and key paths are useless in self-signed mode if !conf.SelfSigned { - if conf.CertPath != "" { - // create path to cert dir - err := os.MkdirAll(conf.CertPath, os.ModePerm) - if err != nil { - log.Fatalf("[Violet] Failed to create certificate path '%s'", conf.CertPath) - } + // create path to cert dir + err := os.MkdirAll(filepath.Join(wd, "certs"), os.ModePerm) + if err != nil { + log.Fatal("[Violet] Failed to create certificate path") } - if conf.KeyPath != "" { - // create path to key dir - err := os.MkdirAll(conf.KeyPath, os.ModePerm) - if err != nil { - log.Fatalf("[Violet] Failed to create certificate key path '%s'", conf.KeyPath) - } + // create path to key dir + err = os.MkdirAll(filepath.Join(wd, "keys"), os.ModePerm) + if err != nil { + log.Fatal("[Violet] Failed to create certificate key path") } } @@ -96,25 +96,28 @@ func normalLoad(conf startUpConfig) { } } - // load the MJWT RSA public key from a pem encoded file - mjwtVerify, err := mjwt.NewMJwtVerifierFromFile(conf.MjwtPubKey) + // load the MJWT RSA private key from a pem encoded file + mjwtSigner, err := mjwt.NewMJwtSignerFromFileOrCreate(conf.SignerIssuer, filepath.Join(wd, "violet.private.pem"), rand.New(rand.NewSource(time.Now().UnixNano())), 4096) if err != nil { - log.Fatalf("[Violet] Failed to load MJWT verifier public key from file: '%s'", conf.MjwtPubKey) + log.Fatal("[Violet] Failed to load MJWT verifier public key from file: ", err) } // open sqlite database - db, err := sql.Open("sqlite3", conf.Database) + db, err := sql.Open("sqlite3", "violet.db.sqlite") if err != nil { - log.Fatalf("[Violet] Failed to open database '%s'...", conf.Database) + log.Fatal("[Violet] Failed to open database") } - allowedDomains := domains.New(db) // load allowed domains - acmeChallenges := utils.NewAcmeChallenge() // load acme challenge store - allowedCerts := certs.New(os.DirFS(conf.CertPath), os.DirFS(conf.KeyPath), conf.SelfSigned) // load certificate manager - hybridTransport := proxy.NewHybridTransport() // load reverse proxy - dynamicFavicons := favicons.New(db, conf.InkscapeCmd) // load dynamic favicon provider - dynamicErrorPages := errorPages.New(errorPageDir) // load dynamic error page provider - dynamicRouter := router.NewManager(db, hybridTransport) // load dynamic router manager + certDir := os.DirFS(filepath.Join(wd, "certs")) + keyDir := os.DirFS(filepath.Join(wd, "keys")) + + allowedDomains := domains.New(db) // load allowed domains + acmeChallenges := utils.NewAcmeChallenge() // load acme challenge store + allowedCerts := certs.New(certDir, keyDir, conf.SelfSigned) // load certificate manager + hybridTransport := proxy.NewHybridTransport() // load reverse proxy + dynamicFavicons := favicons.New(db, conf.InkscapeCmd) // load dynamic favicon provider + dynamicErrorPages := errorPages.New(errorPageDir) // load dynamic error page provider + dynamicRouter := router.NewManager(db, hybridTransport) // load dynamic router manager // struct containing config for the http servers srvConf := &servers.Conf{ @@ -127,7 +130,7 @@ func normalLoad(conf startUpConfig) { Acme: acmeChallenges, Certs: allowedCerts, Favicons: dynamicFavicons, - Verify: mjwtVerify, + Signer: mjwtSigner, ErrorPages: dynamicErrorPages, Router: dynamicRouter, } diff --git a/cmd/violet/setup.go b/cmd/violet/setup.go index 13a59af..f88caa9 100644 --- a/cmd/violet/setup.go +++ b/cmd/violet/setup.go @@ -59,14 +59,15 @@ func (s *setupCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) // store answers from questions var answers struct { - SelfSigned bool - ErrorPages bool - ApiListen string - HttpListen string - HttpsListen string - RateLimit uint64 - FirstDomain string - ApiUrl string + SelfSigned bool + ErrorPages bool + ApiListen string + HttpListen string + HttpsListen string + RateLimit uint64 + FirstDomain string + ApiUrl string + SignerIssuer string } // ask main questions @@ -103,6 +104,10 @@ func (s *setupCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) return nil }, }, + { + Name: "SignerIssuer", + Prompt: &survey.Input{Message: "Issuer name to sign API tokens with", Default: "Violet"}, + }, { Name: "FirstDomain", Prompt: &survey.Input{Message: "First domain", Default: "example.com", Help: "Setup the first domain or it will be more difficult to setup later"}, @@ -130,10 +135,6 @@ func (s *setupCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) confEncode := json.NewEncoder(createConf) confEncode.SetIndent("", " ") err = confEncode.Encode(startUpConfig{ - Database: filepath.Join(wdAbs, "violet.sqlite"), - MjwtPubKey: filepath.Join(wdAbs, "mjwt.public.key"), - CertPath: filepath.Join(wdAbs, "certs"), - KeyPath: filepath.Join(wdAbs, "keys"), SelfSigned: answers.SelfSigned, ErrorPagePath: errorPagePath, Listen: listenConfig{ @@ -141,8 +142,9 @@ func (s *setupCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) Http: answers.HttpListen, Https: answers.HttpsListen, }, - InkscapeCmd: "inkscape", - RateLimit: answers.RateLimit, + InkscapeCmd: "inkscape", + RateLimit: answers.RateLimit, + SignerIssuer: answers.SignerIssuer, }) if err != nil { fmt.Println("[Violet] Failed to write config file: ", err) diff --git a/go.mod b/go.mod index 2016034..087bbf2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/MrMelon54/certgen v0.0.1 - github.com/MrMelon54/mjwt v0.0.2 + github.com/MrMelon54/mjwt v0.1.1 github.com/MrMelon54/png2ico v1.0.1 github.com/MrMelon54/rescheduler v0.0.1 github.com/MrMelon54/trie v0.0.2 diff --git a/go.sum b/go.sum index a80e40d..d615fbf 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/MrMelon54/certgen v0.0.1 h1:ycWdZ2RlxQ5qSuejeBVv4aXjGo5hdqqL4j4EjrXnF github.com/MrMelon54/certgen v0.0.1/go.mod h1:GHflVlSbtFLJZLpN1oWyUvDBRrR8qCWiwZLXCCnS2Gc= github.com/MrMelon54/mjwt v0.0.2 h1:jDqyPnFloh80XdSmZ6jt9qhUj/ULcoQ4QSHXPdkAIE4= github.com/MrMelon54/mjwt v0.0.2/go.mod h1:HzY8P6Je+ovS/fwK5sILRMq5mnZT4+WuFRc98LBy7z4= +github.com/MrMelon54/mjwt v0.1.1 h1:m+aTpxbhQCrOPKHN170DQMFR5r938LkviU38unob5Jw= +github.com/MrMelon54/mjwt v0.1.1/go.mod h1:oYrDBWK09Hju98xb+bRQ0wy+RuAzacxYvKYOZchR2Tk= github.com/MrMelon54/png2ico v1.0.1 h1:zJoSSl4OkvSIMWGyGPvb8fWNa0KrUvMIjgNGLNLJhVQ= github.com/MrMelon54/png2ico v1.0.1/go.mod h1:NOv3tO4497mInG+3tcFkIohmxCywUwMLU8WNxJZLVmU= github.com/MrMelon54/rescheduler v0.0.1 h1:gzNvL8X81M00uYN0i9clFVrXCkG1UuLNYxDcvjKyBqo= diff --git a/servers/api.go b/servers/api.go index 135982b..b5bae9e 100644 --- a/servers/api.go +++ b/servers/api.go @@ -18,7 +18,7 @@ func NewApiServer(conf *Conf, compileTarget utils.MultiCompilable) *http.Server // Endpoint for compile action r.POST("/compile", func(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) { - if !hasPerms(conf.Verify, req, "violet:compile") { + if !hasPerms(conf.Signer, req, "violet:compile") { utils.RespondHttpStatus(rw, http.StatusForbidden) return } @@ -30,7 +30,7 @@ func NewApiServer(conf *Conf, compileTarget utils.MultiCompilable) *http.Server // Endpoint for domains r.PUT("/domain/:domain", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { - if !hasPerms(conf.Verify, req, "violet:domains") { + if !hasPerms(conf.Signer, req, "violet:domains") { utils.RespondHttpStatus(rw, http.StatusForbidden) return } @@ -41,7 +41,7 @@ func NewApiServer(conf *Conf, compileTarget utils.MultiCompilable) *http.Server conf.Domains.Compile() }) r.DELETE("/domain/:domain", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { - if !hasPerms(conf.Verify, req, "violet:domains") { + if !hasPerms(conf.Signer, req, "violet:domains") { utils.RespondHttpStatus(rw, http.StatusForbidden) return } @@ -59,7 +59,7 @@ func NewApiServer(conf *Conf, compileTarget utils.MultiCompilable) *http.Server // Endpoint for acme-challenge r.PUT("/acme-challenge/:domain/:key/:value", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { - if !hasPerms(conf.Verify, req, "violet:acme-challenge") { + if !hasPerms(conf.Signer, req, "violet:acme-challenge") { utils.RespondHttpStatus(rw, http.StatusForbidden) return } @@ -72,7 +72,7 @@ func NewApiServer(conf *Conf, compileTarget utils.MultiCompilable) *http.Server rw.WriteHeader(http.StatusAccepted) }) r.DELETE("/acme-challenge/:domain/:key", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) { - if !hasPerms(conf.Verify, req, "violet:acme-challenge") { + if !hasPerms(conf.Signer, req, "violet:acme-challenge") { utils.RespondHttpStatus(rw, http.StatusForbidden) return } diff --git a/servers/api_test.go b/servers/api_test.go index 4120a48..a3c9555 100644 --- a/servers/api_test.go +++ b/servers/api_test.go @@ -34,10 +34,7 @@ func genSnakeOilProv() mjwt.Signer { func genSnakeOilKey(perm string) string { p := claims.NewPermStorage() p.Set(perm) - val, err := snakeOilProv.GenerateJwt("abc", "abc", 5*time.Minute, auth.AccessTokenClaims{ - UserId: 1, - Perms: p, - }) + val, err := snakeOilProv.GenerateJwt("abc", "abc", nil, 5*time.Minute, auth.AccessTokenClaims{Perms: p}) if err != nil { panic(err) } @@ -54,7 +51,7 @@ func TestNewApiServer_Compile(t *testing.T) { apiConf := &Conf{ Domains: &fakeDomains{}, Acme: utils.NewAcmeChallenge(), - Verify: snakeOilProv, + Signer: snakeOilProv, } f := &fakeCompilable{} srv := NewApiServer(apiConf, utils.MultiCompilable{f}) @@ -81,7 +78,7 @@ func TestNewApiServer_AcmeChallenge_Put(t *testing.T) { apiConf := &Conf{ Domains: &fakeDomains{}, Acme: utils.NewAcmeChallenge(), - Verify: snakeOilProv, + Signer: snakeOilProv, } srv := NewApiServer(apiConf, utils.MultiCompilable{}) acmeKey := genSnakeOilKey("violet:acme-challenge") @@ -125,7 +122,7 @@ func TestNewApiServer_AcmeChallenge_Delete(t *testing.T) { apiConf := &Conf{ Domains: &fakeDomains{}, Acme: utils.NewAcmeChallenge(), - Verify: snakeOilProv, + Signer: snakeOilProv, } srv := NewApiServer(apiConf, utils.MultiCompilable{}) acmeKey := genSnakeOilKey("violet:acme-challenge") diff --git a/servers/conf.go b/servers/conf.go index b6fd22d..eb39333 100644 --- a/servers/conf.go +++ b/servers/conf.go @@ -20,7 +20,7 @@ type Conf struct { Acme AcmeChallengeProvider Certs CertProvider Favicons *favicons.Favicons - Verify mjwt.Verifier + Signer mjwt.Verifier ErrorPages *errorPages.ErrorPages Router *router.Manager } diff --git a/servers/http_test.go b/servers/http_test.go index 4069e6e..eaa1fe0 100644 --- a/servers/http_test.go +++ b/servers/http_test.go @@ -14,7 +14,7 @@ func TestNewHttpServer_AcmeChallenge(t *testing.T) { httpConf := &Conf{ Domains: &fakeDomains{}, Acme: utils.NewAcmeChallenge(), - Verify: snakeOilProv, + Signer: snakeOilProv, } srv := NewHttpServer(httpConf) httpConf.Acme.Put("example.com", "456", "456def") diff --git a/servers/https_test.go b/servers/https_test.go index cb763c3..a6f1d68 100644 --- a/servers/https_test.go +++ b/servers/https_test.go @@ -30,7 +30,7 @@ func TestNewHttpsServer_RateLimit(t *testing.T) { RateLimit: 5, Domains: &fakeDomains{}, Certs: certs.New(nil, nil, true), - Verify: snakeOilProv, + Signer: snakeOilProv, Router: router.NewManager(db, proxy.NewHybridTransportWithCalls(ft, ft)), } srv := NewHttpsServer(httpsConf)