diff --git a/cmd/wg-portal/main.go b/cmd/wg-portal/main.go index 11d2be1..f62d675 100644 --- a/cmd/wg-portal/main.go +++ b/cmd/wg-portal/main.go @@ -5,6 +5,7 @@ import ( "github.com/h44z/wg-portal/internal/app/api/core" handlersV0 "github.com/h44z/wg-portal/internal/app/api/v0/handlers" "github.com/h44z/wg-portal/internal/app/auth" + "github.com/h44z/wg-portal/internal/app/filetemplate" "github.com/h44z/wg-portal/internal/app/users" "github.com/h44z/wg-portal/internal/app/wireguard" "os" @@ -63,7 +64,12 @@ func main() { statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, database, wireGuard) internal.AssertNoError(err) - backend, err := app.New(cfg, eventBus, authenticator, userManager, wireGuardManager, statisticsCollector) + templateManager, err := filetemplate.NewTemplateManager(cfg, database, database) + internal.AssertNoError(err) + + backend, err := app.New(cfg, eventBus, authenticator, userManager, wireGuardManager, + statisticsCollector, templateManager) + internal.AssertNoError(err) err = backend.Startup(ctx) internal.AssertNoError(err) diff --git a/internal/adapters/database.go b/internal/adapters/database.go index ad44a5d..838abab 100644 --- a/internal/adapters/database.go +++ b/internal/adapters/database.go @@ -201,7 +201,7 @@ func (r *SqlRepo) migrate() error { func (r *SqlRepo) GetInterface(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, error) { var in domain.Interface - err := r.db.WithContext(ctx).First(&in, id).Error + err := r.db.WithContext(ctx).Preload("Addresses").First(&in, id).Error if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { return nil, domain.ErrNotFound @@ -356,6 +356,17 @@ func (r *SqlRepo) GetInterfaceIps(ctx context.Context) (map[domain.InterfaceIden // region peers +func (r *SqlRepo) GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) { + var peer domain.Peer + + err := r.db.WithContext(ctx).Where("identifier = ?", id).Find(&peer).Error + if err != nil { + return nil, err + } + + return &peer, nil +} + func (r *SqlRepo) GetInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.Peer, error) { var peers []domain.Peer diff --git a/internal/app/api/v0/handlers/endpoint_interfaces.go b/internal/app/api/v0/handlers/endpoint_interfaces.go index 60d2147..8ffad17 100644 --- a/internal/app/api/v0/handlers/endpoint_interfaces.go +++ b/internal/app/api/v0/handlers/endpoint_interfaces.go @@ -5,6 +5,7 @@ import ( "github.com/h44z/wg-portal/internal/app" "github.com/h44z/wg-portal/internal/app/api/v0/model" "github.com/h44z/wg-portal/internal/domain" + "io" "net/http" ) @@ -129,17 +130,23 @@ func (e interfaceEndpoint) handleConfigGet() gin.HandlerFunc { return } - c.JSON(http.StatusOK, `[Interface] -Address = 10.0.0.1/32, fd12:3456:789a::1/128 -ListenPort = 51820 -PrivateKey = -SaveConfig = true + config, err := e.app.GetInterfaceConfig(c.Request.Context(), domain.InterfaceIdentifier(id)) + if err != nil { + c.JSON(http.StatusInternalServerError, model.Error{ + Code: http.StatusInternalServerError, Message: err.Error(), + }) + return + } -[Peer] -PublicKey = -PresharedKey = -AllowedIPs = 10.0.0.2/32,fd12:3456:789a::2/128 -PersistentKeepalive = 25`) + configString, err := io.ReadAll(config) + if err != nil { + c.JSON(http.StatusInternalServerError, model.Error{ + Code: http.StatusInternalServerError, Message: err.Error(), + }) + return + } + + c.JSON(http.StatusOK, string(configString)) } } diff --git a/internal/app/app.go b/internal/app/app.go index 77fead8..19f2b1d 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -19,9 +19,10 @@ type App struct { UserManager WireGuardManager StatisticsCollector + TemplateManager } -func New(cfg *config.Config, bus evbus.MessageBus, authenticator Authenticator, users UserManager, wireGuard WireGuardManager, stats StatisticsCollector) (*App, error) { +func New(cfg *config.Config, bus evbus.MessageBus, authenticator Authenticator, users UserManager, wireGuard WireGuardManager, stats StatisticsCollector, templates TemplateManager) (*App, error) { a := &App{ Config: cfg, @@ -31,6 +32,7 @@ func New(cfg *config.Config, bus evbus.MessageBus, authenticator Authenticator, UserManager: users, WireGuardManager: wireGuard, StatisticsCollector: stats, + TemplateManager: templates, } startupContext, cancel := context.WithTimeout(context.Background(), 30*time.Second) diff --git a/internal/app/filetemplate/manager.go b/internal/app/filetemplate/manager.go new file mode 100644 index 0000000..2179c24 --- /dev/null +++ b/internal/app/filetemplate/manager.go @@ -0,0 +1,52 @@ +package filetemplate + +import ( + "context" + "fmt" + "github.com/h44z/wg-portal/internal/config" + "github.com/h44z/wg-portal/internal/domain" + "io" +) + +type Manager struct { + cfg *config.Config + tplHandler *TemplateHandler + + users UserDatabaseRepo + wg WireguardDatabaseRepo +} + +func NewTemplateManager(cfg *config.Config, users UserDatabaseRepo, wg WireguardDatabaseRepo) (*Manager, error) { + tplHandler, err := newTemplateHandler() + if err != nil { + return nil, fmt.Errorf("failed to initialize template handler: %w", err) + } + + m := &Manager{ + cfg: cfg, + tplHandler: tplHandler, + + users: users, + wg: wg, + } + + return m, nil +} + +func (m Manager) GetInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) (io.Reader, error) { + iface, peers, err := m.wg.GetInterfaceAndPeers(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to fetch interface %s: %w", id, err) + } + + return m.tplHandler.GetInterfaceConfig(iface, peers) +} + +func (m Manager) GetPeerConfig(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error) { + peer, err := m.wg.GetPeer(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to fetch peer %s: %w", id, err) + } + + return m.tplHandler.GetPeerConfig(peer) +} diff --git a/internal/app/filetemplate/repos.go b/internal/app/filetemplate/repos.go new file mode 100644 index 0000000..15d0854 --- /dev/null +++ b/internal/app/filetemplate/repos.go @@ -0,0 +1,16 @@ +package filetemplate + +import ( + "context" + "github.com/h44z/wg-portal/internal/domain" +) + +type UserDatabaseRepo interface { + GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) +} + +type WireguardDatabaseRepo interface { + GetInterfaceAndPeers(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, []domain.Peer, error) + GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error) + GetInterface(ctx context.Context, id domain.InterfaceIdentifier) (*domain.Interface, error) +} diff --git a/internal/app/template.go b/internal/app/filetemplate/template.go similarity index 76% rename from internal/app/template.go rename to internal/app/filetemplate/template.go index 15f95ad..58d85a8 100644 --- a/internal/app/template.go +++ b/internal/app/filetemplate/template.go @@ -1,4 +1,4 @@ -package app +package filetemplate import ( "bytes" @@ -14,30 +14,34 @@ import ( //go:embed tpl_files/* var TemplateFiles embed.FS -type templateHandler struct { +type TemplateHandler struct { wireGuardTemplates *template.Template mailHtmlTemplates *htmlTemplate.Template mailTextTemplates *template.Template } -func newTemplateHandler() (*templateHandler, error) { - templateCache, err := template.New("WireGuard").ParseFS(TemplateFiles, "tpl_files/*.tpl") +func newTemplateHandler() (*TemplateHandler, error) { + tplFuncs := template.FuncMap{ + "CidrsToString": domain.CidrsToString, + } + + templateCache, err := template.New("WireGuard").Funcs(tplFuncs).ParseFS(TemplateFiles, "tpl_files/*.tpl") if err != nil { return nil, err } - mailHtmlTemplateCache, err := htmlTemplate.New("WireGuard").ParseFS(TemplateFiles, "tpl_files/*.gohtml") + mailHtmlTemplateCache, err := htmlTemplate.New("WireGuard").Funcs(tplFuncs).ParseFS(TemplateFiles, "tpl_files/*.gohtml") if err != nil { return nil, fmt.Errorf("failed to parse html template files: %w", err) } - mailTxtTemplateCache, err := template.New("WireGuard").ParseFS(TemplateFiles, "tpl_files/*.gotpl") + mailTxtTemplateCache, err := template.New("WireGuard").Funcs(tplFuncs).ParseFS(TemplateFiles, "tpl_files/*.gotpl") if err != nil { return nil, fmt.Errorf("failed to parse text template files: %w", err) } - handler := &templateHandler{ + handler := &TemplateHandler{ wireGuardTemplates: templateCache, mailHtmlTemplates: mailHtmlTemplateCache, mailTextTemplates: mailTxtTemplateCache, @@ -46,7 +50,7 @@ func newTemplateHandler() (*templateHandler, error) { return handler, nil } -func (c templateHandler) GetInterfaceConfig(cfg *domain.Interface, peers []*domain.Peer) (io.Reader, error) { +func (c TemplateHandler) GetInterfaceConfig(cfg *domain.Interface, peers []domain.Peer) (io.Reader, error) { var tplBuff bytes.Buffer err := c.wireGuardTemplates.ExecuteTemplate(&tplBuff, "wg_interface.tpl", map[string]interface{}{ @@ -63,7 +67,7 @@ func (c templateHandler) GetInterfaceConfig(cfg *domain.Interface, peers []*doma return &tplBuff, nil } -func (c templateHandler) GetPeerConfig(peer *domain.Peer) (io.Reader, error) { +func (c TemplateHandler) GetPeerConfig(peer *domain.Peer) (io.Reader, error) { var tplBuff bytes.Buffer err := c.wireGuardTemplates.ExecuteTemplate(&tplBuff, "wg_peer.tpl", map[string]interface{}{ @@ -79,7 +83,7 @@ func (c templateHandler) GetPeerConfig(peer *domain.Peer) (io.Reader, error) { return &tplBuff, nil } -func (c templateHandler) GetConfigMail(user *domain.User, peer *domain.Peer, link string) (io.Reader, io.Reader, error) { +func (c TemplateHandler) GetConfigMail(user *domain.User, peer *domain.Peer, link string) (io.Reader, io.Reader, error) { var tplBuff bytes.Buffer var htmlTplBuff bytes.Buffer @@ -104,7 +108,7 @@ func (c templateHandler) GetConfigMail(user *domain.User, peer *domain.Peer, lin return &tplBuff, &htmlTplBuff, nil } -func (c templateHandler) GetConfigMailWithAttachment(user *domain.User, peer *domain.Peer) (io.Reader, io.Reader, error) { +func (c TemplateHandler) GetConfigMailWithAttachment(user *domain.User, peer *domain.Peer) (io.Reader, io.Reader, error) { var tplBuff bytes.Buffer var htmlTplBuff bytes.Buffer diff --git a/internal/app/tpl_files/mail_with_attachment.gohtml b/internal/app/filetemplate/tpl_files/mail_with_attachment.gohtml similarity index 100% rename from internal/app/tpl_files/mail_with_attachment.gohtml rename to internal/app/filetemplate/tpl_files/mail_with_attachment.gohtml diff --git a/internal/app/tpl_files/mail_with_attachment.gotpl b/internal/app/filetemplate/tpl_files/mail_with_attachment.gotpl similarity index 100% rename from internal/app/tpl_files/mail_with_attachment.gotpl rename to internal/app/filetemplate/tpl_files/mail_with_attachment.gotpl diff --git a/internal/app/tpl_files/mail_with_link.gohtml b/internal/app/filetemplate/tpl_files/mail_with_link.gohtml similarity index 100% rename from internal/app/tpl_files/mail_with_link.gohtml rename to internal/app/filetemplate/tpl_files/mail_with_link.gohtml diff --git a/internal/app/tpl_files/mail_with_link.gotpl b/internal/app/filetemplate/tpl_files/mail_with_link.gotpl similarity index 100% rename from internal/app/tpl_files/mail_with_link.gotpl rename to internal/app/filetemplate/tpl_files/mail_with_link.gotpl diff --git a/internal/app/tpl_files/wg_interface.tpl b/internal/app/filetemplate/tpl_files/wg_interface.tpl similarity index 97% rename from internal/app/tpl_files/wg_interface.tpl rename to internal/app/filetemplate/tpl_files/wg_interface.tpl index a47542a..b394856 100644 --- a/internal/app/tpl_files/wg_interface.tpl +++ b/internal/app/filetemplate/tpl_files/wg_interface.tpl @@ -12,7 +12,7 @@ # Core settings PrivateKey = {{ .Interface.KeyPair.PrivateKey }} -Address = {{ .Interface.AddressStr }} +Address = {{ CidrsToString .Interface.Addresses }} # Misc. settings (optional) {{- if ne .Interface.ListenPort 0}} diff --git a/internal/app/tpl_files/wg_peer.tpl b/internal/app/filetemplate/tpl_files/wg_peer.tpl similarity index 100% rename from internal/app/tpl_files/wg_peer.tpl rename to internal/app/filetemplate/tpl_files/wg_peer.tpl diff --git a/internal/app/repos.go b/internal/app/repos.go index 717a406..6a25226 100644 --- a/internal/app/repos.go +++ b/internal/app/repos.go @@ -3,6 +3,7 @@ package app import ( "context" "github.com/h44z/wg-portal/internal/domain" + "io" ) type Authenticator interface { @@ -41,3 +42,8 @@ type WireGuardManager interface { type StatisticsCollector interface { StartBackgroundJobs(ctx context.Context) } + +type TemplateManager interface { + GetInterfaceConfig(ctx context.Context, id domain.InterfaceIdentifier) (io.Reader, error) + GetPeerConfig(ctx context.Context, id domain.PeerIdentifier) (io.Reader, error) +}